Skip to content

Commit

Permalink
Add vote and makePayment functions and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
antico5 committed Oct 27, 2021
1 parent ca4d314 commit d2eb901
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 103 deletions.
5 changes: 3 additions & 2 deletions crowdfunding/.pretierrc → crowdfunding/.prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
"explicitTypes": "always",
"semi": true
}
}
]
Expand Down
217 changes: 118 additions & 99 deletions crowdfunding/contracts/CrowdFunding.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,105 +5,124 @@ pragma solidity ^0.8.7;
import "@openzeppelin/contracts/access/Ownable.sol";

contract CrowdFunding is Ownable {
mapping(address => uint256) public contributors;
uint256 public contributorsCount;
uint256 public minContribution;
uint256 public deadline; //timestamp
uint256 public goal;
uint256 public raisedAmount;

mapping(uint256 => Request) public requests;
uint256 public requestsCount;

struct Request {
string description;
address recipient;
uint256 value;
bool executed;
uint256 votersCount;
mapping(address => bool) voters;
mapping(address => uint256) public contributors;
uint256 public contributorsCount;
uint256 public minContribution;
uint256 public deadline; //timestamp
uint256 public goal;
uint256 public raisedAmount;

mapping(uint256 => Request) public requests;
uint256 public requestsCount;

struct Request {
string description;
address recipient;
uint256 value;
bool executed;
uint256 votersCount;
mapping(address => bool) voters;
}

event RequestCreated(uint indexed requestId, string description, address indexed recipient, uint value);

modifier onFailed() {
require(block.timestamp > deadline && raisedAmount < goal);
_;
}

modifier onSuccess() {
require(raisedAmount >= goal, "the campaign didnt reach its goal yet");
_;
}

modifier onlyContributors() {
require(_contributed() > 0, "only contributors can vote");
_;
}

modifier notExecuted(uint256 requestId) {
require(requests[requestId].executed == false, 'the request has already been executed');
_;
}

constructor(uint256 _goal, uint256 secondsToDeadline) {
goal = _goal;
deadline = block.timestamp + secondsToDeadline;
minContribution = 100 wei;
}

function contribute() public payable {
require(block.timestamp <= deadline, "Crowdfunding already finished");
require(msg.value >= minContribution, "minimum contribution not met");

if (_contributed() == 0) {
// first time contributor
contributorsCount++;
}

modifier onFailed() {
require(block.timestamp > deadline && raisedAmount < goal);
_;
}

modifier onSuccess() {
require(raisedAmount >= goal, "the campaign didnt reach its goal yet");
_;
}

modifier onlyContributors() {
require(_contributed() > 0);
_;
}

constructor(uint256 _goal, uint256 secondsToDeadline) {
goal = _goal;
deadline = block.timestamp + secondsToDeadline;
minContribution = 100 wei;
}

function contribute() public payable {
require(block.timestamp <= deadline, "Crowdfunding already finished");
require(msg.value >= minContribution, "minimum contribution not met");

if (_contributed() == 0) {
// first time contributor
contributorsCount++;
}

_addContribution(msg.value);
raisedAmount += msg.value;
}

function getRefund() public onlyContributors onFailed {
uint256 refundAmount = _contributed();
_setContribution(0);
raisedAmount -= refundAmount;
contributorsCount--;
payable(msg.sender).transfer(refundAmount);
}

receive() external payable {
contribute();
}

function _contributed() internal view returns (uint256) {
return contributors[msg.sender];
}

function _addContribution(uint256 value) internal {
contributors[msg.sender] += value;
}

function _setContribution(uint256 value) internal {
contributors[msg.sender] = value;
}

function createRequest(
string memory description,
address recipient,
uint256 value
) public onlyOwner {
Request storage request = requests[requestsCount];

request.description = description;
request.recipient = recipient;
request.value = value;

requestsCount++;
}

function vote(uint256 requestId) public onlyContributors onSuccess {
require(
requests[requestId].executed == false,
"the request has already been executed"
);
require(
requests[requestId].voters[msg.sender] == false,
"you already voted"
);
}
_addContribution(msg.value);
raisedAmount += msg.value;
}

function getRefund() public onlyContributors onFailed {
uint256 refundAmount = _contributed();
_setContribution(0);
raisedAmount -= refundAmount;
contributorsCount--;
payable(msg.sender).transfer(refundAmount);
}

receive() external payable {
contribute();
}

function _contributed() internal view returns (uint256) {
return contributors[msg.sender];
}

function _addContribution(uint256 value) internal {
contributors[msg.sender] += value;
}

function _setContribution(uint256 value) internal {
contributors[msg.sender] = value;
}

function createRequest(
string memory description,
address recipient,
uint256 value
) public onlyOwner returns(uint) {
uint requestId = requestsCount;
Request storage request = requests[requestId];

request.description = description;
request.recipient = recipient;
request.value = value;

requestsCount++;

emit RequestCreated(requestId, description, recipient, value);
return requestId;
}

function vote(uint256 requestId) public onlyContributors onSuccess notExecuted(requestId) {
require(requests[requestId].voters[msg.sender] == false, "you already voted");

requests[requestId].voters[msg.sender] = true;
requests[requestId].votersCount++;
}

function didVote(uint256 requestId, address contributor) public view returns (bool) {
return requests[requestId].voters[contributor];
}

function makePayment(uint256 requestId) public onlyOwner onSuccess notExecuted(requestId) {
Request storage request = requests[requestId];
require(request.votersCount >= contributorsCount/2, 'need more than half of contributors votes');
require(address(this).balance >= request.value, 'not enough funds');

payable(request.recipient).transfer(request.value);
}
}
88 changes: 86 additions & 2 deletions crowdfunding/test/crowdfunding_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import { TransactionRequest, TransactionResponse } from '@ethersproject/abstract
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { BigNumber, Contract } from 'ethers';
import hre, {ethers} from 'hardhat'
import { MockContract, smock } from '@defi-wonderland/smock'

describe("CrowdFunding", function () {
let contract: Contract;
let contract: MockContract;
let signer: SignerWithAddress;
let otherSigner: SignerWithAddress;
let yetAnotherSigner: SignerWithAddress;

beforeEach(async () => {
// Deploy contact
const factory = await ethers.getContractFactory("CrowdFunding");
const factory = await smock.mock("CrowdFunding");
contract = await factory.deploy(ethers.utils.parseEther('10'), 600);
await contract.deployed();

// Store signer
signer = (await ethers.getSigners())[0]
otherSigner = (await ethers.getSigners())[1]
yetAnotherSigner = (await ethers.getSigners())[2]
});

describe("contribute", function () {
Expand Down Expand Up @@ -108,4 +111,85 @@ describe("CrowdFunding", function () {
expect(contract.connect(otherSigner).createRequest('Test request', signer.address, 1000)).to.revertedWith('owner')
});
});

describe("vote", function () {
beforeEach(async () => {
await contract.createRequest('Test request', signer.address, 1000);
});

describe("when the campaign was funded", function () {
beforeEach(async () => {
await contract.contribute({value: ethers.utils.parseEther('20')})
});

it("increases vote count and marks the contributor as voted", async () => {
let request = await contract.requests(0)
expect(request.votersCount).to.eq(0)
expect(await contract.didVote(0, signer.address)).to.eq(false)

await contract.vote(0);

request = await contract.requests(0)
expect(request.votersCount).to.eq(1)
expect(await contract.didVote(0, signer.address)).to.eq(true)
});

it("only allows contributors to vote", async () => {
expect(contract.connect(otherSigner).vote(0)).to.revertedWith('only contributors')
});

it("doesnt allow you to vote twice", async () => {
expect(contract.vote(0)).to.not.be.reverted;
expect(contract.vote(0)).to.be.revertedWith('already voted')
});

it("doenst allow you to vote if request was already executed", async () => {
await contract.setVariable('requests', {
0: {
executed: true
}
})

expect(contract.vote(0)).to.revertedWith('already been executed')
});
});

describe("when the campaign was not funded", function () {
it("doesnt allow you to vote", async () => {
await contract.contribute({value: 1000}); // user is contributor
expect(contract.vote(0)).to.be.revertedWith('didnt reach its goal')
});
});

});

describe("makePayment", function () {
it("transfers eth to the request recipient", async () => {
// contribute until goal is reached
await contract.connect(signer).contribute({value: ethers.utils.parseEther('5')})
await contract.connect(otherSigner).contribute({value: ethers.utils.parseEther('5')})
await contract.connect(yetAnotherSigner).contribute({value: ethers.utils.parseEther('5')})

const recipient = (await ethers.getSigners())[3]
const recipientOldBalance = await ethers.provider.getBalance(recipient.address)

const requestValue = ethers.utils.parseEther('1.5');
const tx = await contract.createRequest('Test request', recipient.address, requestValue) as TransactionResponse
const receipt = await tx.wait()

const requestId = BigNumber.from(receipt.logs[0].topics[1]) // second value of first emmited event

await contract.connect(signer).vote(requestId)
await contract.connect(otherSigner).vote(requestId)

await contract.makePayment(requestId)

const recipientNewBalance = await ethers.provider.getBalance(recipient.address)
expect(recipientNewBalance.sub(recipientOldBalance)).to.eq(requestValue)
});

it("is only callable by owner", async () => {

});
});
});

0 comments on commit d2eb901

Please sign in to comment.