forked from charitychain-io/smartcontracts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Campaign.sol
198 lines (159 loc) · 7.01 KB
/
Campaign.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
pragma solidity ^0.4.18;
/*
To create a campaign on Charitychain.io, you must drop half of the goal and collect the rest by inviting your entourage to unblock your donation.
If successful, 100% of the funds are donated to the NGO.
If the objective of the campaign is not achieved, everyone is refunded.
*/
/*
Attention, important warning here!
This is a beta version, it should not be used on the Mainnet Ethereum network
*/
/*
This code is inspired by many quality open source projects (giveth.io, weifund.io, openzeppelin.org, ...)
*/
contract Campaign {
// the three possible states of Charitychain Campaign
enum Stages {
CampaignInprogress,
CampaignFailure,
CampaignSuccess
}
// the Contribution data structure
struct Contribution {
// the contribution sender
address sender;
// the value of the contribution
uint256 value;
// the time the contribution was created
uint256 created;
}
// the minimum amount of funds needed to be a success after expiry (in wei)
uint256 public fundingGoal;
// the maximum amount of funds that can be raised (in wei)
uint256 public fundingCap;
// the total amount raised by this campaign (in wei)
uint256 public amountRaised;
// the current campaign expiry (future block number)
uint256 public expiry;
// the time at which the campaign was created (in UNIX timestamp)
uint256 public created;
// the beneficiary of the funds raised, if the campaign is a success
address public beneficiary;
// the contributions data store, where all contributions are notated
Contribution[] public contributions;
// all contribution ID's of a specific sender
mapping(address => uint256[]) public contributionsBySender;
// maps the contribution ID to a bool (has the refund been claimed for this contribution
mapping(uint256 => bool) public refundsClaimed;
// the human readable name of the Campaign
string public name;
// contract owner address
address public owner;
// check the campaign state
modifier atStage(Stages _expectedStage) {
// if the current state does not equal the expected one, throw
require(stage() == uint256(_expectedStage));
_;
}
// check if the contribution is valid
modifier validContribution() {
// if the msg value is zero or amount raised plus the curent message value
// is greater than the funding cap, then throw error
require(msg.value != 0 && amountRaised + msg.value < fundingCap && amountRaised + msg.value > amountRaised);
_;
}
//check if this refund request is valid
modifier validRefund(uint256 _contributionID) {
Contribution memory refundContribution = contributions[_contributionID];
// the refund for this contribution is already claimed or the contribution sender is not the msg.sender
require(!refundsClaimed[_contributionID] && refundContribution.sender == msg.sender);
_;
}
// only the beneficiary of the campaign can use the method with this modifier
modifier onlybeneficiary() {
require(msg.sender == beneficiary);
_;
}
// only the owner can use the method with this modifier
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// Campaign events
event LogContributionMade (address _contributor);
event LogContributionRefunded(address _payoutDestination, uint256 _payoutAmount);
event LogBeneficiaryPayoutMade (address _payoutDestination, uint256 _amountRaised);
// the contract constructor
//The constructor can receive funds with the `payable` modifier.
// To create a campaign, 3 arguments and an initial payment in Eth are required.
function Campaign(string _name, uint256 _expiry, address _beneficiary) payable public {
//set the owner
owner = msg.sender;
// set the campaign name
name = _name;
// set the campaign expiry
expiry = _expiry;
// set the funding goal in wei, the goal is 2* the first contribution
fundingGoal = msg.value*2;
// set the campaign funding cap in wei, arbitrarily set at 10x fundingGoal
fundingCap = fundingGoal*10;
// set the beneficiary address
beneficiary = _beneficiary;
// set the time the campaign was created
created = block.number;
// The creator must be the first contributor
contribute();
}
// allow fallback function to be used to make contributions
function () public payable {
contribute();
}
// get the current campaign stage
function stage() public constant returns (uint256) {
if (block.number < expiry && amountRaised < fundingCap) {
return uint256(Stages.CampaignInprogress);
} else if (block.number >= expiry && amountRaised < fundingGoal) {
return uint256(Stages.CampaignFailure);
} else if ((block.number >= expiry && amountRaised >= fundingGoal) || amountRaised >= fundingCap) {
return uint256(Stages.CampaignSuccess);
}
}
function contribute() public payable atStage(Stages.CampaignInprogress) validContribution() returns (uint256 contributionID) {
// increase contributions array length by 1
contributionID = contributions.length++;
// store contribution data in the contributions array
contributions[contributionID] = Contribution({
sender: msg.sender,
value: msg.value,
created: block.number
});
// add the contribution ID to that senders address
contributionsBySender[msg.sender].push(contributionID);
// increase the amount raised by the message value
amountRaised += msg.value;
// fire the contribution made event
LogContributionMade(msg.sender);
}
// payout the current balance to the beneficiary
function payoutToBeneficiary() public atStage(Stages.CampaignSuccess) onlybeneficiary() {
uint256 payoutAmount = this.balance;
msg.sender.transfer(payoutAmount);
LogBeneficiaryPayoutMade(msg.sender, payoutAmount);
}
function withdrawRefundContribution(uint256 _contributionID) public atStage(Stages.CampaignFailure) validRefund(_contributionID) {
refundsClaimed[_contributionID] = true;
// get the contribution for that contribution ID
Contribution memory refundContribution = contributions[_contributionID];
// send funds to the contributor
msg.sender.transfer(refundContribution.value);
LogContributionRefunded(msg.sender, refundContribution.value);
}
//emergency mechanism, force expiry an allows everyone to claim their money depending on the stage of the campaign
function emergencyCampaignExpiry() external onlyOwner {
expiry = 0;
}
// the total number of contributions made to this campaign
function totalContributions() public constant returns (uint256 amount) {
return uint256(contributions.length);
}
}