-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBurnableOpenPayment.sol
247 lines (203 loc) · 7.73 KB
/
BurnableOpenPayment.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
//A BurnableOpenPayment is instantiated with a specified payer and a commitThreshold.
//The recipient is not set when the contract is instantiated.
//The constructor is payable, so the contract can be instantiated with initial funds.
//Only the payer can fund the Payment after instantiation.
//All behavior of the contract is directed by the payer, but
//the payer can never directly recover the payment,
//unless he calls the recover() function before anyone else commit()s.
//Anyone can become the recipient by contributing the commitThreshold with commit().
//The recipient will never be changed once it's been set via commit().
//The payer can at any time choose to burn or release to the recipient any amount of funds.
pragma solidity ^0.4.10;
contract BurnableOpenPayment {
//BOP will start with a payer but no recipient (recipient==0x0)
address public payer;
address public recipient;
address constant burnAddress = 0x0;
//Note that these will track, but not influence the BOP logic.
uint public amountDeposited;
uint public amountBurned;
uint public amountReleased;
//payerString and recipientString enable rudimentary communication/publishing.
//Although the two parties might quickly move to another medium with better privacy or convenience,
//beginning with this is nice because it's already trustless/transparent/signed/pseudonymous/etc.
string public payerString;
string public recipientString;
//Amount of ether a prospective recipient must pay to permanently become the recipient. See commit().
uint public commitThreshold;
//What if the payer falls off the face of the planet?
//A BOP is instantiated with a chosen defaultAction, which cannot be changed after instantiation.
enum DefaultAction {None, Release, Burn}
DefaultAction public defaultAction;
//if defaultAction != None, how long should we wait allowing the default action to be called?
uint public defaultTimeoutLength;
//Calculated from defaultTimeoutLength in commit(),
//and recaluclated whenever the payer (or possibly the recipient) calls delayDefaultAction()
uint public defaultTriggerTime;
//Most action happens in the Committed state.
enum State {Open, Committed, Expended}
State public state;
//Note that a BOP cannot go from Committed back to Open, but it can go from Expended back to Committed
//(this would retain the committed recipient). Search for Expended and Unexpended events to see how this works.
modifier inState(State s) { require(s == state); _; }
modifier onlyPayer() { require(msg.sender == payer); _; }
modifier onlyRecipient() { require(msg.sender == recipient); _; }
modifier onlyPayerOrRecipient() { require((msg.sender == payer) || (msg.sender == recipient)); _; }
event FundsAdded(uint amount);//The payer has added funds to the BOP.
event PayerStringUpdated(string newPayerString);
event RecipientStringUpdated(string newRecipientString);
event FundsRecovered();
event Committed(address recipient);
event FundsBurned(uint amount);
event FundsReleased(uint amount);
event Expended();
event Unexpended();
event DefaultActionDelayed();
event DefaultActionCalled();
function BurnableOpenPayment(address _payer, uint _commitThreshold, DefaultAction _defaultAction, uint _defaultTimeoutLength, string _payerString)
public
payable {
if (msg.value > 0) {
FundsAdded(msg.value);
amountDeposited += msg.value;
}
state = State.Open;
payer = _payer;
commitThreshold = _commitThreshold;
defaultAction = _defaultAction;
if (defaultAction != DefaultAction.None)
defaultTimeoutLength = _defaultTimeoutLength;
payerString = _payerString;
}
function getFullState()
public
constant
returns (State, string, address, string, uint, uint, uint, uint) {
return (state, payerString, recipient, recipientString, amountDeposited, amountBurned, amountReleased, defaultTriggerTime);
}
function addFunds()
public
onlyPayer()
payable {
require(msg.value > 0);
FundsAdded(msg.value);
amountDeposited += msg.value;
if (state == State.Expended) {
state = State.Committed;
Unexpended();
}
}
function recoverFunds()
public
onlyPayer()
inState(State.Open)
{
FundsRecovered();
selfdestruct(payer);
}
function commit()
public
inState(State.Open)
payable
{
require(msg.value >= commitThreshold);
if (msg.value > 0) {
FundsAdded(msg.value);
amountDeposited += msg.value;
}
recipient = msg.sender;
state = State.Committed;
Committed(recipient);
if (defaultAction != DefaultAction.None) {
defaultTriggerTime = now + defaultTimeoutLength;
}
}
function internalBurn(uint amount)
private
inState(State.Committed)
{
burnAddress.transfer(amount);
amountBurned += amount;
FundsBurned(amount);
if (this.balance == 0) {
state = State.Expended;
Expended();
}
}
function burn(uint amount)
public
inState(State.Committed)
onlyPayer()
{
internalBurn(amount);
}
function internalRelease(uint amount)
private
inState(State.Committed)
{
recipient.transfer(amount);
amountReleased += amount;
FundsReleased(amount);
if (this.balance == 0) {
state = State.Expended;
Expended();
}
}
function release(uint amount)
public
inState(State.Committed)
onlyPayer()
{
internalRelease(amount);
}
function setPayerString(string _string)
public
onlyPayer()
{
payerString = _string;
PayerStringUpdated(payerString);
}
function setRecipientString(string _string)
public
onlyRecipient()
{
recipientString = _string;
RecipientStringUpdated(recipientString);
}
function delayDefaultAction()
public
onlyPayerOrRecipient()
inState(State.Committed)
{
require(defaultAction != DefaultAction.None);
defaultTriggerTime = now + defaultTimeoutLength;
DefaultActionDelayed();
}
function callDefaultAction()
public
onlyPayerOrRecipient()
inState(State.Committed)
{
require(defaultAction != DefaultAction.None);
require(now >= defaultTriggerTime);
if (defaultAction == DefaultAction.Burn) {
internalBurn(this.balance);
}
else if (defaultAction == DefaultAction.Release) {
internalRelease(this.balance);
}
DefaultActionCalled();
}
}
contract BurnableOpenPaymentFactory {
event NewBOP(address newBOPAddress, address payer, uint commitThreshold, BurnableOpenPayment.DefaultAction defaultAction, uint defaultTimeoutLength, string initialPayerString);
function newBurnableOpenPayment(address payer, uint commitThreshold, BurnableOpenPayment.DefaultAction defaultAction, uint defaultTimeoutLength, string initialPayerString)
public
payable
returns (address) {
//pass along any ether to the constructor
address newBOPAddr = (new BurnableOpenPayment).value(msg.value)(payer, commitThreshold, defaultAction, defaultTimeoutLength, initialPayerString);
NewBOP(newBOPAddr, payer, commitThreshold, defaultAction, defaultTimeoutLength, initialPayerString);
return newBOPAddr;
}
}