2023-12-12 22:10:27 +00:00
|
|
|
// 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";
|
2024-03-01 11:02:17 +00:00
|
|
|
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
|
|
|
|
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
2023-12-12 22:10:27 +00:00
|
|
|
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.
|
|
|
|
*/
|
2024-03-01 11:02:17 +00:00
|
|
|
contract CommunityVault is CommunityOwnable, IERC721Receiver {
|
2023-12-12 22:10:27 +00:00
|
|
|
using SafeERC20 for IERC20;
|
2024-03-01 11:02:17 +00:00
|
|
|
using EnumerableSet for EnumerableSet.UintSet;
|
2023-12-12 22:10:27 +00:00
|
|
|
|
2024-02-26 10:00:31 +00:00
|
|
|
event ERC20Deposited(address indexed depositor, address indexed token, uint256 amount);
|
|
|
|
event ERC721Deposited(address indexed depositor, address indexed token, uint256 tokenId);
|
|
|
|
|
2023-12-12 22:10:27 +00:00
|
|
|
error CommunityVault_LengthMismatch();
|
|
|
|
error CommunityVault_NoRecipients();
|
|
|
|
error CommunityVault_TransferAmountZero();
|
2024-02-26 10:00:31 +00:00
|
|
|
error CommunityVault_ERC20TransferAmountTooBig();
|
|
|
|
error CommunityVault_DepositAmountZero();
|
2024-03-01 11:02:17 +00:00
|
|
|
error CommunityVault_IndexOutOfBounds();
|
|
|
|
error CommunityVault_ERC721TokenAlreadyDeposited();
|
|
|
|
error CommunityVault_ERC721TokenNotDeposited();
|
2024-03-02 14:53:52 +00:00
|
|
|
error CommunityVault_AmountExceedsUntrackedBalanceERC20();
|
|
|
|
error CommunityVault_CannotWithdrawTrackedERC721();
|
2024-02-26 10:00:31 +00:00
|
|
|
|
|
|
|
mapping(address => uint256) public erc20TokenBalances;
|
2024-03-01 11:02:17 +00:00
|
|
|
mapping(address => EnumerableSet.UintSet) private erc721TokenIds;
|
2023-12-12 22:10:27 +00:00
|
|
|
|
|
|
|
constructor(address _ownerToken, address _masterToken) CommunityOwnable(_ownerToken, _masterToken) { }
|
|
|
|
|
2024-02-26 10:00:31 +00:00
|
|
|
/**
|
|
|
|
* @dev Allows anyone to deposit ERC20 tokens into the vault.
|
|
|
|
* @param token The address of the ERC20 token to deposit.
|
|
|
|
* @param amount The amount of tokens to deposit.
|
|
|
|
*/
|
|
|
|
function depositERC20(address token, uint256 amount) external {
|
|
|
|
if (amount == 0) {
|
|
|
|
revert CommunityVault_DepositAmountZero();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transfer tokens from the sender to this contract
|
|
|
|
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
|
|
|
|
|
|
|
|
// Update the total balance of the token in the vault
|
|
|
|
erc20TokenBalances[token] += amount;
|
|
|
|
|
|
|
|
// Emit an event for the deposit (optional, but recommended for tracking)
|
|
|
|
emit ERC20Deposited(msg.sender, token, amount);
|
|
|
|
}
|
|
|
|
|
2024-03-01 11:02:17 +00:00
|
|
|
/**
|
|
|
|
* @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++) {
|
|
|
|
// Add the token ID to the EnumerableSet for the given token
|
|
|
|
bool added = erc721TokenIds[token].add(tokenIds[i]);
|
|
|
|
if (!added) {
|
|
|
|
revert CommunityVault_ERC721TokenAlreadyDeposited();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transfer the token from the sender to this contract
|
|
|
|
IERC721(token).safeTransferFrom(msg.sender, address(this), tokenIds[i]);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2023-12-12 22:10:27 +00:00
|
|
|
/**
|
|
|
|
* @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();
|
|
|
|
}
|
|
|
|
|
2024-02-26 10:00:31 +00:00
|
|
|
if (amounts[i] > erc20TokenBalances[token]) {
|
|
|
|
revert CommunityVault_ERC20TransferAmountTooBig();
|
|
|
|
}
|
|
|
|
|
|
|
|
erc20TokenBalances[token] -= amounts[i];
|
2023-12-12 22:10:27 +00:00
|
|
|
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++) {
|
2024-03-01 11:02:17 +00:00
|
|
|
bool removed = erc721TokenIds[token].remove(tokenIds[i]);
|
|
|
|
if (!removed) {
|
|
|
|
revert CommunityVault_ERC721TokenNotDeposited();
|
|
|
|
}
|
|
|
|
|
2023-12-12 22:10:27 +00:00
|
|
|
IERC721(token).safeTransferFrom(address(this), recipients[i], tokenIds[i]);
|
|
|
|
}
|
|
|
|
}
|
2024-03-01 11:02:17 +00:00
|
|
|
|
2024-03-02 14:53:52 +00:00
|
|
|
/// @notice Withdraws a specified amount of an untracked ERC20 token from the community vault.
|
|
|
|
/// @dev This function allows the community owner or token master to withdraw untracked ERC20 tokens. It checks if
|
|
|
|
/// the requested amount does not exceed the untracked balance. If it does, the transaction is reverted.
|
|
|
|
/// @param tokenAddress The address of the ERC20 token to withdraw.
|
|
|
|
/// @param amount The amount of the ERC20 token to withdraw.
|
|
|
|
/// @param to The address to which the ERC20 tokens will be transferred.
|
|
|
|
function withdrawUntrackedERC20(
|
|
|
|
address tokenAddress,
|
|
|
|
uint256 amount,
|
|
|
|
address to
|
|
|
|
)
|
|
|
|
public
|
|
|
|
onlyCommunityOwnerOrTokenMaster
|
|
|
|
{
|
|
|
|
uint256 contractBalance = IERC20(tokenAddress).balanceOf(address(this));
|
|
|
|
uint256 untrackedBalance = contractBalance - erc20TokenBalances[tokenAddress];
|
|
|
|
|
|
|
|
if (amount > untrackedBalance) {
|
|
|
|
revert CommunityVault_AmountExceedsUntrackedBalanceERC20();
|
|
|
|
}
|
|
|
|
|
|
|
|
IERC20(tokenAddress).safeTransfer(to, amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @notice Withdraws specified ERC721 tokens that are not tracked by the community vault.
|
|
|
|
/// @dev This function allows the community owner or token master to withdraw untracked ERC721 tokens by token IDs.
|
|
|
|
/// It checks each token ID against tracked tokens and if any are found, the transaction is reverted.
|
|
|
|
/// @param tokenAddress The address of the ERC721 token to withdraw.
|
|
|
|
/// @param tokenIds An array of token IDs of the ERC721 tokens to withdraw.
|
|
|
|
/// @param to The address to which the ERC721 tokens will be transferred.
|
|
|
|
function withdrawUntrackedERC721(
|
|
|
|
address tokenAddress,
|
|
|
|
uint256[] memory tokenIds,
|
|
|
|
address to
|
|
|
|
)
|
|
|
|
public
|
|
|
|
onlyCommunityOwnerOrTokenMaster
|
|
|
|
{
|
|
|
|
for (uint256 i = 0; i < tokenIds.length; i++) {
|
|
|
|
if (erc721TokenIds[tokenAddress].contains(tokenIds[i])) {
|
|
|
|
revert CommunityVault_CannotWithdrawTrackedERC721();
|
|
|
|
}
|
|
|
|
|
|
|
|
IERC721(tokenAddress).safeTransferFrom(address(this), to, tokenIds[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-01 11:02:17 +00:00
|
|
|
/**
|
|
|
|
* @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;
|
|
|
|
}
|
2023-12-12 22:10:27 +00:00
|
|
|
}
|