communities-contracts/test/CommunityERC721.t.sol

267 lines
9.1 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import { Test } from "forge-std/Test.sol";
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
import { DeployOwnerAndMasterToken } from "../script/DeployOwnerAndMasterToken.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { CommunityOwnable } from "../contracts/CommunityOwnable.sol";
import { BaseToken } from "../contracts/tokens/BaseToken.sol";
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
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
import { CommunityERC721 } from "../contracts/tokens/CommunityERC721.sol";
contract CommunityERC721Test is Test {
CommunityERC721 internal collectibleV1;
address internal deployer;
address[] internal accounts = new address[](4);
string internal name = "Test";
string internal symbol = "TEST";
string internal baseURI = "http://local.dev";
uint256 internal maxSupply = 4;
bool internal remoteBurnable = true;
bool internal transferable = true;
function setUp() public virtual {
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
DeployOwnerAndMasterToken deployment = new DeployOwnerAndMasterToken();
(OwnerToken ownerToken, MasterToken masterToken, DeploymentConfig deploymentConfig) = deployment.run();
deployer = deploymentConfig.deployer();
collectibleV1 = new CommunityERC721(
name, symbol, maxSupply, remoteBurnable, transferable, baseURI, address(ownerToken), address(masterToken)
);
accounts[0] = makeAddr("one");
accounts[1] = makeAddr("two");
accounts[2] = makeAddr("three");
accounts[3] = makeAddr("four");
}
}
contract DeploymentTest is CommunityERC721Test {
function test_Deployment() public {
assertEq(collectibleV1.name(), name);
assertEq(collectibleV1.symbol(), symbol);
assertEq(collectibleV1.maxSupply(), maxSupply);
assertEq(collectibleV1.remoteBurnable(), remoteBurnable);
assertEq(collectibleV1.transferable(), transferable);
assertEq(collectibleV1.baseTokenURI(), baseURI);
}
}
contract MintToTest is CommunityERC721Test {
event StatusMint(address indexed from, address indexed to, uint256 indexed tokenId);
function setUp() public virtual override {
CommunityERC721Test.setUp();
}
function test_RevertWhen_SenderIsNotOwner() public {
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
collectibleV1.mintTo(accounts);
}
function test_RevertWhen_MaxSupplyIsReached() public {
vm.startPrank(deployer);
collectibleV1.mintTo(accounts);
address[] memory otherAddresses = new address[](1);
otherAddresses[0] = makeAddr("anotherAccount");
vm.expectRevert(BaseToken.BaseToken_MaxSupplyReached.selector);
collectibleV1.mintTo(otherAddresses);
assertEq(collectibleV1.maxSupply(), maxSupply);
assertEq(collectibleV1.totalSupply(), maxSupply);
}
function test_MintTo() public {
uint256 length = accounts.length;
for (uint8 i = 0; i < length; i++) {
assertEq(collectibleV1.balanceOf(accounts[i]), 0);
}
vm.prank(deployer);
for (uint8 i = 0; i < length; i++) {
vm.expectEmit(true, true, true, true);
emit StatusMint(address(0), accounts[i], i);
}
collectibleV1.mintTo(accounts);
for (uint8 i = 0; i < length; i++) {
assertEq(collectibleV1.balanceOf(accounts[i]), 1);
}
}
}
contract RemoteBurnTest is CommunityERC721Test {
function setUp() public virtual override {
CommunityERC721Test.setUp();
}
function test_RevertWhen_SenderIsNotOwner() public {
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
collectibleV1.remoteBurn(ids);
}
function test_RemoteBurn() public {
vm.startPrank(deployer);
collectibleV1.mintTo(accounts);
assertEq(collectibleV1.totalSupply(), maxSupply);
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
collectibleV1.remoteBurn(ids);
assertEq(collectibleV1.balanceOf(accounts[0]), 0);
assertEq(collectibleV1.totalSupply(), maxSupply - 1);
}
}
contract SafeBatchTransferFromTest is CommunityERC721Test {
function setUp() public virtual override {
CommunityERC721Test.setUp();
}
function test_RevertWhen_ReceiversAndIdsMismatch() public {
// ensure sender owns a token
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = accounts[1];
// ids must be of same length as `receivers`
uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;
vm.prank(accounts[0]);
vm.expectRevert(BaseToken.BaseToken_ReceiversAndIdsMismatch.selector);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_RevertWhen_NotAuthorized() public {
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = accounts[3];
// ids must be of same length as `accounts`
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
// ensure accounts[3] has no ownership or approval of token with id `0`
assertEq(collectibleV1.ownerOf(ids[0]), accounts[0]);
assertEq(collectibleV1.isApprovedForAll(accounts[0], receivers[0]), false);
vm.prank(receivers[0]);
vm.expectRevert(bytes("ERC721: caller is not token owner or approved"));
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_RevertWhen_ReceiverAddressIsZero() public {
// ensure sender owns a token
vm.prank(deployer);
collectibleV1.mintTo(accounts);
address[] memory receivers = new address[](1);
receivers[0] = address(0);
uint256[] memory ids = new uint256[](1);
ids[0] = 0;
vm.prank(accounts[0]);
vm.expectRevert(bytes("ERC721: transfer to the zero address"));
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
}
function test_SafeBatchTransferFrom() public {
// ensure sender owns a token
vm.prank(deployer);
address[] memory sameAddresses = new address[](3);
sameAddresses[0] = accounts[0];
sameAddresses[1] = accounts[0];
sameAddresses[2] = accounts[0];
collectibleV1.mintTo(sameAddresses);
address[] memory receivers = new address[](3);
receivers[0] = accounts[1];
receivers[1] = accounts[2];
receivers[2] = accounts[3];
uint256[] memory ids = new uint256[](3);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
vm.prank(accounts[0]);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
assertEq(collectibleV1.balanceOf(accounts[0]), 0);
assertEq(collectibleV1.balanceOf(accounts[1]), 1);
assertEq(collectibleV1.balanceOf(accounts[2]), 1);
assertEq(collectibleV1.balanceOf(accounts[3]), 1);
}
function test_SafeBatchTransferFromToSingleReceiver() public {
// ensure sender owns a token
vm.prank(deployer);
address[] memory sameAddresses = new address[](3);
sameAddresses[0] = accounts[0];
sameAddresses[1] = accounts[0];
sameAddresses[2] = accounts[0];
collectibleV1.mintTo(sameAddresses);
address[] memory receivers = new address[](3);
receivers[0] = accounts[1];
receivers[1] = accounts[1];
receivers[2] = accounts[1];
uint256[] memory ids = new uint256[](3);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
vm.prank(accounts[0]);
collectibleV1.safeBatchTransferFrom(accounts[0], receivers, ids, "");
assertEq(collectibleV1.balanceOf(accounts[0]), 0);
assertEq(collectibleV1.balanceOf(accounts[1]), 3);
}
}
contract NotTransferableTest is CommunityERC721Test {
function setUp() public virtual override {
DeployOwnerAndMasterToken deployment = new DeployOwnerAndMasterToken();
(OwnerToken ownerToken, MasterToken masterToken, DeploymentConfig deploymentConfig) = deployment.run();
deployer = deploymentConfig.deployer();
collectibleV1 = new CommunityERC721(
name, symbol, maxSupply, remoteBurnable, false, baseURI, address(ownerToken), address(masterToken)
);
accounts[0] = makeAddr("one");
accounts[1] = makeAddr("two");
accounts[2] = makeAddr("three");
accounts[3] = makeAddr("four");
}
function test_RevertWhen_TokenIsNotTransferable() public {
// ensure accounts own tokens
vm.prank(deployer);
collectibleV1.mintTo(accounts);
uint256[] memory ids = new uint256[](4);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;
ids[3] = 3;
vm.prank(accounts[0]);
vm.expectRevert(BaseToken.BaseToken_NotTransferable.selector);
collectibleV1.safeBatchTransferFrom(accounts[0], accounts, ids, "");
}
}