-
Notifications
You must be signed in to change notification settings - Fork 8
/
dlc_test.go
274 lines (215 loc) · 7.62 KB
/
dlc_test.go
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
package integration
import (
"math"
"math/rand"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/p2pderivatives/dlc/internal/oracle"
"github.com/p2pderivatives/dlc/internal/rpc"
"github.com/p2pderivatives/dlc/pkg/dlc"
"github.com/p2pderivatives/dlc/pkg/utils"
"github.com/stretchr/testify/assert"
)
// Senario: There's a `lottery` oracle who publishes a random digit number everyday,
// and 2 parties bet on tomorrow's numbers randomly
func TestContractorMakeAndExecuteDLC(t *testing.T) {
// Given an oracle "Olivia"
nDigit := 2
olivia, _ := newOracle("Olivia", nDigit)
// And next announcement time
fixingTime := nextLotteryAnnouncement()
// And a contractor "Alice"
alice, _ := newContractor("Alice")
balanceA := btcutil.Amount(2 * btcutil.SatoshiPerBitcoin)
contratorHasBalance(t, alice, balanceA)
// And a contractor "Bob"
bob, _ := newContractor("Bob")
balanceB := btcutil.Amount(2 * btcutil.SatoshiPerBitcoin)
contratorHasBalance(t, bob, balanceB)
// -- Making DLC --
// When Alice and Bob bet on tomorrow's lottery
contractorsBetOnLottery(
t, alice, bob, nDigit, fixingTime, blockHeightAfterDays(2))
// And Alice offers a DLC to Bob
contractorGetCommitmentsFromOracle(t, alice, olivia)
contractorOfferCounterparty(t, alice, bob)
// And Bob accepts the offer
contractorGetCommitmentsFromOracle(t, bob, olivia)
contractorAcceptOffer(t, bob, alice)
// And Alice signs all txs and send the signs to Bob
contractorSignAllTxs(t, alice, bob)
// And Bob sends fund tx to the network
contractorSendFundTx(t, bob)
// Then Alice and Bob should have remaining balance after funding
contractorShouldHaveBalanceAfterFunding(t, alice, balanceA)
contractorShouldHaveBalanceAfterFunding(t, bob, balanceB)
// -- Executing Contract --
// When Olivia fixes a number
oracleFixLottery(t, olivia, nDigit, fixingTime)
// And Alice and Bob fixe a deal using Olivia's messages and sign
contractorFixLotteryDeal(t, alice, olivia, nDigit)
contractorFixLotteryDeal(t, bob, olivia, nDigit)
// And Alice sends CETx and closing tx
contractorExecuteContract(t, alice)
// Alice and Bob should receive funds accoding to the fixed deal
contractorShouldReceiveFundsByFixedDeal(t, alice, balanceA)
contractorShouldReceiveFundsByFixedDeal(t, bob, balanceB)
}
// Senario: either party refunds after contract period
func TestContractorRefundDLC(t *testing.T) {
// Given an oracle "Olivia"
nDigit := 2
olivia, _ := newOracle("Olivia", nDigit)
// And next announcement time
fixingTime := nextLotteryAnnouncement()
// And a contractor "Alice"
alice, _ := newContractor("Alice")
balanceA := btcutil.Amount(2 * btcutil.SatoshiPerBitcoin)
contratorHasBalance(t, alice, balanceA)
// And a contractor "Bob"
bob, _ := newContractor("Bob")
balanceB := btcutil.Amount(2 * btcutil.SatoshiPerBitcoin)
contratorHasBalance(t, bob, balanceB)
// -- Making DLC --
// When Alice and Bob bet on tomorrow's lottery
refundUnlockAt := blockHeightAfterDays(2) // block height of 2 days later
contractorsBetOnLottery(t, alice, bob, nDigit, fixingTime, refundUnlockAt)
// And Alice offers a DLC to Bob
contractorGetCommitmentsFromOracle(t, alice, olivia)
contractorOfferCounterparty(t, alice, bob)
// And Bob accepts the offer
contractorGetCommitmentsFromOracle(t, bob, olivia)
contractorAcceptOffer(t, bob, alice)
// And Alice signs all txs and send the signs to Bob
contractorSignAllTxs(t, alice, bob)
// And Bob sends fund tx to the network
contractorSendFundTx(t, bob)
// Then Alice and Bob should have remaining balance after funding
contractorShouldHaveBalanceAfterFunding(t, alice, balanceA)
contractorShouldHaveBalanceAfterFunding(t, bob, balanceB)
// -- Refunding --
// When Olivia fixes an invalid messages
oracleFixInvalidLottery(t, olivia, nDigit, fixingTime)
// Then Alice and Bob cannot fix deal with invalid messages
contractorCannotFixLotteryDeal(t, alice, olivia, nDigit)
contractorCannotFixLotteryDeal(t, bob, olivia, nDigit)
// And Alice and Bob cannot refund before the unlock time
contractorCannotRefund(t, alice)
contractorCannotRefund(t, bob)
// Given after the unlock time
waitUntil(t, refundUnlockAt)
// When alice refund
contractorRefund(t, alice)
// Then Alice and Bob should receive funds accoding to the fixed deal
contractorShouldReceiveRefund(t, alice, balanceA)
contractorShouldReceiveRefund(t, bob, balanceB)
}
// nextLotteryAnnouncement returns time of tomorrow noon
func nextLotteryAnnouncement() time.Time {
tomorrow := time.Now().AddDate(0, 0, 1)
year, month, day := tomorrow.Date()
return time.Date(year, month, day, 12, 0, 0, 0, tomorrow.Location())
}
func blockHeightAfterDays(days int) uint32 {
unlockAt := time.Now().AddDate(0, 0, days)
d := time.Until(unlockAt)
timePerBlock := chaincfg.RegressionNetParams.TargetTimePerBlock
blocks := uint32(d / timePerBlock)
curHeight, _ := rpc.GetBlockCount()
return uint32(curHeight) + blocks
}
// The both contractors agree on random n-digit number lottery
// by creating all patterns randomly
func contractorsBetOnLottery(
t *testing.T, c1, c2 *Contractor, nDigit int, fixingTime time.Time, refundUnlockAt uint32) {
var onebtc btcutil.Amount = 1 * btcutil.SatoshiPerBitcoin
famta, famtb := onebtc, onebtc
deals := randomDealsForAllDigitPatterns(nDigit, int(famta+famtb))
net := &chaincfg.RegressionNetParams
conds, err := dlc.NewConditions(
net, fixingTime, famta, famtb, 1, 1, refundUnlockAt, deals, nil)
assert.NoError(t, err)
c1.createDLCBuilder(conds, dlc.FirstParty)
c2.createDLCBuilder(conds, dlc.SecondParty)
}
func randomDealsForAllDigitPatterns(nDigit, famt int) []*dlc.Deal {
var deals []*dlc.Deal
for d := 0; d < int(math.Pow10(nDigit)); d++ {
// randomly decide deals
damta, damtb := randomDealAmts(famt)
deal := dlc.NewDeal(damta, damtb, nDigitToBytes(d, nDigit))
deals = append(deals, deal)
}
return deals
}
func randomDealAmts(famt int) (btcutil.Amount, btcutil.Amount) {
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
a := r.Intn(famt + 1)
b := famt - a
return utils.ItoAmt(a), utils.ItoAmt(b)
}
// convert a n-digit number to byte
// e.g. 123 -> [][]byte{{1}, {2}, {3}}
func nDigitToBytes(d int, n int) [][]byte {
b := make([][]byte, n)
for i := 0; i < n; i++ {
b[i] = []byte{byte(d % 10)}
d = d / 10
}
return b
}
func oracleFixLottery(
t *testing.T, o *oracle.Oracle, n int, ftime time.Time) {
msgs := nDigitToBytes(randomNumber(n), n)
err := o.FixMsgs(ftime, msgs)
assert.NoError(t, err)
}
func randomNumber(nDigit int) (d int) {
for i := 0; i < nDigit; i++ {
d = d + randomDigit()*int(math.Pow10(i))
}
return
}
func randomDigit() int {
s := rand.NewSource(time.Now().UnixNano())
return rand.New(s).Intn(10)
}
func contractorFixLotteryDeal(
t *testing.T, c *Contractor, o *oracle.Oracle, n int) {
// use all messages that oracle publishes
idxs := []int{}
for i := 0; i < n; i++ {
idxs = append(idxs, i)
}
contractorFixDeal(t, c, o, idxs)
}
func contractorCannotFixLotteryDeal(
t *testing.T, c *Contractor, o *oracle.Oracle, n int) {
// use all messages that oracle publishes
idxs := []int{}
for i := 0; i < n; i++ {
idxs = append(idxs, i)
}
contractorCannotFixDeal(t, c, o, idxs)
}
func oracleFixInvalidLottery(
t *testing.T, o *oracle.Oracle, n int, ftime time.Time) {
msgs := randomStringMsgs(n)
err := o.FixMsgs(ftime, msgs)
assert.NoError(t, err)
}
func randomStringMsgs(n int) [][]byte {
msgs := make([][]byte, n)
for i := 0; i < n; i++ {
msgs[i] = []byte(string(randomChar()))
}
return msgs
}
func randomChar() rune {
var runes = []rune("abcdefghijklmnopqrstuvwxyz")
s := rand.NewSource(time.Now().UnixNano())
return runes[rand.New(s).Intn(len(runes))]
}