Skip to content

Instantly share code, notes, and snippets.

@arvindkalra
Last active February 14, 2020 13:27
Show Gist options
  • Save arvindkalra/264c87af561b58aed361336fe4fa7e27 to your computer and use it in GitHub Desktop.
Save arvindkalra/264c87af561b58aed361336fe4fa7e27 to your computer and use it in GitHub Desktop.
GoFundMe
[{"constant":true,"inputs":[],"name":"getTotalApprovalRequests","outputs":[{"name":"total","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getNumberOfSpendings","outputs":[{"name":"numSpendings","type":"uint256"},{"name":"numSpendingsOpen","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint256"},{"name":"to","type":"address"},{"name":"amount","type":"uint256"},{"name":"receipt","type":"string"},{"name":"isOpen","type":"bool"}],"name":"spendDonations","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"index","type":"uint256"}],"name":"getCampaign","outputs":[{"name":"campaignName","type":"string"},{"name":"owner","type":"address"},{"name":"supportingDocuments","type":"string"},{"name":"totalDonations","type":"uint256[4]"},{"name":"times","type":"uint256[2]"},{"name":"campaignType","type":"string"},{"name":"requiredDonation","type":"uint256"},{"name":"donors","type":"address[]"},{"name":"donorsOpen","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint256"},{"name":"amount","type":"uint256"},{"name":"isOpen","type":"bool"}],"name":"donateTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint256"}],"name":"getPendingShareBack","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_donationTime","type":"uint256"},{"name":"_spendTime","type":"uint256"},{"name":"_requiredDonation","type":"uint256"},{"name":"_supportingDocuments","type":"string"},{"name":"_campaignType","type":"string"},{"name":"_campaignName","type":"string"}],"name":"startCampaign","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getMyCampaigns","outputs":[{"name":"indices","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"getApprovalReqeustDetail","outputs":[{"name":"amount","type":"uint256"},{"name":"approved","type":"bool"},{"name":"spendReceipt","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"campaignIndex","type":"uint256"},{"name":"spendingIndex","type":"uint256"},{"name":"isOpen","type":"bool"}],"name":"getSpendingDetail","outputs":[{"name":"amount","type":"uint256"},{"name":"to","type":"address"},{"name":"receipt","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"apporvalRequestId","type":"uint256"}],"name":"approveExpense","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"index","type":"uint256"},{"name":"donor","type":"address"}],"name":"getDonation","outputs":[{"name":"amount","type":"uint256"},{"name":"amountOpen","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint256"}],"name":"endCampaign","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"erc20Address","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"creator","type":"address"},{"indexed":false,"name":"campaignName","type":"string"},{"indexed":false,"name":"index","type":"uint256"},{"indexed":false,"name":"requiredDonation","type":"uint256"}],"name":"CampaignCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"index","type":"uint256"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"donor","type":"address"}],"name":"TokensDonated","type":"event"}]
/**
*Submitted for verification at Etherscan.io on 2020-02-14
*/
pragma solidity >=0.4.22 <0.6.0;
interface ERC20 {
function balanceOf(address _owner) view external returns (uint balance);
function transferFrom(address _from, address _to, uint _value) external returns (bool success);
function approve(address _spender, uint _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint remaining);
function decimals() external view returns(uint digits);
function transfer(address recipient, uint256 amount) external returns (bool);
}
contract GoFundMe {
address contractOwner;
uint256 totalCampaigns;
ERC20 erc20;
uint256 decimal;
struct SpendData {
uint256 amount;
string spendReceipt;
address to;
bool approved;
}
struct SpendingApprovals {
uint256 campaignId;
uint256 expenditureId;
bool isOpen;
}
mapping(address => SpendingApprovals[]) recipientsToApprovals;
struct Campaign {
string campaignName;
address owner;
string supportingDocuments;
uint256 donationEndTime;
uint256 spendingEndTime;
string campaignType;
uint256 requiredDonation;
uint256 totalDonation;
uint256 totalDonationOpen;
mapping(address => uint256) donations;
mapping(address => uint256) donationsOpen;
mapping(address => uint256) percentageDonation;
mapping(address => uint256) percentageDonationsOpen;
address[] donors;
address[] donorsOpen;
bool campaignEnded;
bool moneyReturned;
uint256 totalSpent;
mapping(uint256 => SpendData) expenditures;
uint256 numberOfSpendings;
uint256 totalSpentOpen;
mapping(uint256 => SpendData) expendituresOpen;
uint256 numberOfSpendingsOpen;
}
Campaign[] campaigns;
mapping(address => uint256[]) addressToCampaignIndices;
event CampaignCreated(address creator, string campaignName, uint256 index, uint256 requiredDonation);
event TokensDonated(uint256 index, uint256 amount, address donor);
constructor(address erc20Address) public {
contractOwner = msg.sender;
totalCampaigns = 0;
erc20 = ERC20(erc20Address);
decimal = 2;
}
function startCampaign(uint256 _donationTime, uint256 _spendTime, uint256 _requiredDonation, string memory _supportingDocuments, string memory _campaignType, string memory _campaignName) public{
uint256 currentIndex = totalCampaigns++;
Campaign memory campaign;
campaign.owner = msg.sender;
campaign.campaignName = _campaignName;
campaign.campaignType = _campaignType;
campaign.donationEndTime = now + _donationTime;
campaign.spendingEndTime = now + _spendTime;
campaign.requiredDonation = _requiredDonation;
campaign.supportingDocuments = _supportingDocuments;
campaign.campaignEnded = false;
campaign.moneyReturned = false;
campaign.numberOfSpendings = 0;
campaign.totalSpent = 0;
campaign.numberOfSpendingsOpen = 0;
campaign.totalSpentOpen = 0;
campaigns.push(campaign);
addressToCampaignIndices[msg.sender].push(currentIndex);
emit CampaignCreated(msg.sender, _campaignName, currentIndex, _requiredDonation);
}
function getCampaign(uint256 index) public view returns(
string memory campaignName,
address owner,
string memory supportingDocuments,
uint256[4] totalDonations,
uint256[2] memory times,
string memory campaignType,
uint256 requiredDonation,
address[] memory donors,
address[] memory donorsOpen
){
Campaign memory currentCampaign = campaigns[index];
campaignName = currentCampaign.campaignName;
owner = currentCampaign.owner;
supportingDocuments = currentCampaign.supportingDocuments;
totalDonations[0] = currentCampaign.totalDonation;
totalDonations[1] = currentCampaign.totalDonationOpen;
totalDonations[2] = currentCampaign.totalSpent;
totalDonations[3] = currentCampaign.totalSpentOpen;
times[0] = currentCampaign.donationEndTime;
times[1] = currentCampaign.spendingEndTime;
campaignType = currentCampaign.campaignType;
requiredDonation = currentCampaign.requiredDonation;
donors = currentCampaign.donors;
donorsOpen = currentCampaign.donorsOpen;
}
function donateTokens(uint256 index, uint256 amount, bool isOpen) public {
Campaign storage currentCampaign = campaigns[index];
require(now <= currentCampaign.donationEndTime, "This campaign is not accepting donations now");
safeTransfer(msg.sender, address(this), amount);
if(isOpen == true){
currentCampaign.totalDonationOpen += amount;
if(currentCampaign.donationsOpen[msg.sender] == 0){
currentCampaign.donorsOpen.push(msg.sender);
}
currentCampaign.donationsOpen[msg.sender] += amount;
}else{
currentCampaign.totalDonation += amount;
if(currentCampaign.donations[msg.sender] == 0){
currentCampaign.donors.push(msg.sender);
}
currentCampaign.donations[msg.sender] += amount;
}
emit TokensDonated(index, amount, msg.sender);
}
function getMyCampaigns() public view returns(uint256[] indices){
indices = addressToCampaignIndices[msg.sender];
}
function getDonation(uint256 index, address donor) public view returns(uint256 amount, uint256 amountOpen){
Campaign storage currentCampaign = campaigns[index];
amount = currentCampaign.donations[donor];
amountOpen = currentCampaign.donationsOpen[donor];
}
function safeTransfer(address _from, address _to, uint256 _amount) private {
require(erc20.balanceOf(_from) >= _amount, "Sender Does Not Hold Enough Tokens");
require(erc20.allowance(_from, address(this)) >= _amount, "Sender Has Not Provided Enough Allowance to the Contract");
erc20.transferFrom(_from, _to, _amount);
}
function endCampaign(uint256 index) public {
Campaign storage currentCampaign = campaigns[index];
require(now > currentCampaign.donationEndTime, "Campaign can not be closed yet");
require(currentCampaign.owner == msg.sender, "Not the Owner of this campaign");
calculatePercentage(index);
}
function calculatePercentage(uint256 index) private {
Campaign storage currentCampaign = campaigns[index];
require(currentCampaign.campaignEnded == false, "Campaign has already ended");
address[] memory donors = currentCampaign.donors;
uint256 totalDonation = currentCampaign.totalDonation;
for(uint256 i = 0; i < donors.length; i++){
currentCampaign.percentageDonation[donors[i]] = (totalDonation * 10 ** decimal) / currentCampaign.donations[donors[i]];
}
donors = currentCampaign.donorsOpen;
totalDonation = currentCampaign.totalDonationOpen;
for(i = 0; i < donors.length; i++){
currentCampaign.percentageDonationsOpen[donors[i]] = (totalDonation * 10 ** decimal) / currentCampaign.donationsOpen[donors[i]];
}
currentCampaign.campaignEnded = true;
}
function spendDonations(uint256 index, address to, uint256 amount, string receipt, bool isOpen) public{
Campaign storage currentCampaign = campaigns[index];
require(now <= currentCampaign.spendingEndTime, "Spend Time is Closed");
SpendData memory currentSpend;
currentSpend.to = to;
currentSpend.spendReceipt = receipt;
currentSpend.amount = amount;
currentSpend.approved = false;
SpendingApprovals memory currentApproval;
currentApproval.campaignId = index;
uint256 expenditureId;
if(isOpen == true){
require(currentCampaign.totalSpentOpen + amount <= currentCampaign.totalDonationOpen, "You have used all your donation");
expenditureId = currentCampaign.numberOfSpendingsOpen++;
currentCampaign.expendituresOpen[expenditureId] = currentSpend;
currentCampaign.totalSpentOpen += amount;
currentApproval.isOpen = true;
currentApproval.expenditureId = expenditureId;
}else{
//TODO Write a check for KYC
require(currentCampaign.totalSpent + amount <= currentCampaign.totalDonation, "You have used all your donation");
expenditureId = currentCampaign.numberOfSpendingsOpen++;
currentCampaign.expenditures[expenditureId] = currentSpend;
currentCampaign.totalSpent += amount;
currentApproval.isOpen = false;
currentApproval.expenditureId = expenditureId;
}
recipientsToApprovals[to].push(currentApproval);
}
function approveExpense(uint256 apporvalRequestId) public{
SpendingApprovals storage currentApproval = recipientsToApprovals[msg.sender][apporvalRequestId];
Campaign storage currentCampaign = campaigns[currentApproval.campaignId];
if(currentApproval.isOpen){
SpendData storage currentSpend = currentCampaign.expendituresOpen[currentApproval.expenditureId];
} else{
currentSpend = currentCampaign.expenditures[currentApproval.expenditureId];
}
require(currentSpend.approved == false, "Expense Already Approved");
require(currentSpend.to == msg.sender, "Sender is not recipient of this Expense");
currentSpend.approved = true;
erc20.transfer(currentSpend.to, currentSpend.amount);
}
function getTotalApprovalRequests() public view returns(uint256 total){
total = recipientsToApprovals[msg.sender].length;
}
function getApprovalReqeustDetail(uint256 id) public view returns(uint256 amount, bool approved, string spendReceipt){
SpendingApprovals storage currentApproval = recipientsToApprovals[msg.sender][id];
Campaign storage currentCampaign = campaigns[currentApproval.campaignId];
SpendData storage currentSpend;
if(currentApproval.isOpen == true){
currentSpend = currentCampaign.expendituresOpen[currentApproval.expenditureId];
}else{
currentSpend = currentCampaign.expenditures[currentApproval.expenditureId];
}
amount = currentSpend.amount;
approved = currentSpend.approved;
spendReceipt = currentSpend.spendReceipt;
}
function getNumberOfSpendings(uint256 index) public view returns(uint256 numSpendings, uint256 numSpendingsOpen){
Campaign storage currentCampaign = campaigns[index];
numSpendings = currentCampaign.numberOfSpendings;
numSpendingsOpen = currentCampaign.numberOfSpendingsOpen;
}
function getSpendingDetail(uint256 campaignIndex, uint256 spendingIndex, bool isOpen) public view returns(uint256 amount, address to, string receipt){
Campaign storage currentCampaign = campaigns[campaignIndex];
SpendData storage currentSpend = currentCampaign.expenditures[spendingIndex];
if(isOpen == true){
currentSpend = currentCampaign.expendituresOpen[spendingIndex];
}
amount = currentSpend.amount;
to = currentSpend.to;
receipt = currentSpend.spendReceipt;
}
function getPendingShareBack(uint256 index) public {
Campaign storage currentCampaign = campaigns[index];
require(now > currentCampaign.spendingEndTime, "The Campaign Owner is still spending money");
require(currentCampaign.moneyReturned == false, "Share is already returned");
uint256 leftAmount = currentCampaign.totalDonation - currentCampaign.totalSpent;
address[] memory donors = currentCampaign.donors;
for(uint256 i = 0; i < donors.length; i++){
uint256 donorPercentage = getDonationPercentage(index, donors[i], false);
uint256 toBeReturned = (leftAmount * 10 ** decimal) / donorPercentage;
erc20.transfer(donors[i], toBeReturned);
}
leftAmount = currentCampaign.totalDonationOpen - currentCampaign.totalSpentOpen;
donors = currentCampaign.donorsOpen;
for(i = 0; i < donors.length; i++){
donorPercentage = getDonationPercentage(index, donors[i], true);
toBeReturned = (leftAmount * 10 ** decimal) / donorPercentage;
erc20.transfer(donors[i], toBeReturned);
}
currentCampaign.moneyReturned = true;
}
function getDonationPercentage(uint256 index, address donor, bool isOpen) private view returns(uint256 percentage){
if(isOpen == true){
percentage = campaigns[index].percentageDonationsOpen[donor];
}else{
percentage = campaigns[index].percentageDonation[donor];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment