communities-contracts/contracts/CommunityVault.sol

230 lines
8.9 KiB
Solidity
Raw Normal View History

// 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 { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.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, 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);
error CommunityVault_LengthMismatch();
error CommunityVault_NoRecipients();
error CommunityVault_TransferAmountZero();
error CommunityVault_ERC20TransferAmountTooBig();
error CommunityVault_DepositAmountZero();
error CommunityVault_IndexOutOfBounds();
error CommunityVault_ERC721TokenAlreadyDeposited();
error CommunityVault_ERC721TokenNotDeposited();
error CommunityVault_AmountExceedsUntrackedBalanceERC20();
error CommunityVault_CannotWithdrawTrackedERC721();
mapping(address => uint256) public erc20TokenBalances;
mapping(address => EnumerableSet.UintSet) private erc721TokenIds;
constructor(address _ownerToken, address _masterToken) CommunityOwnable(_ownerToken, _masterToken) { }
/**
* @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);
}
/**
* @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);
}
/**
* @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();
}
if (amounts[i] > erc20TokenBalances[token]) {
revert CommunityVault_ERC20TransferAmountTooBig();
}
erc20TokenBalances[token] -= amounts[i];
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++) {
bool removed = erc721TokenIds[token].remove(tokenIds[i]);
if (!removed) {
revert CommunityVault_ERC721TokenNotDeposited();
}
IERC721(token).safeTransferFrom(address(this), recipients[i], tokenIds[i]);
}
}
/// @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]);
}
}
/**
* @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;
}
}