-
Notifications
You must be signed in to change notification settings - Fork 47
/
MarketCrowdfunding.sol
310 lines (293 loc) · 12.9 KB
/
MarketCrowdfunding.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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
pragma solidity ^0.4.0;
import "MarketFactories/AbstractMarketFactory.sol";
import "Tokens/AbstractToken.sol";
import "MarketMakers/AbstractMarketMaker.sol";
import "EventFactory/AbstractEventFactory.sol";
/// @title Market crowdfunding contract - Allows crowdfunding of markets.
/// @author Stefan George - <[email protected]>
/// @author Martin Koeppelmann - <[email protected]>
contract MarketCrowdfunding {
/*
* Events
*/
event CampaignCreation(address indexed creator, bytes32 indexed campaignHash);
event Funding(address indexed investor, uint256 investment, bytes32 indexed campaignHash);
/*
* External contracts
*/
EventFactory constant eventFactory = EventFactory({{EventFactory}});
/*
* Data structures
*/
// campaign hash => Campaign
mapping(bytes32 => Campaign) public campaigns;
// event hash => campaign hashes
mapping (bytes32 => bytes32[]) public campaignHashes;
// user address => campaign hash => funding
mapping(address => mapping(bytes32 => uint)) public shares;
struct Campaign {
MarketFactory marketFactory;
Token token;
MarketMaker marketMaker;
bytes32 eventHash;
bytes32 marketHash;
uint fee;
uint initialFunding; // For market
uint totalFunding; // Funding for market and initial shares
uint raisedAmount;
uint closingAtTimestamp;
uint collectedFees;
/* outcome => shares */
uint[] initialShares;
}
/*
* Read and write functions
*/
/// @dev Starts a new crowdfunding campaign to fund a market for an event. Returns campaign hash.
/// @param marketFactory Address of market factory contract.
/// @param eventHash Hash identifying event for market.
/// @param fee Fee charged by investors for trades on market.
/// @param initialFunding Initial funding for automated market maker.
/// @param totalFunding Total funding needed for campaign to complete successfully.
/// @param marketMaker Contract address of automated market maker.
/// @param closingAtTimestamp Block number when campaign ends. Funding has to be completed until this block.
/// @param initialShares An array of an initial share distribution. The market maker buys those shares from his own market on creation. This is why total funding is not necessarily equal to initial funding of market.
/// @return campaignHash Returns campaign hash.
function startCampaign(address marketFactory,
bytes32 eventHash,
uint fee,
uint initialFunding,
uint totalFunding,
address marketMaker,
uint closingAtTimestamp,
uint[] initialShares
)
external
returns (bytes32 campaignHash)
{
campaignHash = sha3(marketFactory,
eventHash,
fee,
initialFunding,
totalFunding,
marketMaker,
closingAtTimestamp,
initialShares);
var (, , , , eventOutcomeCount, eventTokenAddress, , , , ) = eventFactory.getEvent(eventHash);
if (campaigns[campaignHash].closingAtTimestamp > 0 || eventOutcomeCount == 0) {
// Campaign exists already or event is invalid
throw;
}
campaigns[campaignHash].marketFactory = MarketFactory(marketFactory);
campaigns[campaignHash].eventHash = eventHash;
campaigns[campaignHash].fee = fee;
campaigns[campaignHash].initialFunding = initialFunding;
campaigns[campaignHash].totalFunding = totalFunding;
campaigns[campaignHash].marketMaker = MarketMaker(marketMaker);
campaigns[campaignHash].token = Token(eventTokenAddress);
campaigns[campaignHash].closingAtTimestamp = closingAtTimestamp;
campaigns[campaignHash].initialShares = initialShares;
campaignHashes[eventHash].push(campaignHash);
CampaignCreation(msg.sender, campaignHash);
}
/// @dev Creates market once funding is successfully completed. Returns success.
/// @param campaignHash Hash identifying campaign.
function createMarket(bytes32 campaignHash)
external
returns (bytes32 marketHash)
{
MarketFactory marketFactory = campaigns[campaignHash].marketFactory;
Token token = campaigns[campaignHash].token;
if (campaigns[campaignHash].raisedAmount < campaigns[campaignHash].totalFunding) {
// Campaign funding goal was not reached
throw;
}
if (!token.approve(marketFactory, campaigns[campaignHash].totalFunding)) {
// Tokens could not be transferred
throw;
}
marketHash = marketFactory.createMarket(campaigns[campaignHash].eventHash,
campaigns[campaignHash].fee,
campaigns[campaignHash].initialFunding,
campaigns[campaignHash].marketMaker);
if (marketHash == 0) {
// Market could not be created
throw;
}
campaigns[campaignHash].marketHash = marketHash;
uint totalCosts = 0;
for (uint8 i=0; i<campaigns[campaignHash].initialShares.length; i++) {
uint buyValue = campaigns[campaignHash].totalFunding - campaigns[campaignHash].initialFunding - totalCosts;
totalCosts += marketFactory.buyShares(marketHash, i, campaigns[campaignHash].initialShares[i], buyValue);
}
}
/// @dev Funds campaign until total funding is reached with Ether or tokens. Returns success.
/// @param campaignHash Hash identifying campaign.
/// @param tokens Number of tokens used for funding in case funding is not done in Ether.
function fund(bytes32 campaignHash, uint tokens)
external
{
Token token = campaigns[campaignHash].token;
if ( campaigns[campaignHash].closingAtTimestamp < now
|| campaigns[campaignHash].raisedAmount == campaigns[campaignHash].totalFunding)
{
// Campaign is over or funding goal was reached
throw;
}
uint investment = tokens;
if (campaigns[campaignHash].raisedAmount + investment > campaigns[campaignHash].totalFunding) {
// Sender send too much value, difference is returned to sender
investment = campaigns[campaignHash].totalFunding - campaigns[campaignHash].raisedAmount;
}
if (investment == 0 || !token.transferFrom(msg.sender, this, investment)) {
// Tokens could not be transferred
throw;
}
campaigns[campaignHash].raisedAmount += investment;
shares[msg.sender][campaignHash] += investment;
Funding(msg.sender, investment, campaignHash);
}
/// @dev Withdraws funds from an investor in case of an unsuccessful campaign. Returns success.
/// @param campaignHash Hash identifying campaign.
function withdrawFunding(bytes32 campaignHash)
external
{
if ( campaigns[campaignHash].closingAtTimestamp >= now
|| campaigns[campaignHash].raisedAmount == campaigns[campaignHash].totalFunding)
{
// Campaign is still going or market has been created
throw;
}
uint funding = shares[msg.sender][campaignHash];
shares[msg.sender][campaignHash] = 0;
Token token = campaigns[campaignHash].token;
if (funding > 0 && !token.transfer(msg.sender, funding)) {
// Tokens could not be transferred
throw;
}
}
/// @dev Withdraws fees earned by market. Has to be done before a market investor can get its share of those winnings. Returns success.
/// @param campaignHash Hash identifying campaign.
function withdrawContractFees(bytes32 campaignHash)
external
{
campaigns[campaignHash].collectedFees += campaigns[campaignHash].marketFactory.withdrawFees(campaigns[campaignHash].marketHash);
}
/// @dev Withdraws investor's share of earned fees by a market. Returns success.
/// @param campaignHash Hash identifying campaign.
function withdrawFees(bytes32 campaignHash)
external
{
if (campaigns[campaignHash].collectedFees == 0) {
// No fees collected
throw;
}
Token token = campaigns[campaignHash].token;
uint userFees = campaigns[campaignHash].collectedFees * shares[msg.sender][campaignHash] / campaigns[campaignHash].totalFunding;
shares[msg.sender][campaignHash] = 0;
if (userFees > 0 && !token.transfer(msg.sender, userFees)) {
// Tokens could not be transferred
throw;
}
}
/*
* Read functions
*/
/// @dev Returns array of all campaign hashes of corresponding event hashes.
/// @param eventHashes Array of event hashes identifying events.
/// @return allCampaignHashes Returns all campaign hashes.
function getCampaignHashes(bytes32[] eventHashes)
constant
external
returns (uint[] allCampaignHashes)
{
// Calculate array size
uint arrPos = 0;
for (uint i=0; i<eventHashes.length; i++) {
uint campaignHashesCount = campaignHashes[eventHashes[i]].length;
if (campaignHashesCount > 0) {
arrPos += 2 + campaignHashesCount;
}
}
// Fill array
allCampaignHashes = new uint[](arrPos);
arrPos = 0;
for (i=0; i<eventHashes.length; i++) {
campaignHashesCount = campaignHashes[eventHashes[i]].length;
if (campaignHashesCount > 0) {
allCampaignHashes[arrPos] = uint(eventHashes[i]);
allCampaignHashes[arrPos + 1] = campaignHashesCount;
for (uint j=0; j<campaignHashesCount; j++) {
allCampaignHashes[arrPos + 2 + j] = uint(campaignHashes[eventHashes[i]][j]);
}
arrPos += 2 + campaignHashesCount;
}
}
}
/// @dev Returns array of encoded campaigns.
/// @param _campaignHashes Array of campaign hashes identifying campaigns.
/// @return _campaignHashes Returns campaign hashes.
function getCampaigns(bytes32[] _campaignHashes)
constant
external
returns (uint[] allCampaigns)
{
// Calculate array size
uint arrPos = 0;
for (uint i=0; i<_campaignHashes.length; i++) {
arrPos += 13 + campaigns[_campaignHashes[i]].initialShares.length;
}
// Fill array
allCampaigns = new uint[](arrPos);
arrPos = 0;
for (i=0; i<_campaignHashes.length; i++) {
bytes32 campaignHash = _campaignHashes[i];
Campaign campaign = campaigns[campaignHash];
allCampaigns[arrPos] = uint(campaignHash);
allCampaigns[arrPos + 1] = uint(campaign.marketFactory);
allCampaigns[arrPos + 2] = uint(campaign.token);
allCampaigns[arrPos + 3] = uint(campaign.marketMaker);
allCampaigns[arrPos + 4] = uint(campaign.eventHash);
allCampaigns[arrPos + 5] = uint(campaign.marketHash);
allCampaigns[arrPos + 6] = campaign.fee;
allCampaigns[arrPos + 7] = campaign.initialFunding;
allCampaigns[arrPos + 8] = campaign.totalFunding;
allCampaigns[arrPos + 9] = campaign.raisedAmount;
allCampaigns[arrPos + 10] = campaign.closingAtTimestamp;
allCampaigns[arrPos + 11] = campaign.collectedFees;
allCampaigns[arrPos + 12] = campaign.initialShares.length;
for (uint j=0; j<campaign.initialShares.length; j++) {
allCampaigns[arrPos + 13 + j] = campaign.initialShares[j];
}
arrPos += 13 + campaign.initialShares.length;
}
}
/// @dev Returns array of encoded investments an investor holds in campaigns.
/// @param _campaignHashes Array of campaign hashes identifying campaigns.
/// @return allShares Returns all user's shares.
function getShares(address user, bytes32[] _campaignHashes)
constant
external
returns (uint[] allShares)
{
// Calculate array size
uint arrPos = 0;
for (uint i=0; i<_campaignHashes.length; i++) {
bytes32 campaignHash = _campaignHashes[i];
if (shares[user][campaignHash] > 0) {
arrPos += 2;
}
}
// Fill array
allShares = new uint[](arrPos);
arrPos = 0;
for (i=0; i<_campaignHashes.length; i++) {
campaignHash = _campaignHashes[i];
if (shares[user][campaignHash] > 0) {
allShares[arrPos] = uint(campaignHash);
allShares[arrPos + 1] = shares[user][campaignHash];
arrPos += 2;
}
}
}
}