Skip to content

Commit

Permalink
NFT DAO tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mobycrypt committed Aug 28, 2023
1 parent 059a708 commit 514e15b
Show file tree
Hide file tree
Showing 35 changed files with 14,941 additions and 8,155 deletions.
4 changes: 2 additions & 2 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"require": "ts-node/register",
"timeout": "10000",
"timeout": 10000,
"watch-files": ["test/**/*.ts"],
"mochaOptions": {
"exit": true
}
}
}
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"printWidth": 155
}
3 changes: 3 additions & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
configureYulOptimizer: true
};
6 changes: 6 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
7 changes: 3 additions & 4 deletions contracts/DAOVerifier.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol';

contract DAOVerifier {

bytes32 public merkleRoot;

constructor(bytes32 _merkleRoot) {
merkleRoot = _merkleRoot;
}

function verifyIsIncluded(bytes32[] calldata proof, uint64 maxAllowanceToMint) view public returns (bool) {
function verifyIsIncluded(bytes32[] calldata proof, uint64 maxAllowanceToMint) public view returns (bool) {
bytes32 leaf = keccak256(abi.encode(msg.sender, maxAllowanceToMint));
bool verified = MerkleProof.verify(proof, merkleRoot, leaf);
return verified;
}
}
}
5 changes: 2 additions & 3 deletions contracts/ERC20Development.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

contract ERC20Development is ERC20 {

constructor(string memory _name, string memory _symbol, uint256 _totalSupply) ERC20(_name, _symbol) {
_mint(msg.sender, _totalSupply);
}
}
}
9 changes: 4 additions & 5 deletions contracts/ERC721Development.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@openzeppelin/contracts/utils/Counters.sol';

contract ERC721Development is ERC721 {

using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
string private baseUri;

constructor() ERC721("DevelopmentNFT", "DNFT") {}
constructor() ERC721('DevelopmentNFT', 'DNFT') {}

function createNFT(address recipient, string memory baseUri) external returns (uint256) {
_tokenIds.increment();
Expand All @@ -28,4 +27,4 @@ contract ERC721Development is ERC721 {

return 'ipfs://bafybeigs2gea5bauuru4cnec4y5qbitx3a5f73fqx3oo3hwdjtmnis2iny/7610.json';
}
}
}
58 changes: 30 additions & 28 deletions contracts/Proposal.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

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

struct PollCard {
uint256 nativeForVotes;
Expand All @@ -15,11 +15,10 @@ struct PollCard {
}

contract Proposal {

// 32 bytes hex value
bytes32 public immutable proposalMerkleRootHex;
address payable public immutable sequencerAddress;
uint256 public immutable challengePeriodSeconds;
uint256 public immutable challengePeriodSeconds;
uint256 public immutable nativeCollateral;
uint256 public immutable tokenCollateral;
bytes[] private payloads;
Expand All @@ -34,13 +33,14 @@ contract Proposal {
bool public executed = false;

constructor(
bytes32 _proposalMerkleRootHex,
address payable _sequencerAddress,
uint256 _nativeCollateral,
uint256 _tokenCollateral,
uint256 _challengePeriodSeconds,
bytes[] memory _payloads,
address _daoPoolAddress) payable {
bytes32 _proposalMerkleRootHex,
address payable _sequencerAddress,
uint256 _nativeCollateral,
uint256 _tokenCollateral,
uint256 _challengePeriodSeconds,
bytes[] memory _payloads,
address _daoPoolAddress
) payable {
proposalMerkleRootHex = _proposalMerkleRootHex;
sequencerAddress = _sequencerAddress;
nativeCollateral = _nativeCollateral;
Expand All @@ -57,7 +57,7 @@ contract Proposal {
forVotesCounter = votesCount;
}

function vote(bool voteSide) payable public isInChallengePeriodMod {
function vote(bool voteSide) public payable isInChallengePeriodMod {
uint256 votesCount = _validateCollateralAndGetVotesCount();
PollCard storage pollCard = votes[msg.sender];
if (voteSide) {
Expand All @@ -70,15 +70,15 @@ contract Proposal {
}

function _validateCollateralAndGetVotesCount() private returns (uint256) {
require(msg.value >= nativeCollateral, "Collateral too small");
require(msg.value % nativeCollateral == 0, "Collateral incorrect");
require(msg.value >= nativeCollateral, 'Collateral too small');
require(msg.value % nativeCollateral == 0, 'Collateral incorrect');
return msg.value / nativeCollateral;
}

function voteWithToken(bool voteSide) public isInChallengePeriodMod {
uint256 userTokenBalance = daoPool.balanceOf(msg.sender);
uint256 votesCount = userTokenBalance / tokenCollateral;
require(votesCount > 0, "Token collateral not enough");
require(votesCount > 0, 'Token collateral not enough');
PollCard storage pollCard = votes[msg.sender];
bool firstVote = pollCard.tokenForVotes == 0 && pollCard.tokenAgainstVotes == 0;
if (voteSide) {
Expand All @@ -95,17 +95,19 @@ contract Proposal {
}
}

function claimReward() payable public isAfterChallengePeriodMod {
function claimReward() public payable isAfterChallengePeriodMod {
PollCard memory pollCard = votes[msg.sender];
bool passed = isPassed();
uint256 wonVoterVotesCount = passed ? (pollCard.nativeForVotes + pollCard.tokenForVotes) : (pollCard.nativeAgainstVotes + pollCard.tokenAgainstVotes);
require(wonVoterVotesCount > 0, "Reward not apply");
uint256 wonVoterVotesCount = passed
? (pollCard.nativeForVotes + pollCard.tokenForVotes)
: (pollCard.nativeAgainstVotes + pollCard.tokenAgainstVotes);
require(wonVoterVotesCount > 0, 'Reward not apply');
uint256 allWonVotesCount = passed ? forVotesCounter : againstVotesCounter;
uint256 allOppositeVotesCount = passed ? againstVotesCounter : forVotesCounter;
uint256 balanceToDistribute = allOppositeVotesCount * nativeCollateral;
uint256 dust = balanceToDistribute % allWonVotesCount;
uint256 voterReturnCollateralCount = passed ? pollCard.nativeForVotes : pollCard.nativeAgainstVotes;
uint256 reward = (voterReturnCollateralCount * nativeCollateral) + (balanceToDistribute * wonVoterVotesCount / allWonVotesCount);
uint256 reward = (voterReturnCollateralCount * nativeCollateral) + ((balanceToDistribute * wonVoterVotesCount) / allWonVotesCount);
// if it's last tx, send the dust left to last claimer
if (reward + dust == address(this).balance) {
reward = address(this).balance;
Expand All @@ -115,13 +117,13 @@ contract Proposal {
}

function executeProposal() public payable isAfterChallengePeriodMod {
require(!executed, "Proposal already executed");
require(isPassed(), "Proposal did not pass");
require(!executed, 'Proposal already executed');
require(isPassed(), 'Proposal did not pass');
executed = true;
for (uint256 i = 0; i < payloads.length; ++i) {
bytes memory payload = payloads[i];
(bool success, ) = daoAddress.call(payload);
require(success, "Proposal: underlying transaction reverted");
require(success, 'Proposal: underlying transaction reverted');
}
}

Expand All @@ -145,12 +147,12 @@ contract Proposal {
}

modifier isInChallengePeriodMod() {
require(block.timestamp <= (contractCreationTime + challengePeriodSeconds), "Is not in challenge period");
require(block.timestamp <= (contractCreationTime + challengePeriodSeconds), 'Is not in challenge period');
_;
}

modifier isAfterChallengePeriodMod() {
require(block.timestamp > (contractCreationTime + challengePeriodSeconds), "Is not after challenge period");
require(block.timestamp > (contractCreationTime + challengePeriodSeconds), 'Is not after challenge period');
_;
}
}
}
47 changes: 27 additions & 20 deletions contracts/dao/Dao.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// SPDX-License-Identifier: MIT
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 "../pool/DaoPool.sol";

abstract contract Dao is ReentrancyGuard {
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
import '../Proposal.sol';
import '../pool/DaoPool.sol';

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

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

// _tokenCollateral - should already include decimals
// _challengePeriod - in seconds
Expand All @@ -36,13 +35,18 @@ abstract contract Dao is ReentrancyGuard {
nativeCollateral = _challengeCollateral;
}

function createProposal(
bytes memory _proposalId,
bytes32 _proposalMerkleRoot,
bytes[] calldata _payloads) payable public nonReentrant {
function createProposal(bytes memory _proposalId, bytes32 _proposalMerkleRoot, bytes[] calldata _payloads) public payable nonReentrant {
Proposal existingPool = proposals[_proposalId];
require(address(existingPool) == address(0), "Proposal already exists");
Proposal proposal = new Proposal{value: msg.value}(_proposalMerkleRoot, payable(msg.sender), nativeCollateral, tokenCollateral, challengePeriodSeconds, _payloads, address(getDaoPool()));
require(address(existingPool) == address(0), 'Proposal already exists');
Proposal proposal = new Proposal{value: msg.value}(
_proposalMerkleRoot,
payable(msg.sender),
nativeCollateral,
tokenCollateral,
challengePeriodSeconds,
_payloads,
address(getDaoPool())
);
proposals[_proposalId] = proposal;
getDaoPool().approveProposal(address(proposal));
emit ProposalCreated(_proposalId, address(proposal));
Expand All @@ -51,20 +55,23 @@ abstract contract Dao is ReentrancyGuard {
// it could be interface based for more assets support
function sendErc20(bytes memory proposalId, address tokenAddress, address to, uint256 amount) external {
Proposal proposal = proposals[proposalId];
require(address(proposal) != address(0), "Proposal does not exist");
require(address(proposal) != address(0), 'Proposal does not exist');
bool isProposalPassed = proposal.isPassed();
require(isProposalPassed, "Proposal did not pass");
require(isProposalPassed, 'Proposal did not pass');
IERC20 token = IERC20(tokenAddress);
require(token.transfer(to, amount), "ERC20 transfer failed");
require(token.transfer(to, amount), 'ERC20 transfer failed');
}

function sendNft(bytes memory proposalId, address tokenAddress, address to, uint256 tokenId) external {
Proposal proposal = proposals[proposalId];
require(address(proposal) != address(0), "Proposal does not exist");
require(address(proposal) != address(0), 'Proposal does not exist');
bool isProposalPassed = proposal.isPassed();
require(isProposalPassed, "Proposal did not pass");
require(isProposalPassed, 'Proposal did not pass');
IERC721 token = IERC721(tokenAddress);
token.transferFrom(address(this), to, tokenId);
}

}
function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) external override returns (bytes4) {
return this.onERC721Received.selector;
}
}
17 changes: 10 additions & 7 deletions contracts/dao/ERC20Dao.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./Dao.sol";
import "../pool/ERC20DaoPool.sol";
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) {
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) {
function getDaoPool() internal view override returns (DaoPool) {
return daoPool;
}
}
}
17 changes: 10 additions & 7 deletions contracts/dao/NFTDao.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "./Dao.sol";
import "../pool/NFTDaoPool.sol";
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) {
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) {
function getDaoPool() internal view override returns (DaoPool) {
return daoPool;
}
}
}
Loading

0 comments on commit 514e15b

Please sign in to comment.