-
Notifications
You must be signed in to change notification settings - Fork 3
/
BurnablePayment.sol
328 lines (276 loc) · 9.51 KB
/
BurnablePayment.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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//A BurnablePayment is instantiated with one "opening agent" (Payer or Worker), a title, an initial deposit, a commitThreshold, and an autoreleaseInterval.
//If the opening agent is the payer:
// The contract starts in the PayerOpened state.
// Payer is expected to request some service via the title and additional statements.
// The initial deposit represents the amount Payer will pay for the service.
// Another user can claim the job by calling commit() and becoming the worker.
//If the opening agent is the worker:
// The contract starts in the WorkerOpened state.
// Worker is expected to offer some service via the title and additional statements.
// The initial deposit serves as collateral that a payer will have control over.
// Another user can claim the service by calling commit() and becoming the payer.
//While in either Open state,
// The opening agent can call recover() to destroy the contract and refund all deposited funds.
// The opening agent can log statements to add additional details, clarifications, or corrections.
// Anyone can enter the contract as the open role by contributing the commitThreshold with commit();
// this changes the state to Committed.
//Upon changing from either Open state -> Committed:
// AutoreleaseTime is set to (now + autoreleaseInterval).
//In the Committed state:
// Both roles are permanent.
// Both Payer and Worker can log statements.
// 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.2;
contract BurnablePaymentFactory {
//contract address array
address[]public BPs;
event NewBurnablePayment(
address indexed bpAddress,
bool payerOpened,
address creator,
uint deposited,
uint commitThreshold,
uint autoreleaseInterval,
string title,
string initialStatement
);
function newBP(bool payerOpened, address creator, uint commitThreshold, uint autoreleaseInterval, string title, string initialStatement)
public
payable
returns (address newBPAddr)
{
//pass along any ether to the constructor
newBPAddr = (new BurnablePayment).value(msg.value)(payerOpened, creator, commitThreshold, autoreleaseInterval, title, initialStatement);
NewBurnablePayment(newBPAddr, payerOpened, creator, msg.value, commitThreshold, autoreleaseInterval, title, initialStatement);
BPs.push(newBPAddr);
return newBPAddr;
}
function getBPCount()
public
constant
returns(uint)
{
return BPs.length;
}
}
contract BurnablePayment {
//title will never change
string public title;
//BP will start with a payer or a worker but not both
address public payer;
address public worker;
address constant BURN_ADDRESS = 0x0;
//Set to true if fundsRecovered is called
bool recovered = false;
//Note that these will track, but not influence the BP logic.
uint public amountDeposited;
uint public amountBurned;
uint public amountReleased;
//Amount of ether that must be deposited via commit() to become the second party of the BP.
uint public commitThreshold;
//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 {
PayerOpened,
WorkerOpened,
Committed,
Closed
}
//Note that a BP cannot go from Committed back to either Open state, but it can go from Closed back to Committed
//Search for Closed and Unclosed events to see how this works.
State public state;
modifier inState(State s) {
require(s == state);
_;
}
modifier inOpenState() {
require(state == State.PayerOpened || state == State.WorkerOpened);
_;
}
modifier onlyPayer() {
require(msg.sender == payer);
_;
}
modifier onlyWorker() {
require(msg.sender == worker);
_;
}
modifier onlyPayerOrWorker() {
require((msg.sender == payer) || (msg.sender == worker));
_;
}
modifier onlyCreatorWhileOpen() {
if (state == State.PayerOpened) {
require(msg.sender == payer);
} else if (state == State.WorkerOpened) {
require(msg.sender == worker);
} else {
revert();
}
_;
}
event Created(address indexed contractAddress, bool payerOpened, address creator, uint commitThreshold, uint autoreleaseInterval, string title);
event FundsAdded(address from, uint amount); //The payer has added funds to the BP.
event PayerStatement(string statement);
event WorkerStatement(string statement);
event FundsRecovered();
event Committed(address committer);
event FundsBurned(uint amount);
event FundsReleased(uint amount);
event Closed();
event Unclosed();
event AutoreleaseDelayed();
event AutoreleaseTriggered();
function BurnablePayment(bool payerIsOpening, address creator, uint _commitThreshold, uint _autoreleaseInterval, string _title, string initialStatement)
public
payable
{
Created(this, payerIsOpening, creator, _commitThreshold, 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;
if (payerIsOpening) {
state = State.PayerOpened;
payer = creator;
} else {
state = State.WorkerOpened;
worker = creator;
}
commitThreshold = _commitThreshold;
autoreleaseInterval = _autoreleaseInterval;
if (bytes(initialStatement).length > 0) {
if (payerIsOpening) {
PayerStatement(initialStatement);
} else {
WorkerStatement(initialStatement);
}
}
}
function addFunds()
public
payable
onlyPayerOrWorker()
{
require(msg.value > 0);
FundsAdded(msg.sender, msg.value);
amountDeposited += msg.value;
if (state == State.Closed) {
state = State.Committed;
Unclosed();
}
}
function recoverFunds()
public
onlyCreatorWhileOpen()
{
recovered = true;
FundsRecovered();
if (state == State.PayerOpened)
selfdestruct(payer);
else if (state == State.WorkerOpened)
selfdestruct(worker);
}
function commit()
public
inOpenState()
payable
{
require(msg.value == commitThreshold);
if (msg.value > 0) {
FundsAdded(msg.sender, msg.value);
amountDeposited += msg.value;
}
if (state == State.PayerOpened)
worker = msg.sender;
else
payer = msg.sender;
state = State.Committed;
Committed(msg.sender);
autoreleaseTime = now + autoreleaseInterval;
}
function internalBurn(uint amount)
private
{
BURN_ADDRESS.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
{
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);
}
function getFullState()
public
constant
returns(State, address, address, string, uint, uint, uint, uint, uint, uint, uint) {
return (state, payer, worker, title, this.balance, commitThreshold, amountDeposited, amountBurned, amountReleased, autoreleaseInterval, autoreleaseTime);
}
}