zold 0.8 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +1 -0
- data/README.md +16 -8
- data/Rakefile +1 -1
- data/bin/zold +1 -1
- data/lib/zold/amount.rb +11 -3
- data/lib/zold/commands/calculate.rb +5 -0
- data/lib/zold/commands/merge.rb +2 -2
- data/lib/zold/commands/pay.rb +12 -3
- data/lib/zold/commands/taxes.rb +3 -3
- data/lib/zold/node/emission.rb +3 -2
- data/lib/zold/node/entrance.rb +3 -3
- data/lib/zold/patch.rb +2 -1
- data/lib/zold/prefixes.rb +1 -1
- data/lib/zold/score.rb +3 -0
- data/lib/zold/tax.rb +22 -6
- data/lib/zold/txn.rb +4 -3
- data/lib/zold/version.rb +1 -1
- data/lib/zold/wallet.rb +7 -1
- data/test/commands/test_diff.rb +13 -15
- data/test/commands/test_merge.rb +25 -34
- data/test/commands/test_node.rb +2 -2
- data/test/commands/test_pay.rb +5 -10
- data/test/commands/test_taxes.rb +1 -1
- data/test/fake_home.rb +59 -0
- data/test/node/test_emission.rb +4 -6
- data/test/test_amount.rb +2 -2
- data/test/test_patch.rb +7 -9
- data/test/test_prefixes.rb +3 -4
- data/test/test_tax.rb +49 -6
- data/test/test_wallet.rb +9 -34
- data/wp/.gitignore +2 -1
- data/wp/Makefile +6 -1
- data/wp/main.bib +99 -0
- data/wp/wp.pdf +0 -0
- data/wp/wp.tex +296 -145
- data/zold.gemspec +1 -1
- metadata +9 -3
data/test/fake_home.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright (c) 2018 Yegor Bugayenko
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the 'Software'), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
11
|
+
# copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
# SOFTWARE.
|
20
|
+
|
21
|
+
require 'tmpdir'
|
22
|
+
require_relative '../lib/zold/id'
|
23
|
+
require_relative '../lib/zold/wallet'
|
24
|
+
require_relative '../lib/zold/key'
|
25
|
+
|
26
|
+
# Fake home dir.
|
27
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
28
|
+
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
29
|
+
# License:: MIT
|
30
|
+
class FakeHome
|
31
|
+
def initialize(dir = Dir.pwd)
|
32
|
+
@dir = dir
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
Dir.mktmpdir 'test' do |dir|
|
37
|
+
yield FakeHome.new(dir)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def wallets
|
42
|
+
Zold::Wallets.new(@dir)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_wallet
|
46
|
+
id = Zold::Id.new
|
47
|
+
wallet = Zold::Wallet.new(File.join(@dir, id.to_s))
|
48
|
+
wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
49
|
+
wallet
|
50
|
+
end
|
51
|
+
|
52
|
+
def copies(wallet)
|
53
|
+
Zold::Copies.new(File.join(@dir, "copies/#{wallet.id}"))
|
54
|
+
end
|
55
|
+
|
56
|
+
def remotes
|
57
|
+
Zold::Remotes.new(File.join(@dir, 'secrets/remotes'))
|
58
|
+
end
|
59
|
+
end
|
data/test/node/test_emission.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
21
|
require 'minitest/autorun'
|
22
|
-
|
22
|
+
require_relative '../fake_home'
|
23
23
|
require_relative '../test__helper'
|
24
24
|
require_relative '../../lib/zold/node/emission'
|
25
25
|
require_relative '../../lib/zold/amount'
|
@@ -27,13 +27,11 @@ require_relative '../../lib/zold/amount'
|
|
27
27
|
class EmissionTest < Minitest::Test
|
28
28
|
def test_emission
|
29
29
|
(1..10).each do |year|
|
30
|
-
|
31
|
-
|
32
|
-
wallet = Zold::Wallet.new(File.join(dir, id.to_s))
|
33
|
-
wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
30
|
+
FakeHome.new.run do |home|
|
31
|
+
wallet = home.create_wallet
|
34
32
|
wallet.add(
|
35
33
|
Zold::Txn.new(
|
36
|
-
1, Time.now - 24 * 365 * year,
|
34
|
+
1, Time.now - 60 * 24 * 365 * year,
|
37
35
|
Zold::Amount.new(zld: 39.99),
|
38
36
|
'NOPREFIX', Zold::Id::ROOT, '-'
|
39
37
|
)
|
data/test/test_amount.rb
CHANGED
@@ -38,8 +38,8 @@ class TestAmount < Minitest::Test
|
|
38
38
|
def test_parses_coins
|
39
39
|
amount = Zold::Amount.new(coins: 900_000_000)
|
40
40
|
assert(
|
41
|
-
amount.to_s.include?('
|
42
|
-
"#{amount} is not equal to '
|
41
|
+
amount.to_s.include?('0.21ZLD'),
|
42
|
+
"#{amount} is not equal to '0.21ZLD'"
|
43
43
|
)
|
44
44
|
end
|
45
45
|
|
data/test/test_patch.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
21
|
require 'minitest/autorun'
|
22
|
-
|
22
|
+
require_relative 'fake_home'
|
23
23
|
require_relative '../lib/zold/key'
|
24
24
|
require_relative '../lib/zold/id'
|
25
25
|
require_relative '../lib/zold/wallet'
|
@@ -32,21 +32,19 @@ require_relative '../lib/zold/patch'
|
|
32
32
|
# License:: MIT
|
33
33
|
class TestPatch < Minitest::Test
|
34
34
|
def test_builds_patch
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
second = Zold::Wallet.new(File.join(dir, 'second'))
|
40
|
-
third = Zold::Wallet.new(File.join(dir, 'third'))
|
41
|
-
first.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
35
|
+
FakeHome.new.run do |home|
|
36
|
+
first = home.create_wallet
|
37
|
+
second = home.create_wallet
|
38
|
+
third = home.create_wallet
|
42
39
|
File.write(second.path, File.read(first.path))
|
40
|
+
key = Zold::Key.new(file: 'fixtures/id_rsa')
|
43
41
|
first.sub(Zold::Amount.new(zld: 39.0), "NOPREFIX@#{Zold::Id.new}", key)
|
44
42
|
first.sub(Zold::Amount.new(zld: 11.0), "NOPREFIX@#{Zold::Id.new}", key)
|
45
43
|
first.sub(Zold::Amount.new(zld: 3.0), "NOPREFIX@#{Zold::Id.new}", key)
|
46
44
|
second.sub(Zold::Amount.new(zld: 44.0), "NOPREFIX@#{Zold::Id.new}", key)
|
47
45
|
File.write(third.path, File.read(first.path))
|
48
46
|
t = third.sub(Zold::Amount.new(zld: 10.0), "NOPREFIX@#{Zold::Id.new}", key)
|
49
|
-
third.add(t.inverse(id))
|
47
|
+
third.add(t.inverse(first.id))
|
50
48
|
patch = Zold::Patch.new
|
51
49
|
patch.start(first)
|
52
50
|
patch.join(second)
|
data/test/test_prefixes.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
require 'minitest/autorun'
|
22
22
|
require 'tmpdir'
|
23
|
+
require_relative 'fake_home'
|
23
24
|
require_relative '../lib/zold/key'
|
24
25
|
require_relative '../lib/zold/id'
|
25
26
|
require_relative '../lib/zold/wallet'
|
@@ -31,10 +32,8 @@ require_relative '../lib/zold/prefixes'
|
|
31
32
|
# License:: MIT
|
32
33
|
class TestPrefixes < Minitest::Test
|
33
34
|
def test_creates_and_validates
|
34
|
-
|
35
|
-
|
36
|
-
wallet = Zold::Wallet.new(File.join(dir, id.to_s))
|
37
|
-
wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
35
|
+
FakeHome.new.run do |home|
|
36
|
+
wallet = home.create_wallet
|
38
37
|
prefixes = Zold::Prefixes.new(wallet)
|
39
38
|
(8..32).each do |len|
|
40
39
|
prefix = prefixes.create(len)
|
data/test/test_tax.rb
CHANGED
@@ -19,14 +19,16 @@
|
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
21
|
require 'minitest/autorun'
|
22
|
-
require 'tmpdir'
|
23
22
|
require 'time'
|
23
|
+
require_relative 'fake_home'
|
24
24
|
require_relative '../lib/zold/id'
|
25
25
|
require_relative '../lib/zold/txn'
|
26
26
|
require_relative '../lib/zold/wallet'
|
27
27
|
require_relative '../lib/zold/tax'
|
28
28
|
require_relative '../lib/zold/key'
|
29
29
|
require_relative '../lib/zold/amount'
|
30
|
+
require_relative '../lib/zold/prefixes'
|
31
|
+
require_relative '../lib/zold/score'
|
30
32
|
|
31
33
|
# Tax test.
|
32
34
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
@@ -34,10 +36,8 @@ require_relative '../lib/zold/amount'
|
|
34
36
|
# License:: MIT
|
35
37
|
class TestTax < Minitest::Test
|
36
38
|
def test_calculates_tax_for_one_year
|
37
|
-
|
38
|
-
|
39
|
-
wallet = Zold::Wallet.new(File.join(dir, id.to_s))
|
40
|
-
wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
39
|
+
FakeHome.new.run do |home|
|
40
|
+
wallet = home.create_wallet
|
41
41
|
wallet.add(
|
42
42
|
Zold::Txn.new(
|
43
43
|
1,
|
@@ -47,7 +47,50 @@ class TestTax < Minitest::Test
|
|
47
47
|
)
|
48
48
|
)
|
49
49
|
tax = Zold::Tax.new(wallet)
|
50
|
-
|
50
|
+
assert(tax.debt > Zold::Amount.new(coins: 16_775_000))
|
51
|
+
assert(tax.debt < Zold::Amount.new(coins: 16_775_999))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_checks_existence_of_duplicates
|
56
|
+
FakeHome.new.run do |home|
|
57
|
+
wallet = home.create_wallet
|
58
|
+
wallet.add(
|
59
|
+
Zold::Txn.new(
|
60
|
+
1,
|
61
|
+
Time.now - 24 * 60 * 365,
|
62
|
+
Zold::Amount.new(zld: 19.99),
|
63
|
+
'NOPREFIX', Zold::Id.new, '-'
|
64
|
+
)
|
65
|
+
)
|
66
|
+
target = home.create_wallet
|
67
|
+
invoice = "#{Zold::Prefixes.new(target).create}@#{target.id}"
|
68
|
+
tax = Zold::Tax.new(wallet)
|
69
|
+
score = Zold::Score.new(
|
70
|
+
Time.now, 'localhost', 80, invoice,
|
71
|
+
%w[A B C D E F G H I J K L M N O P Q R S T U V]
|
72
|
+
)
|
73
|
+
tax.pay(Zold::Key.new(file: 'fixtures/id_rsa'), score)
|
74
|
+
assert(
|
75
|
+
tax.exists?(
|
76
|
+
Zold::Txn.new(
|
77
|
+
2,
|
78
|
+
Time.now,
|
79
|
+
Zold::Amount.new(zld: 10.99),
|
80
|
+
'AAPREFIX', target.id, tax.details(score)
|
81
|
+
)
|
82
|
+
)
|
83
|
+
)
|
84
|
+
assert(
|
85
|
+
!tax.exists?(
|
86
|
+
Zold::Txn.new(
|
87
|
+
2,
|
88
|
+
Time.now,
|
89
|
+
Zold::Amount.new(zld: 10.99),
|
90
|
+
'NOPREFIX', target.id, '-'
|
91
|
+
)
|
92
|
+
)
|
93
|
+
)
|
51
94
|
end
|
52
95
|
end
|
53
96
|
end
|
data/test/test_wallet.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
require 'minitest/autorun'
|
22
22
|
require 'tmpdir'
|
23
|
+
require_relative 'fake_home'
|
23
24
|
require_relative '../lib/zold/key'
|
24
25
|
require_relative '../lib/zold/id'
|
25
26
|
require_relative '../lib/zold/wallet'
|
@@ -33,8 +34,8 @@ require_relative '../lib/zold/commands/pay'
|
|
33
34
|
# License:: MIT
|
34
35
|
class TestWallet < Minitest::Test
|
35
36
|
def test_adds_transaction
|
36
|
-
|
37
|
-
wallet =
|
37
|
+
FakeHome.new.run do |home|
|
38
|
+
wallet = home.create_wallet
|
38
39
|
amount = Zold::Amount.new(zld: 39.99)
|
39
40
|
key = Zold::Key.new(file: 'fixtures/id_rsa')
|
40
41
|
wallet.sub(amount, "NOPREFIX@#{Zold::Id.new}", key)
|
@@ -48,8 +49,8 @@ class TestWallet < Minitest::Test
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def test_adds_transaction_and_reads_back
|
51
|
-
|
52
|
-
wallet =
|
52
|
+
FakeHome.new.run do |home|
|
53
|
+
wallet = home.create_wallet
|
53
54
|
amount = Zold::Amount.new(zld: 39.99)
|
54
55
|
key = Zold::Key.new(file: 'fixtures/id_rsa')
|
55
56
|
txn = wallet.sub(amount, "NOPREFIX@#{Zold::Id.new}", key)
|
@@ -58,25 +59,9 @@ class TestWallet < Minitest::Test
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
|
-
def test_initializes_it
|
62
|
-
Dir.mktmpdir 'test' do |dir|
|
63
|
-
pkey = Zold::Key.new(file: File.join(Dir.pwd, 'fixtures/id_rsa.pub'))
|
64
|
-
Dir.chdir(dir) do
|
65
|
-
file = File.join(dir, 'source')
|
66
|
-
wallet = Zold::Wallet.new(file)
|
67
|
-
id = Zold::Id.new.to_s
|
68
|
-
wallet.init(id, pkey)
|
69
|
-
assert(
|
70
|
-
wallet.id == id,
|
71
|
-
"#{wallet.id} is not equal to #{id}"
|
72
|
-
)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
62
|
def test_iterates_income_transactions
|
78
|
-
|
79
|
-
wallet =
|
63
|
+
FakeHome.new.run do |home|
|
64
|
+
wallet = home.create_wallet
|
80
65
|
wallet.add(
|
81
66
|
Zold::Txn.new(
|
82
67
|
1, Time.now, Zold::Amount.new(zld: 39.99),
|
@@ -94,19 +79,9 @@ class TestWallet < Minitest::Test
|
|
94
79
|
sum += t.amount
|
95
80
|
end
|
96
81
|
assert(
|
97
|
-
sum == Zold::Amount.new(coins:
|
98
|
-
"#{sum} is not equal to #{Zold::Amount.new(zld: 54.94)}"
|
82
|
+
sum == Zold::Amount.new(coins: 235_965_503_242),
|
83
|
+
"#{sum} (#{sum.to_i}) is not equal to #{Zold::Amount.new(zld: 54.94)}"
|
99
84
|
)
|
100
85
|
end
|
101
86
|
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def wallet(dir)
|
106
|
-
id = Zold::Id.new
|
107
|
-
file = File.join(dir, id.to_s)
|
108
|
-
wallet = Zold::Wallet.new(file)
|
109
|
-
wallet.init(id, Zold::Key.new(file: 'fixtures/id_rsa.pub'))
|
110
|
-
wallet
|
111
|
-
end
|
112
87
|
end
|
data/wp/.gitignore
CHANGED
data/wp/Makefile
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
OPTS=-shell-escape -halt-on-error -interaction=errorstopmode -output-directory=.
|
2
2
|
|
3
3
|
wp.pdf: wp.tex
|
4
|
-
pdflatex ${OPTS} wp.tex
|
4
|
+
pdflatex ${OPTS} wp.tex > /dev/null
|
5
|
+
biber wp > /dev/null
|
6
|
+
pdflatex ${OPTS} wp.tex > /dev/null
|
7
|
+
grep 'LaTeX Warning' wp.log ; if [ $$? -eq 0 ]; then cat wp.log; exit -1; fi
|
8
|
+
grep 'Overfull ' wp.log ; if [ $$? -eq 0 ]; then cat wp.log; exit -1; fi
|
9
|
+
grep 'Underfull ' wp.log ; if [ $$? -eq 0 ]; then cat wp.log; exit -1; fi
|
5
10
|
|
6
11
|
clean:
|
7
12
|
rm -rf wp.log wp.pdf wp.out wp.aux
|
data/wp/main.bib
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
@article{nakamoto2008,
|
2
|
+
title={Bitcoin: A Peer-to-peer Electronic Cash System},
|
3
|
+
author={Nakamoto, Satoshi},
|
4
|
+
year={2008}
|
5
|
+
}
|
6
|
+
|
7
|
+
@article{buterin2013,
|
8
|
+
author={Vitalik Buterin},
|
9
|
+
title={Ethereum: A Next-Generation Smart Contract and Decentralized Application Platform},
|
10
|
+
year={2013},
|
11
|
+
url={https://github.com/ethereum/wiki/wiki/White-Paper}
|
12
|
+
}
|
13
|
+
|
14
|
+
@article{popov2017,
|
15
|
+
title={The Tangle},
|
16
|
+
author={Serguei Popov},
|
17
|
+
year={2017},
|
18
|
+
url={https://iotatoken.com/IOTA_Whitepaper.pdf}
|
19
|
+
}
|
20
|
+
|
21
|
+
@inproceedings{kaskaloglu2014,
|
22
|
+
title={Near Zero Bitcoin Transaction Fees Cannot Last Forever},
|
23
|
+
author={Kaskaloglu, Kerem},
|
24
|
+
booktitle={The International Conference on Digital Security and Forensics},
|
25
|
+
pages={91--99},
|
26
|
+
year={2014},
|
27
|
+
organization={The Society of Digital Information and Wireless Communication}
|
28
|
+
}
|
29
|
+
|
30
|
+
@article{van2014,
|
31
|
+
title={Why Bitcoin Has Value},
|
32
|
+
author={Van Alstyne, Marshall},
|
33
|
+
journal={Communications of the ACM},
|
34
|
+
volume={57},
|
35
|
+
number={5},
|
36
|
+
pages={30--32},
|
37
|
+
year={2014},
|
38
|
+
publisher={ACM}
|
39
|
+
}
|
40
|
+
|
41
|
+
@article{andreessen2014,
|
42
|
+
title={Why Bitcoin Matters},
|
43
|
+
author={Andreessen, Marc},
|
44
|
+
journal={New York Times},
|
45
|
+
volume={21},
|
46
|
+
year={2014}
|
47
|
+
}
|
48
|
+
|
49
|
+
@article{pilkington2016,
|
50
|
+
title={Blockchain Technology: Principles and Applications},
|
51
|
+
author={Pilkington, Marc},
|
52
|
+
journal={Research Handbook on Digital Transformations},
|
53
|
+
pages={225},
|
54
|
+
year={2016},
|
55
|
+
publisher={Edward Elgar Publishing}
|
56
|
+
}
|
57
|
+
|
58
|
+
@inproceedings{karame2012,
|
59
|
+
title={Double-spending Fast Payments in Bitcoin},
|
60
|
+
author={Karame, Ghassan O. and Androulaki, Elli and Capkun, Srdjan},
|
61
|
+
booktitle={Proceedings of the 2012 ACM conference on Computer and communications security},
|
62
|
+
pages={906--917},
|
63
|
+
year={2012},
|
64
|
+
organization={ACM}
|
65
|
+
}
|
66
|
+
|
67
|
+
@inproceedings{everaere2010,
|
68
|
+
title={Double Spending Protection for E-cash Based on Risk Management},
|
69
|
+
author={Everaere, Patricia and Simplot-Ryl, Isabelle and Traor{\'e}, Issa},
|
70
|
+
booktitle={International Conference on Information Security},
|
71
|
+
pages={394--408},
|
72
|
+
year={2010},
|
73
|
+
organization={Springer}
|
74
|
+
}
|
75
|
+
|
76
|
+
@techreport{boyen2016,
|
77
|
+
title={Blockchain-free Cryptocurrencies: A Framework for Truly Decentralised Fast Transactions},
|
78
|
+
author={Boyen, Xavier and Carr, Christopher and Haines, Thomas},
|
79
|
+
year={2016},
|
80
|
+
institution={Cryptology ePrint Archive, Report 2016/871}
|
81
|
+
}
|
82
|
+
|
83
|
+
@inproceedings{moser2015,
|
84
|
+
title={Trends, Tips, Tolls: A Longitudinal Study of Bitcoin Transaction Fees},
|
85
|
+
author={M{\"o}ser, Malte and B{\"o}hme, Rainer},
|
86
|
+
booktitle={International Conference on Financial Cryptography and Data Security},
|
87
|
+
pages={19--33},
|
88
|
+
year={2015},
|
89
|
+
organization={Springer}
|
90
|
+
}
|
91
|
+
|
92
|
+
@article{kiayias2015,
|
93
|
+
title={Speed-Security Tradeoffs in Blockchain Protocols},
|
94
|
+
author={Kiayias, Aggelos and Panagiotakos, Giorgos},
|
95
|
+
journal={IACR Cryptology ePrint Archive},
|
96
|
+
volume={2015},
|
97
|
+
pages={1019},
|
98
|
+
year={2015}
|
99
|
+
}
|
data/wp/wp.pdf
ADDED
Binary file
|