-
Notifications
You must be signed in to change notification settings - Fork 7
/
BOP.sol
271 lines (221 loc) · 7.37 KB
/
BOP.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
//Note that interfaces refer to these contracts as "Burnable Payments", rather than "Burnable Open Payments".
//The next version of this contract will follow suit,
//removing the misleading/redundant "Open" from the names of these contracts.
//A BurnableOpenPayment is instantiated with a Payer, a title, a serviceDeposit, and an autoreleaseInterval.
//Worker is not set when the contract is instantiated; the payment is in the Open state.
//The constructor is payable, so the contract can be instantiated with initial funds.
//In addition, anyone can add more funds to the Payment by calling addFunds.
//All behavior of the contract is directed by the Payer,
//but he can only recover() the ether if the contract is in the Open state.
//In the Open state,
//Payer can logPayerStatement() to add additional details, clarifications, or corrections.
//Anyone can become the Worker by contributing the serviceDeposit with commit();
//this changes the state from Open to Committed. The BOP will never return to the Open state.
//After this state change, Worker will never be changed.
//In the Committed state,
//AutoreleaseTime is set to (now + autoreleaseInterval).
//Payer/Worker can logPayerStatement() or logWorkerStatement(), respectively.
//Payer can at any time choose to burn() or release() to Worker any amount of funds.
//Payer can delayAutorelease() setting the autoreleaseTime to (now + autoreleaseInterval), any number of times.
//If autoreleaseTime comes, Worker can triggerAutorelease() to claim all ether remaining in the payment.
//Once the balance of the payment is 0, the state changes to Closed.
//In the Closed state,
//Payer and Worker can still log statements.
//If addFunds() is called, the contract returns to the Committed state.
pragma solidity ^ 0.4.10;
contract BurnableOpenPaymentFactory {
event NewBOP(address indexed newBOPAddress, address payer, uint serviceDeposit, uint autoreleaseTime, string title, string initialStatement);
//contract address array
address[]public BOPs;
function getBOPCount()
public
constant
returns(uint) {
return BOPs.length;
}
function newBurnableOpenPayment(address payer, uint serviceDeposit, uint autoreleaseInterval, string title, string initialStatement)
public
payable
returns(address) {
//pass along any ether to the constructor
address newBOPAddr = (new BurnableOpenPayment).value(msg.value)(payer, serviceDeposit, autoreleaseInterval, title, initialStatement);
NewBOP(newBOPAddr, payer, serviceDeposit, autoreleaseInterval, title, initialStatement);
//save created BOPs in contract array
BOPs.push(newBOPAddr);
return newBOPAddr;
}
}
contract BurnableOpenPayment {
//title will never change
string public title;
//BOP will start with a payer but no worker (worker==0x0)
address public payer;
address public worker;
address constant burnAddress = 0x0;
//Set to true if fundsRecovered is called
bool recovered = false;
//Note that these will track, but not influence the BOP logic.
uint public amountDeposited;
uint public amountBurned;
uint public amountReleased;
//Amount of ether a prospective worker must pay to permanently become the worker. See commit().
uint public serviceDeposit;
//How long should we wait before allowing the default release to be called?
uint public autoreleaseInterval;
//Calculated from autoreleaseInterval in commit(),
//and recaluclated whenever the payer (or possibly the worker) calls delayhasDefaultRelease()
//After this time, auto-release can be called by the Worker.
uint public autoreleaseTime;
//Most action happens in the Committed state.
enum State {
Open,
Committed,
Closed
}
State public state;
//Note that a BOP cannot go from Committed back to Open, but it can go from Closed back to Committed
//(this would retain the committed worker). Search for Closed and Unclosed events to see how this works.
modifier inState(State s) {
require(s == state);
_;
}
modifier onlyPayer() {
require(msg.sender == payer);
_;
}
modifier onlyWorker() {
require(msg.sender == worker);
_;
}
modifier onlyPayerOrWorker() {
require((msg.sender == payer) || (msg.sender == worker));
_;
}
event Created(address indexed contractAddress, address payer, uint serviceDeposit, uint autoreleaseInterval, string title);
event FundsAdded(address from, uint amount); //The payer has added funds to the BOP.
event PayerStatement(string statement);
event WorkerStatement(string statement);
event FundsRecovered();
event Committed(address worker);
event FundsBurned(uint amount);
event FundsReleased(uint amount);
event Closed();
event Unclosed();
event AutoreleaseDelayed();
event AutoreleaseTriggered();
function BurnableOpenPayment(address _payer, uint _serviceDeposit, uint _autoreleaseInterval, string _title, string initialStatement)
public
payable {
Created(this, _payer, _serviceDeposit, _autoreleaseInterval, _title);
if (msg.value > 0) {
//Here we use tx.origin instead of msg.sender (msg.sender is just the factory contract)
FundsAdded(tx.origin, msg.value);
amountDeposited += msg.value;
}
title = _title;
state = State.Open;
payer = _payer;
serviceDeposit = _serviceDeposit;
autoreleaseInterval = _autoreleaseInterval;
if (bytes(initialStatement).length > 0)
PayerStatement(initialStatement);
}
function getFullState()
public
constant
returns(address, string, State, address, uint, uint, uint, uint, uint, uint, uint) {
return (payer, title, state, worker, this.balance, serviceDeposit, amountDeposited, amountBurned, amountReleased, autoreleaseInterval, autoreleaseTime);
}
function addFunds()
public
payable {
require(msg.value > 0);
FundsAdded(msg.sender, msg.value);
amountDeposited += msg.value;
if (state == State.Closed) {
state = State.Committed;
Unclosed();
}
}
function recoverFunds()
public
onlyPayer()
inState(State.Open) {
recovered = true;
FundsRecovered();
selfdestruct(payer);
}
function commit()
public
inState(State.Open)
payable{
require(msg.value == serviceDeposit);
if (msg.value > 0) {
FundsAdded(msg.sender, msg.value);
amountDeposited += msg.value;
}
worker = msg.sender;
state = State.Committed;
Committed(worker);
autoreleaseTime = now + autoreleaseInterval;
}
function internalBurn(uint amount)
private
inState(State.Committed) {
burnAddress.transfer(amount);
amountBurned += amount;
FundsBurned(amount);
if (this.balance == 0) {
state = State.Closed;
Closed();
}
}
function burn(uint amount)
public
inState(State.Committed)
onlyPayer() {
internalBurn(amount);
}
function internalRelease(uint amount)
private
inState(State.Committed) {
worker.transfer(amount);
amountReleased += amount;
FundsReleased(amount);
if (this.balance == 0) {
state = State.Closed;
Closed();
}
}
function release(uint amount)
public
inState(State.Committed)
onlyPayer() {
internalRelease(amount);
}
function logPayerStatement(string statement)
public
onlyPayer() {
PayerStatement(statement);
}
function logWorkerStatement(string statement)
public
onlyWorker() {
WorkerStatement(statement);
}
function delayAutorelease()
public
onlyPayer()
inState(State.Committed) {
autoreleaseTime = now + autoreleaseInterval;
AutoreleaseDelayed();
}
function triggerAutorelease()
public
onlyWorker()
inState(State.Committed) {
require(now >= autoreleaseTime);
AutoreleaseTriggered();
internalRelease(this.balance);
}
}