add CommunityVault contract (#22)
* add CommunityVault contract * use named import * test vault transfer functions for erc20 and erc721 * fix camel case test name * update format with latest version of forge
This commit is contained in:
parent
d86381ffd1
commit
6f83e7346c
|
@ -5,6 +5,7 @@ AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827)
|
||||||
CollectibleV1Test:test_Deployment() (gas: 36386)
|
CollectibleV1Test:test_Deployment() (gas: 36386)
|
||||||
CommunityERC20Test:test_Deployment() (gas: 35198)
|
CommunityERC20Test:test_Deployment() (gas: 35198)
|
||||||
CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
|
CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
|
||||||
|
CommunityVaultTest:test_Deployment() (gas: 10436)
|
||||||
CreateTest:test_Create() (gas: 2269916)
|
CreateTest:test_Create() (gas: 2269916)
|
||||||
CreateTest:test_Create() (gas: 2568994)
|
CreateTest:test_Create() (gas: 2568994)
|
||||||
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
|
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||||
|
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
||||||
|
import { CommunityOwnable } from "./CommunityOwnable.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title CommunityVault
|
||||||
|
* @dev This contract acts as a Vault for storing ERC20 and ERC721 tokens.
|
||||||
|
* It allows any user to deposit tokens into the vault.
|
||||||
|
* Only community owners, as defined in the CommunityOwnable contract, have
|
||||||
|
* permissions to transfer these tokens out of the vault.
|
||||||
|
*/
|
||||||
|
contract CommunityVault is CommunityOwnable {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
|
||||||
|
error CommunityVault_LengthMismatch();
|
||||||
|
error CommunityVault_NoRecipients();
|
||||||
|
error CommunityVault_TransferAmountZero();
|
||||||
|
|
||||||
|
constructor(address _ownerToken, address _masterToken) CommunityOwnable(_ownerToken, _masterToken) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Transfers ERC20 tokens to a list of addresses.
|
||||||
|
* @param token The ERC20 token address.
|
||||||
|
* @param recipients The list of recipient addresses.
|
||||||
|
* @param amounts The list of amounts to transfer to each recipient.
|
||||||
|
*/
|
||||||
|
function transferERC20(
|
||||||
|
address token,
|
||||||
|
address[] calldata recipients,
|
||||||
|
uint256[] calldata amounts
|
||||||
|
)
|
||||||
|
external
|
||||||
|
onlyCommunityOwnerOrTokenMaster
|
||||||
|
{
|
||||||
|
if (recipients.length != amounts.length) {
|
||||||
|
revert CommunityVault_LengthMismatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipients.length == 0) {
|
||||||
|
revert CommunityVault_NoRecipients();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < recipients.length; i++) {
|
||||||
|
if (amounts[i] == 0) {
|
||||||
|
revert CommunityVault_TransferAmountZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
IERC20(token).safeTransfer(recipients[i], amounts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Transfers ERC721 tokens to a list of addresses.
|
||||||
|
* @param token The ERC721 token address.
|
||||||
|
* @param recipients The list of recipient addresses.
|
||||||
|
* @param tokenIds The list of token IDs to transfer to each recipient.
|
||||||
|
*/
|
||||||
|
function transferERC721(
|
||||||
|
address token,
|
||||||
|
address[] calldata recipients,
|
||||||
|
uint256[] calldata tokenIds
|
||||||
|
)
|
||||||
|
external
|
||||||
|
onlyCommunityOwnerOrTokenMaster
|
||||||
|
{
|
||||||
|
if (recipients.length != tokenIds.length) {
|
||||||
|
revert CommunityVault_LengthMismatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipients.length == 0) {
|
||||||
|
revert CommunityVault_NoRecipients();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < recipients.length; i++) {
|
||||||
|
IERC721(token).safeTransferFrom(address(this), recipients[i], tokenIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,12 +43,7 @@ contract CommunityMasterTokenFactory is BaseTokenFactory {
|
||||||
revert CommunityMasterTokenFactory_InvalidOwnerTokenAddress();
|
revert CommunityMasterTokenFactory_InvalidOwnerTokenAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
MasterToken masterToken = new MasterToken(
|
MasterToken masterToken = new MasterToken(_name, _symbol, _baseURI, _ownerToken);
|
||||||
_name,
|
|
||||||
_symbol,
|
|
||||||
_baseURI,
|
|
||||||
_ownerToken
|
|
||||||
);
|
|
||||||
emit CreateToken(address(masterToken));
|
emit CreateToken(address(masterToken));
|
||||||
return address(masterToken);
|
return address(masterToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,13 +50,7 @@ contract CommunityOwnerTokenFactory is BaseTokenFactory {
|
||||||
revert CommunityOwnerTokenFactory_InvalidSignerPublicKey();
|
revert CommunityOwnerTokenFactory_InvalidSignerPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
OwnerToken ownerToken = new OwnerToken(
|
OwnerToken ownerToken = new OwnerToken(_name, _symbol, _baseURI, _receiver, _signerPublicKey);
|
||||||
_name,
|
|
||||||
_symbol,
|
|
||||||
_baseURI,
|
|
||||||
_receiver,
|
|
||||||
_signerPublicKey
|
|
||||||
);
|
|
||||||
emit CreateToken(address(ownerToken));
|
emit CreateToken(address(ownerToken));
|
||||||
return address(ownerToken);
|
return address(ownerToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
|
||||||
|
contract TestERC20Token is ERC20 {
|
||||||
|
constructor() ERC20("Test Token", "TEST") { }
|
||||||
|
|
||||||
|
function mint(address to, uint256 amount) external {
|
||||||
|
_mint(to, amount);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// SPDX-License-Identifier: Mozilla Public License 2.0
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
||||||
|
|
||||||
|
contract TestERC721Token is ERC721 {
|
||||||
|
uint256 private currentTokenId;
|
||||||
|
|
||||||
|
constructor() ERC721("Test NFT", "TNFT") { }
|
||||||
|
|
||||||
|
function mint(address to) external {
|
||||||
|
_mint(to, currentTokenId);
|
||||||
|
currentTokenId++;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,10 +23,7 @@ contract DeployOwnerAndMasterToken is BaseScript {
|
||||||
);
|
);
|
||||||
|
|
||||||
MasterToken masterToken = new MasterToken(
|
MasterToken masterToken = new MasterToken(
|
||||||
masterTokenConfig.name,
|
masterTokenConfig.name, masterTokenConfig.symbol, masterTokenConfig.baseURI, address(ownerToken)
|
||||||
masterTokenConfig.symbol,
|
|
||||||
masterTokenConfig.baseURI,
|
|
||||||
address(ownerToken)
|
|
||||||
);
|
);
|
||||||
vm.stopBroadcast();
|
vm.stopBroadcast();
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,7 @@ contract CollectibleV1Test is Test {
|
||||||
deployer = deploymentConfig.deployer();
|
deployer = deploymentConfig.deployer();
|
||||||
|
|
||||||
collectibleV1 = new CollectibleV1(
|
collectibleV1 = new CollectibleV1(
|
||||||
name,
|
name, symbol, maxSupply, remoteBurnable, transferable, baseURI, address(ownerToken), address(masterToken)
|
||||||
symbol,
|
|
||||||
maxSupply,
|
|
||||||
remoteBurnable,
|
|
||||||
transferable,
|
|
||||||
baseURI,
|
|
||||||
address(ownerToken),
|
|
||||||
address(masterToken)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
accounts[0] = makeAddr("one");
|
accounts[0] = makeAddr("one");
|
||||||
|
|
|
@ -27,15 +27,8 @@ contract CommunityERC20Test is Test {
|
||||||
|
|
||||||
deployer = deploymentConfig.deployer();
|
deployer = deploymentConfig.deployer();
|
||||||
|
|
||||||
communityToken = new CommunityERC20(
|
communityToken =
|
||||||
name,
|
new CommunityERC20(name, symbol, decimals, maxSupply, baseURI, address(ownerToken), address(masterToken));
|
||||||
symbol,
|
|
||||||
decimals,
|
|
||||||
maxSupply,
|
|
||||||
baseURI,
|
|
||||||
address(ownerToken),
|
|
||||||
address(masterToken)
|
|
||||||
);
|
|
||||||
|
|
||||||
accounts[0] = makeAddr("one");
|
accounts[0] = makeAddr("one");
|
||||||
accounts[1] = makeAddr("two");
|
accounts[1] = makeAddr("two");
|
||||||
|
|
|
@ -0,0 +1,215 @@
|
||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
import { Test } from "forge-std/Test.sol";
|
||||||
|
import { TestERC20Token } from "../contracts/mocks/TestERC20Token.sol";
|
||||||
|
import { TestERC721Token } from "../contracts/mocks/TestERC721Token.sol";
|
||||||
|
import { CommunityVault } from "../contracts/CommunityVault.sol";
|
||||||
|
import { CommunityOwnable } from "../contracts/CommunityOwnable.sol";
|
||||||
|
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
|
||||||
|
import { DeployOwnerAndMasterToken } from "../script/DeployOwnerAndMasterToken.s.sol";
|
||||||
|
import { CommunityERC20 } from "../contracts/tokens/CommunityERC20.sol";
|
||||||
|
import { OwnerToken } from "../contracts/tokens/OwnerToken.sol";
|
||||||
|
import { MasterToken } from "../contracts/tokens/MasterToken.sol";
|
||||||
|
|
||||||
|
contract CommunityVaultTest is Test {
|
||||||
|
CommunityVault internal vault;
|
||||||
|
|
||||||
|
address[] internal accounts = new address[](2);
|
||||||
|
address internal deployer;
|
||||||
|
|
||||||
|
TestERC20Token internal erc20Token;
|
||||||
|
TestERC721Token internal erc721Token;
|
||||||
|
CommunityERC20 internal communityERC20Token;
|
||||||
|
OwnerToken internal ownerToken;
|
||||||
|
MasterToken internal masterToken;
|
||||||
|
|
||||||
|
function setUp() public virtual {
|
||||||
|
DeploymentConfig deploymentConfig;
|
||||||
|
DeployOwnerAndMasterToken deployment = new DeployOwnerAndMasterToken();
|
||||||
|
(ownerToken, masterToken, deploymentConfig) = deployment.run();
|
||||||
|
|
||||||
|
deployer = deploymentConfig.deployer();
|
||||||
|
|
||||||
|
erc20Token = new TestERC20Token();
|
||||||
|
erc721Token = new TestERC721Token();
|
||||||
|
|
||||||
|
communityERC20Token = new CommunityERC20("Test", "TST", 18, 100, "", address(ownerToken), address(masterToken));
|
||||||
|
|
||||||
|
vault = new CommunityVault(address(ownerToken), address(masterToken));
|
||||||
|
|
||||||
|
accounts[0] = makeAddr("one");
|
||||||
|
accounts[1] = makeAddr("two");
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_Deployment() public {
|
||||||
|
assertEq(vault.ownerToken(), address(ownerToken));
|
||||||
|
assertEq(vault.masterToken(), address(masterToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract CommunityVaultBaseERC20Test is CommunityVaultTest {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultTest.setUp();
|
||||||
|
|
||||||
|
// mint 10 tokens to user
|
||||||
|
address user = accounts[0];
|
||||||
|
erc20Token.mint(user, 10e18);
|
||||||
|
|
||||||
|
// user transfer 10 tokens to the vault
|
||||||
|
vm.prank(user);
|
||||||
|
erc20Token.transfer(address(vault), 10e18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TransferERC20ByNonAdminTest is CommunityVaultBaseERC20Test {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultBaseERC20Test.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_revertIfCalledByNonAdmin() public {
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 10e18);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](2);
|
||||||
|
amounts[0] = 1;
|
||||||
|
amounts[1] = 1;
|
||||||
|
|
||||||
|
vm.prank(accounts[0]);
|
||||||
|
|
||||||
|
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
|
||||||
|
vault.transferERC20(address(erc20Token), accounts, amounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TransferERC20ByAdminTest is CommunityVaultBaseERC20Test {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultBaseERC20Test.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_LengthMismatch() public {
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 10e18);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](1);
|
||||||
|
amounts[0] = 5e18;
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vm.expectRevert(CommunityVault.CommunityVault_LengthMismatch.selector);
|
||||||
|
vault.transferERC20(address(erc20Token), accounts, amounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_TransferAmountZero() public {
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 10e18);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](2);
|
||||||
|
amounts[0] = 5e18;
|
||||||
|
amounts[1] = 0;
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vm.expectRevert(CommunityVault.CommunityVault_TransferAmountZero.selector);
|
||||||
|
vault.transferERC20(address(erc20Token), accounts, amounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_NoRecipients() public {
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 10e18);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](0);
|
||||||
|
address[] memory tmpAccounts = new address[](0);
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vm.expectRevert(CommunityVault.CommunityVault_NoRecipients.selector);
|
||||||
|
vault.transferERC20(address(erc20Token), tmpAccounts, amounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_AdminCanTransferERC20() public {
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 10e18);
|
||||||
|
|
||||||
|
uint256[] memory amounts = new uint256[](2);
|
||||||
|
amounts[0] = 5e18;
|
||||||
|
amounts[1] = 5e18;
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vault.transferERC20(address(erc20Token), accounts, amounts);
|
||||||
|
|
||||||
|
assertEq(erc20Token.balanceOf(address(vault)), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract CommunityVaultBaseERC721Test is CommunityVaultTest {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultTest.setUp();
|
||||||
|
|
||||||
|
// mint 2 token to user
|
||||||
|
address user = accounts[0];
|
||||||
|
erc721Token.mint(user);
|
||||||
|
erc721Token.mint(user);
|
||||||
|
|
||||||
|
// user transfer 2 tokens to the vault
|
||||||
|
vm.startPrank(user);
|
||||||
|
erc721Token.transferFrom(user, address(vault), 0);
|
||||||
|
erc721Token.transferFrom(user, address(vault), 1);
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TransferERC721ByNonAdminTest is CommunityVaultBaseERC721Test {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultBaseERC721Test.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_RevertIfCalledByNonAdmin() public {
|
||||||
|
assertEq(erc721Token.balanceOf(address(vault)), 2);
|
||||||
|
uint256[] memory ids = new uint256[](2);
|
||||||
|
ids[0] = 0;
|
||||||
|
ids[1] = 1;
|
||||||
|
|
||||||
|
vm.prank(accounts[0]);
|
||||||
|
vm.expectRevert(CommunityOwnable.CommunityOwnable_NotAuthorized.selector);
|
||||||
|
|
||||||
|
vault.transferERC721(address(erc721Token), accounts, ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TransferERC721ByAdminTest is CommunityVaultBaseERC721Test {
|
||||||
|
function setUp() public virtual override {
|
||||||
|
CommunityVaultBaseERC721Test.setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_LengthMismatch() public {
|
||||||
|
assertEq(erc721Token.balanceOf(address(vault)), 2);
|
||||||
|
|
||||||
|
uint256[] memory ids = new uint256[](1);
|
||||||
|
ids[0] = 0;
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vm.expectRevert(CommunityVault.CommunityVault_LengthMismatch.selector);
|
||||||
|
vault.transferERC721(address(erc721Token), accounts, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_NoRecipients() public {
|
||||||
|
assertEq(erc721Token.balanceOf(address(vault)), 2);
|
||||||
|
|
||||||
|
uint256[] memory ids = new uint256[](0);
|
||||||
|
address[] memory tmpAccounts = new address[](0);
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vm.expectRevert(CommunityVault.CommunityVault_NoRecipients.selector);
|
||||||
|
vault.transferERC721(address(erc721Token), tmpAccounts, ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_AdminCanTransferERC721() public {
|
||||||
|
assertEq(erc721Token.balanceOf(address(vault)), 2);
|
||||||
|
|
||||||
|
assertEq(erc721Token.ownerOf(0), address(vault));
|
||||||
|
assertEq(erc721Token.ownerOf(1), address(vault));
|
||||||
|
|
||||||
|
uint256[] memory ids = new uint256[](2);
|
||||||
|
ids[0] = 0;
|
||||||
|
ids[1] = 1;
|
||||||
|
|
||||||
|
vm.prank(deployer);
|
||||||
|
vault.transferERC721(address(erc721Token), accounts, ids);
|
||||||
|
|
||||||
|
assertEq(erc721Token.balanceOf(address(vault)), 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue