Skip to content

Commit

Permalink
feat(CommunityVault): add erc721 deposit function
Browse files Browse the repository at this point in the history
  • Loading branch information
0xb337r007 committed Mar 1, 2024
1 parent 926135c commit e354b7a
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 26 deletions.
45 changes: 34 additions & 11 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ AddEntryTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 14827)
CollectibleV1Test:test_Deployment() (gas: 36386)
CommunityERC20Test:test_Deployment() (gas: 35198)
CommunityTokenDeployerTest:test_Deployment() (gas: 14805)
CommunityVaultTest:test_Deployment() (gas: 10436)
CreateTest:test_Create() (gas: 2269916)
CreateTest:test_Create() (gas: 2568994)
CommunityVaultBaseERC20Test:test_Deployment() (gas: 10572)
CommunityVaultBaseERC721Test:test_Deployment() (gas: 10572)
CommunityVaultBaseTransferERC721Test:test_Deployment() (gas: 10572)
CommunityVaultDepositERC721Test:testSuccessfulDepositERC721() (gas: 184700)
CommunityVaultDepositERC721Test:test_Deployment() (gas: 10714)
CommunityVaultTest:test_Deployment() (gas: 10572)
CreateTest:test_Create() (gas: 2271919)
CreateTest:test_Create() (gas: 2569994)
CreateTest:test_RevertWhen_InvalidOwnerTokenAddress() (gas: 15523)
CreateTest:test_RevertWhen_InvalidReceiverAddress() (gas: 15656)
CreateTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 17057)
Expand All @@ -17,32 +22,35 @@ CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16421)
CreateTest:test_RevertWhen_SenderIsNotTokenDeployer() (gas: 16524)
DeployContracts:test() (gas: 120)
DeployOwnerAndMasterToken:test() (gas: 120)
DeployTest:test_Deploy() (gas: 4911563)
DeployTest:test_Deploy() (gas: 4914566)
DeployTest:test_Deployment() (gas: 14947)
DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 4907793)
DeployTest:test_RevertWhen_AlreadyDeployed() (gas: 4910796)
DeployTest:test_RevertWhen_InvalidCommunityAddress() (gas: 51385)
DeployTest:test_RevertWhen_InvalidDeployerAddress() (gas: 55272)
DeployTest:test_RevertWhen_InvalidDeploymentSignature() (gas: 65617)
DeployTest:test_RevertWhen_InvalidSignerPublicKey() (gas: 53433)
DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2694728)
DeployTest:test_RevertWhen_InvalidTokenMetadata() (gas: 2695728)
DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 14671)
DeploymentTest:test_Deployment() (gas: 17295)
DepositERC20Test:testDepositZeroTokens() (gas: 15199)
DepositERC20Test:testSuccessfulDepositERC20() (gas: 85584)
DepositERC20Test:test_Deployment() (gas: 10594)
GetEntryTest:test_ReturnZeroAddressIfEntryDoesNotExist() (gas: 11906)
MintToTest:test_Deployment() (gas: 35220)
MintToTest:test_Deployment() (gas: 36386)
MintToTest:test_Deployment() (gas: 83220)
MintToTest:test_MintTo() (gas: 178063)
MintToTest:test_MintTo() (gas: 526242)
MintToTest:test_MintTo() (gas: 526292)
MintToTest:test_RevertWhen_AddressesAndAmountsAreNotEqualLength() (gas: 29673)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20653)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511039)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 20703)
MintToTest:test_RevertWhen_MaxSupplyIsReached() (gas: 511139)
MintToTest:test_RevertWhen_MaxSupplyReached() (gas: 134799)
MintToTest:test_RevertWhen_SenderIsNotOwner() (gas: 31544)
OwnerTokenTest:test_Deployment() (gas: 83220)
RemoteBurnTest:test_Deployment() (gas: 36386)
RemoteBurnTest:test_Deployment() (gas: 83242)
RemoteBurnTest:test_RemoteBurn() (gas: 459164)
RemoteBurnTest:test_RemoteBurn() (gas: 459214)
RemoteBurnTest:test_RevertWhen_RemoteBurn() (gas: 14768)
RemoteBurnTest:test_RevertWhen_SenderIsNotOwner() (gas: 20379)
SetCommunityTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas: 12941)
Expand Down Expand Up @@ -75,4 +83,19 @@ SetTokenDeployerAddressTest:test_RevertWhen_InvalidTokenDeployerAddress() (gas:
SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438)
SetTokenDeployerAddressTest:test_RevertWhen_SenderIsNotOwner() (gas: 12438)
SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768)
SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768)
SetTokenDeployerAddressTest:test_SetTokenDeployerAddress() (gas: 22768)
TransferERC20ByAdminTest:test_AdminCanTransferERC20() (gas: 97863)
TransferERC20ByAdminTest:test_Deployment() (gas: 10714)
TransferERC20ByAdminTest:test_LengthMismatch() (gas: 26191)
TransferERC20ByAdminTest:test_NoRecipients() (gas: 19561)
TransferERC20ByAdminTest:test_TransferAmountZero() (gas: 66102)
TransferERC20ByAdminTest:test_TransferERC20AmountTooBig() (gas: 59124)
TransferERC20ByNonAdminTest:test_Deployment() (gas: 10594)
TransferERC20ByNonAdminTest:test_revertIfCalledByNonAdmin() (gas: 30002)
TransferERC721ByAdminTest:test_AdminCanTransferERC721() (gas: 141821)
TransferERC721ByAdminTest:test_Deployment() (gas: 10670)
TransferERC721ByAdminTest:test_LengthMismatch() (gas: 26201)
TransferERC721ByAdminTest:test_NoRecipients() (gas: 19551)
TransferERC721ByAdminTest:test_RevertOnTransferERC721IfNotDeposited() (gas: 32781)
TransferERC721ByNonAdminTest:test_Deployment() (gas: 10714)
TransferERC721ByNonAdminTest:test_RevertIfCalledByNonAdmin() (gas: 30043)
67 changes: 66 additions & 1 deletion contracts/CommunityVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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 { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import { CommunityOwnable } from "./CommunityOwnable.sol";

/**
Expand All @@ -14,8 +16,9 @@ import { CommunityOwnable } from "./CommunityOwnable.sol";
* Only community owners, as defined in the CommunityOwnable contract, have
* permissions to transfer these tokens out of the vault.
*/
contract CommunityVault is CommunityOwnable {
contract CommunityVault is CommunityOwnable, IERC721Receiver {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.UintSet;

event ERC20Deposited(address indexed depositor, address indexed token, uint256 amount);
event ERC721Deposited(address indexed depositor, address indexed token, uint256 tokenId);
Expand All @@ -25,8 +28,12 @@ contract CommunityVault is CommunityOwnable {
error CommunityVault_TransferAmountZero();
error CommunityVault_ERC20TransferAmountTooBig();
error CommunityVault_DepositAmountZero();
error CommunityVault_IndexOutOfBounds();
error CommunityVault_ERC721TokenAlreadyDeposited();
error CommunityVault_ERC721TokenNotDeposited();

mapping(address => uint256) public erc20TokenBalances;
mapping(address => EnumerableSet.UintSet) private erc721TokenIds;

constructor(address _ownerToken, address _masterToken) CommunityOwnable(_ownerToken, _masterToken) { }

Expand All @@ -50,6 +57,50 @@ contract CommunityVault is CommunityOwnable {
emit ERC20Deposited(msg.sender, token, amount);
}

/**
* @dev Allows anyone to deposit multiple ERC721 tokens into the vault.
* @param token The address of the ERC721 token to deposit.
* @param tokenIds The IDs of the tokens to deposit.
*/
function depositERC721(address token, uint256[] memory tokenIds) public {
for (uint256 i = 0; i < tokenIds.length; i++) {
// Transfer the token from the sender to this contract
IERC721(token).safeTransferFrom(msg.sender, address(this), tokenIds[i]);

// Add the token ID to the EnumerableSet for the given token
bool added = erc721TokenIds[token].add(tokenIds[i]);
if (!added) {
revert CommunityVault_ERC721TokenAlreadyDeposited();
}

// Emit an event for the deposit
emit ERC721Deposited(msg.sender, token, tokenIds[i]);
}
}

/**
* @dev Gets the count of ERC721 tokens deposited for a given token address.
* @param token The address of the ERC721 token.
* @return The count of tokens deposited.
*/
function erc721TokenBalances(address token) public view returns (uint256) {
return erc721TokenIds[token].length();
}

/**
* @dev Retrieves a deposited ERC721 token ID by index.
* @param token The address of the ERC721 token.
* @param index The index of the token ID to retrieve.
* @return The token ID at the given index.
*/
function getERC721DepositedTokenByIndex(address token, uint256 index) public view returns (uint256) {
if (index < erc721TokenIds[token].length()) {
revert CommunityVault_IndexOutOfBounds();
}

return erc721TokenIds[token].at(index);
}

/**
* @dev Transfers ERC20 tokens to a list of addresses.
* @param token The ERC20 token address.
Expand Down Expand Up @@ -109,7 +160,21 @@ contract CommunityVault is CommunityOwnable {
}

for (uint256 i = 0; i < recipients.length; i++) {
bool removed = erc721TokenIds[token].remove(tokenIds[i]);
if (!removed) {
revert CommunityVault_ERC721TokenNotDeposited();
}

IERC721(token).safeTransferFrom(address(this), recipients[i], tokenIds[i]);
}
}

/**
* @dev Handles the receipt of an ERC721 token.
* @return bytes4 Returns `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
* to indicate the contract implements `onERC721Received` as per ERC721.
*/
function onERC721Received(address, address, uint256, bytes calldata) public pure override returns (bytes4) {
return this.onERC721Received.selector;
}
}
89 changes: 75 additions & 14 deletions test/CommunityVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -168,26 +168,42 @@ contract CommunityVaultBaseERC721Test is CommunityVaultTest {
function setUp() public virtual override {
CommunityVaultTest.setUp();

// mint 2 token to user
// mint 4 token to user
address user = accounts[0];
erc721Token.mint(user);
erc721Token.mint(user);
erc721Token.mint(user);
erc721Token.mint(user);
}
}

contract CommunityVaultBaseTransferERC721Test is CommunityVaultBaseERC721Test {
function setUp() public virtual override {
CommunityVaultBaseERC721Test.setUp();

address user = accounts[0];

// user transfer 2 tokens to the vault
uint256[] memory ids = new uint256[](3);
ids[0] = 0;
ids[1] = 1;
ids[2] = 2;

vm.startPrank(user);
erc721Token.transferFrom(user, address(vault), 0);
erc721Token.transferFrom(user, address(vault), 1);
erc721Token.approve(address(vault), ids[0]);
erc721Token.approve(address(vault), ids[1]);
erc721Token.approve(address(vault), ids[2]);
vault.depositERC721(address(erc721Token), ids);
vm.stopPrank();
}
}

contract TransferERC721ByNonAdminTest is CommunityVaultBaseERC721Test {
contract TransferERC721ByNonAdminTest is CommunityVaultBaseTransferERC721Test {
function setUp() public virtual override {
CommunityVaultBaseERC721Test.setUp();
CommunityVaultBaseTransferERC721Test.setUp();
}

function test_RevertIfCalledByNonAdmin() public {
assertEq(erc721Token.balanceOf(address(vault)), 2);
uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;
Expand All @@ -199,14 +215,12 @@ contract TransferERC721ByNonAdminTest is CommunityVaultBaseERC721Test {
}
}

contract TransferERC721ByAdminTest is CommunityVaultBaseERC721Test {
contract TransferERC721ByAdminTest is CommunityVaultBaseTransferERC721Test {
function setUp() public virtual override {
CommunityVaultBaseERC721Test.setUp();
CommunityVaultBaseTransferERC721Test.setUp();
}

function test_LengthMismatch() public {
assertEq(erc721Token.balanceOf(address(vault)), 2);

uint256[] memory ids = new uint256[](1);
ids[0] = 0;

Expand All @@ -216,8 +230,6 @@ contract TransferERC721ByAdminTest is CommunityVaultBaseERC721Test {
}

function test_NoRecipients() public {
assertEq(erc721Token.balanceOf(address(vault)), 2);

uint256[] memory ids = new uint256[](0);
address[] memory tmpAccounts = new address[](0);

Expand All @@ -227,10 +239,16 @@ contract TransferERC721ByAdminTest is CommunityVaultBaseERC721Test {
}

function test_AdminCanTransferERC721() public {
assertEq(erc721Token.balanceOf(address(vault)), 2);
assertEq(erc721Token.balanceOf(address(vault)), 3);
assertEq(vault.erc721TokenBalances(address(erc721Token)), 3);

// accounts[0] has 1 token with id 3
assertEq(erc721Token.balanceOf(accounts[0]), 1);
assertEq(erc721Token.balanceOf(accounts[1]), 0);

assertEq(erc721Token.ownerOf(0), address(vault));
assertEq(erc721Token.ownerOf(1), address(vault));
assertEq(erc721Token.ownerOf(2), address(vault));

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
Expand All @@ -239,6 +257,49 @@ contract TransferERC721ByAdminTest is CommunityVaultBaseERC721Test {
vm.prank(deployer);
vault.transferERC721(address(erc721Token), accounts, ids);

assertEq(erc721Token.balanceOf(address(vault)), 0);
assertEq(erc721Token.balanceOf(address(vault)), 1);
assertEq(vault.erc721TokenBalances(address(erc721Token)), 1);

assertEq(erc721Token.balanceOf(accounts[0]), 2);
assertEq(erc721Token.balanceOf(accounts[1]), 1);
}

function test_RevertOnTransferERC721IfNotDeposited() public {
// id 3 is not deposited
assertEq(erc721Token.ownerOf(3), address(accounts[0]));

uint256[] memory ids = new uint256[](1);
ids[0] = 3;

address[] memory accountsList = new address[](1);
accountsList[0] = accounts[0];

vm.prank(deployer);
vm.expectRevert(CommunityVault.CommunityVault_ERC721TokenNotDeposited.selector);
vault.transferERC721(address(erc721Token), accountsList, ids);
}
}

contract CommunityVaultDepositERC721Test is CommunityVaultBaseERC721Test {
function setUp() public virtual override {
CommunityVaultBaseERC721Test.setUp();
}

function testSuccessfulDepositERC721() public {
uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

uint256 initialVaultBalance = erc721Token.balanceOf(address(vault));
uint256 initialTokenBalanceValue = vault.erc721TokenBalances(address(erc721Token));

vm.startPrank(accounts[0]);
erc721Token.approve(address(vault), ids[0]);
erc721Token.approve(address(vault), ids[1]);
vault.depositERC721(address(erc721Token), ids);
vm.stopPrank();

assertEq(erc721Token.balanceOf(address(vault)), initialVaultBalance + 2);
assertEq(vault.erc721TokenBalances(address(erc721Token)), initialTokenBalanceValue + 2);
}
}

0 comments on commit e354b7a

Please sign in to comment.