Skip to content

Commit

Permalink
Introduced NFT dao
Browse files Browse the repository at this point in the history
  • Loading branch information
mobycrypt committed Aug 26, 2023
1 parent a1634e9 commit 059a708
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 102 deletions.
20 changes: 0 additions & 20 deletions contracts/ERC20DAO.sol

This file was deleted.

8 changes: 4 additions & 4 deletions contracts/Proposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pragma solidity ^0.8.18;

import "./IProposalUserChallenge.sol";
import "./ProposalUserChallenge.sol";
import "./DAO.sol";
import "./IDAOPool.sol";
import "./dao/Dao.sol";
import "./pool/IDaoPool.sol";
import "hardhat/console.sol";

struct PollCard {
Expand All @@ -24,7 +24,7 @@ contract Proposal {
uint256 public immutable tokenCollateral;
bytes[] private payloads;
address public immutable daoAddress;
IDAOPool public immutable daoPool;
IDaoPool public immutable daoPool;
uint256 public immutable contractCreationTime;

uint256 public forVotesCounter;
Expand All @@ -48,7 +48,7 @@ contract Proposal {
challengePeriodSeconds = _challengePeriodSeconds;
payloads = _payloads;
contractCreationTime = block.timestamp;
daoPool = IDAOPool(_daoPoolAddress);
daoPool = IDaoPool(_daoPoolAddress);
daoAddress = msg.sender;

uint256 votesCount = _validateCollateralAndGetVotesCount();
Expand Down
2 changes: 1 addition & 1 deletion contracts/ProposalUserChallenge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
//import "@openzeppelin/cosdsdsntracts/utils/cryptography/ECDSA.sol";
//import "hardhat/console.sol";
//import "./DAO.sol";
//import "./Dao.sol";
//import "./Proposal.sol";
//
//using ECDSA for bytes32;
Expand Down
12 changes: 6 additions & 6 deletions contracts/DAO.sol → contracts/dao/Dao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./Proposal.sol";
import "./ERC20DAOPool.sol";
import "../Proposal.sol";
import "../pool/DaoPool.sol";

abstract contract DAO is ReentrancyGuard {
abstract contract Dao is ReentrancyGuard {

mapping(bytes => Proposal) private proposals;
uint256 public challengePeriodSeconds = 1 minutes;
Expand All @@ -17,7 +17,7 @@ abstract contract DAO is ReentrancyGuard {
event ProposalCreated(bytes proposalId, address proposalAddress);
event DaoPoolCreated(address daoPoolAddress);

function getDaoPool() virtual internal returns (ERC20DAOPool);
function getDaoPool() virtual internal returns (DaoPool);

// _tokenCollateral - should already include decimals
// _challengePeriod - in seconds
Expand Down Expand Up @@ -53,7 +53,7 @@ abstract contract DAO is ReentrancyGuard {
Proposal proposal = proposals[proposalId];
require(address(proposal) != address(0), "Proposal does not exist");
bool isProposalPassed = proposal.isPassed();
require(isProposalPassed == true, "Proposal did not pass");
require(isProposalPassed, "Proposal did not pass");
IERC20 token = IERC20(tokenAddress);
require(token.transfer(to, amount), "ERC20 transfer failed");
}
Expand All @@ -62,7 +62,7 @@ abstract contract DAO is ReentrancyGuard {
Proposal proposal = proposals[proposalId];
require(address(proposal) != address(0), "Proposal does not exist");
bool isProposalPassed = proposal.isPassed();
require(isProposalPassed == true, "Proposal did not pass");
require(isProposalPassed, "Proposal did not pass");
IERC721 token = IERC721(tokenAddress);
token.transferFrom(address(this), to, tokenId);
}
Expand Down
20 changes: 20 additions & 0 deletions contracts/dao/ERC20Dao.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./Dao.sol";
import "../pool/ERC20DaoPool.sol";

contract ERC20Dao is Dao {

ERC20DaoPool public daoPool;

constructor(address _daoTokenAddress, uint256 _tokenCollateral, uint256 _challengePeriodSeconds, uint256 _nativeCollateral)
Dao(_tokenCollateral, _challengePeriodSeconds, _nativeCollateral) {
daoPool = new ERC20DaoPool(_daoTokenAddress);
emit DaoPoolCreated(address(daoPool));
}

function getDaoPool() override view internal returns (DaoPool) {
return daoPool;
}
}
20 changes: 20 additions & 0 deletions contracts/dao/NFTDao.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./Dao.sol";
import "../pool/NFTDaoPool.sol";

contract NFTDao is Dao {

NFTDaoPool public daoPool;

constructor(address _daoTokenAddress, uint256 _tokenCollateral, uint256 _challengePeriodSeconds, uint256 _nativeCollateral)
Dao(_tokenCollateral, _challengePeriodSeconds, _nativeCollateral) {
daoPool = new NFTDaoPool(_daoTokenAddress);
emit DaoPoolCreated(address(daoPool));
}

function getDaoPool() override view internal returns (DaoPool) {
return daoPool;
}
}
30 changes: 30 additions & 0 deletions contracts/pool/DaoPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./IDaoPool.sol";

abstract contract DaoPool is IDaoPool, Ownable {

mapping(address => address[]) public proposalForVoters;
mapping(address => address[]) public proposalAgainstVoters;
mapping(address => uint256) public voterActiveProposals;
mapping(address => bool) public approvedProposals;

// only DAO can call this
function approveProposal(address proposalAddress) public onlyOwner {
approvedProposals[proposalAddress] = true;
}

// is invoked by proposal
// msg.sender == proposal address
function vote(address voterAddress, bool voteSide) override external {
require(approvedProposals[msg.sender], "Proposal not approved");
if (voteSide) {
proposalForVoters[msg.sender].push(voterAddress);
} else {
proposalAgainstVoters[msg.sender].push(voterAddress);
}
voterActiveProposals[voterAddress] += 1;
}
}
29 changes: 5 additions & 24 deletions contracts/ERC20DAOPool.sol → contracts/pool/ERC20DaoPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Proposal.sol";
contract ERC20DAOPool is IDAOPool, Ownable {
import "./DaoPool.sol";
import "../Proposal.sol";

contract ERC20DaoPool is DaoPool {

IERC20 public token;
mapping(address => uint256) public balances;
mapping(address => address[]) public proposalForVoters;
mapping(address => address[]) public proposalAgainstVoters;
mapping(address => uint256) public voterActiveProposals;
mapping(address => bool) public approvedProposals;

event TokensDeposited(address indexed user, address indexed tokenAddress, uint256 amount);
event TokensWithdrawn(address indexed user, address indexed tokenAddress, uint256 amount, address indexed withdrawAddress);
Expand All @@ -19,11 +17,6 @@ contract ERC20DAOPool is IDAOPool, Ownable {
token = IERC20(_tokenAddress);
}

// only DAO can call this
function approveProposal(address proposalAddress) public onlyOwner {
approvedProposals[proposalAddress] = true;
}

function deposit(uint256 amount) public {
balances[msg.sender] += amount;
emit TokensDeposited(msg.sender, address(token), amount);
Expand All @@ -41,18 +34,6 @@ contract ERC20DAOPool is IDAOPool, Ownable {
emit TokensWithdrawn(msg.sender, address(token), amount, withdrawAddress);
}

// is invoked by proposal
// msg.sender == proposal address
function vote(address voterAddress, bool voteSide) override external {
require(approvedProposals[msg.sender], "Proposal not approved");
if (voteSide) {
proposalForVoters[msg.sender].push(voterAddress);
} else {
proposalAgainstVoters[msg.sender].push(voterAddress);
}
voterActiveProposals[voterAddress] += 1;
}

function resolveProposal(address proposalAddress) public {
Proposal proposal = Proposal(proposalAddress);
require(proposal.isEnded(), "Proposal not ended");
Expand All @@ -62,7 +43,7 @@ contract ERC20DAOPool is IDAOPool, Ownable {
address voterAddress = voters[i];
toTransferAmount += balanceOf(voterAddress);
delete balances[voterAddress];
voterActiveProposals[voterAddress] -= 1;
voterActiveProposals[voterAddress] = 0;
}
delete proposalForVoters[proposalAddress];
delete proposalAgainstVoters[proposalAddress];
Expand Down
3 changes: 1 addition & 2 deletions contracts/IDAOPool.sol → contracts/pool/IDaoPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./Proposal.sol";

interface IDAOPool {
interface IDaoPool {

function balanceOf(address account) external view returns (uint256);
function vote(address proposalAddress, bool voteSide) external;
Expand Down
64 changes: 64 additions & 0 deletions contracts/pool/NFTDaoPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./DaoPool.sol";
import "../Proposal.sol";

contract NFTDaoPool is DaoPool {

ERC721 public token;
mapping(address => uint256[]) public balances;

event TokensDeposited(address indexed user, address indexed tokenAddress, uint256 tokenId);
event TokensWithdrawn(address indexed user, address indexed tokenAddress, uint256 tokenId, address indexed withdrawAddress);

constructor(address _tokenAddress) {
token = ERC721(_tokenAddress);
}

function deposit(uint256 tokenId) public {
balances[msg.sender].push(tokenId);
emit TokensDeposited(msg.sender, address(token), tokenId);
token.safeTransferFrom(msg.sender, address(this), tokenId);
}

function withdraw(uint256 tokenId, address withdrawAddress) public {
require(voterActiveProposals[msg.sender] == 0, "User has active proposals");
uint256 index;
uint256[] storage userTokenIds = balances[msg.sender];
for (uint256 i = 0; i < balances[msg.sender].length ; i++) {
if (userTokenIds[i] == tokenId) {
index = userTokenIds[i];
token.safeTransferFrom(address(this), withdrawAddress, tokenId);
delete userTokenIds[i];
break;
}
}
emit TokensWithdrawn(msg.sender, address(token), tokenId, withdrawAddress);
}

function resolveProposal(address proposalAddress) public {
Proposal proposal = Proposal(proposalAddress);
require(proposal.isEnded(), "Proposal not ended");
address[] memory voters = proposal.isPassed() ? proposalForVoters[proposalAddress] : proposalAgainstVoters[proposalAddress];
uint256 toTransferAmount = 0;
for (uint256 i = 0; i < voters.length; i++) {
address voterAddress = voters[i];
uint256[] memory userTokenIds = balances[voterAddress];
for (uint256 k = 0; k < userTokenIds.length; k++) {
uint256 tokenId = userTokenIds[k];
token.safeTransferFrom(address(this), owner(), tokenId);
}
delete balances[voterAddress];
voterActiveProposals[voterAddress] = 0;
}
delete proposalForVoters[proposalAddress];
delete proposalAgainstVoters[proposalAddress];
delete approvedProposals[proposalAddress];
}

function balanceOf(address account) override public view returns (uint256) {
return balances[account].length;
}
}
Loading

0 comments on commit 059a708

Please sign in to comment.