-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCampaign.sol
More file actions
236 lines (201 loc) · 6.98 KB
/
Campaign.sol
File metadata and controls
236 lines (201 loc) · 6.98 KB
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title Campaign
* @author conceptcodes.eth
* @notice This contract is used to represent a campaign.
* @dev All function calls are currently intended to be made by the dApp only
*/
contract Campaign is Ownable, AccessControl {
// ----------------- Variables -----------------
Details private campaignDetails;
using Counters for Counters.Counter;
Counters.Counter private totalLikes;
Counters.Counter private numApplications;
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant USER_ROLE = keccak256("USER_ROLE");
// ----------------- Events -----------------
/**
* @dev Emitted when the status of the campaign is updated
* @param status the new status of the campaign
* @param timestamp timestamp of the event
*/
event StatusUpdated(Status status, uint256 timestamp);
/**
* @dev Emitted when a user submits an application to the campaign
* @param user the user who submitted the application
* @param timestamp timestamp of the event
*/
event Liked(address user, uint256 timestamp);
/**
* @dev Emitted when a user's application is chosen
* @param user the user who submitted the application
* @param numApplications number of applications to the campaign
* @param timestamp timestamp of the event
*/
event UserSelected(
address user,
uint256 numApplications,
uint256 timestamp
);
/**
* @dev Emitted when a user submits an application to the campaign
* @param user the user who submitted the application
* @param timestamp timestamp of the event
*/
event Applied(address user, uint256 timestamp);
/**
* @dev Emitted when a user withdraws their payout from the campaign
* @param totalLikes total number of likes the campaign received
* @param minLikes minimum number of likes required to receive payout
* @param timestamp timestamp of the event
*/
event Withdrawal(
uint256 totalLikes,
uint256 minLikes,
uint256 timestamp
);
// ----------------- Structs, Enums, Mappings -----------------
struct Details {
string name;
string description;
string image;
uint256 minLikes;
Status status;
uint256 payout;
address user;
address brand;
uint256 applicationWindowEnd;
uint256 campaignEnd;
}
enum Status {
Created,
Active,
Finished
}
mapping(address => bool) public likes;
mapping(address => bool) private applications;
// ----------------- Constructor -----------------
constructor(
string memory _name,
string memory _description,
string memory _image,
uint256 _minLikes,
uint256 _applyTime,
uint256 _activeTime,
address _brand
) payable {
campaignDetails.name = _name;
campaignDetails.description = _description;
campaignDetails.image = _image;
campaignDetails.minLikes = _minLikes;
campaignDetails.status = Status.Created;
campaignDetails.payout = msg.value;
campaignDetails.user = address(0);
campaignDetails.brand = _brand;
campaignDetails.applicationWindowEnd = block.timestamp + _applyTime;
campaignDetails.campaignEnd =
campaignDetails.applicationWindowEnd +
_activeTime;
emit StatusUpdated(campaignDetails.status, block.timestamp);
}
// ----------------- Getters -----------------
function getDetails()
public
view
returns (string memory, string memory, string memory)
{
return (
campaignDetails.name,
campaignDetails.description,
campaignDetails.image
);
}
function getUser() public view returns (address) {
return campaignDetails.user;
}
function getStatus() public view returns (Status) {
return campaignDetails.status;
}
function getTotalLikes() public view returns (uint256) {
return totalLikes.current();
}
function getMinLikes() public view returns (uint256) {
return campaignDetails.minLikes;
}
function getPayoutAmt() public view returns (uint256) {
return campaignDetails.payout;
}
function getCampaignEnd() public view returns (uint256) {
return campaignDetails.campaignEnd;
}
function getApplicationWindowEnd() public view returns (uint256) {
return campaignDetails.applicationWindowEnd;
}
function hasMetLikeGoal() public view returns (bool) {
return totalLikes.current() >= campaignDetails.minLikes;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
function getBrand() public view returns (address) {
return campaignDetails.brand;
}
// ----------------- Setters -----------------
function updateStatus(Status _status) public onlyRole(ADMIN_ROLE) {
campaignDetails.status = _status;
}
function assign(address _user) public onlyRole(ADMIN_ROLE) {
campaignDetails.user = _user;
_grantRole(USER_ROLE, _user);
}
// ----------------- Logic -----------------
function applyForCampaign() public {
require(
campaignDetails.status == Status.Created,
"Campaign is not accepting applications"
);
require(
block.timestamp <= campaignDetails.applicationWindowEnd,
"Application window has closed"
);
require(
!applications[msg.sender],
"You have already applied to this campaign"
);
applications[msg.sender] = true;
numApplications.increment();
emit Applied(msg.sender, block.timestamp);
}
function like() public {
require(
campaignDetails.status == Status.Active,
"Campaign is not active"
);
require(
block.timestamp <= campaignDetails.campaignEnd,
"Campaign has ended"
);
require(!likes[msg.sender], "You have already liked this campaign");
emit Liked(msg.sender, block.timestamp);
totalLikes.increment();
}
// TODO: Add reentrancy guard
function payout() public onlyOwner onlyRole(USER_ROLE) {
require(
campaignDetails.status == Status.Finished,
"Campaign is not finished"
);
require(campaignDetails.user != address(0), "Campaign has no user");
require(campaignDetails.payout > 0, "Campaign has no payout");
payable(campaignDetails.user).transfer(campaignDetails.payout);
emit Withdrawal(
totalLikes.current(),
campaignDetails.minLikes,
block.timestamp
);
}
}