erc20 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +16 -7
- data/Rakefile +0 -1
- data/lib/erc20/erc20.rb +1 -1
- data/lib/erc20/wallet.rb +99 -75
- data/test/erc20/test_fake_wallet.rb +1 -1
- data/test/erc20/test_wallet.rb +120 -20
- data/test/test__helper.rb +3 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01ef1fcd61cd21540d932e28c65ed8afa08891e0cc20b498202eb146809d4993
|
4
|
+
data.tar.gz: c8e4593fb968dcfbad8e27d24aa974a37946b444eb42b4022139c48c51ffc5ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38e4e84ea00f176c34a9f3f15ff0e77473f31fb29196b72d7e7b70af38e7aa8877015ff3249822b55665dfadac2cecb909a46d4c62554bcf5c85d8e1a1d30744
|
7
|
+
data.tar.gz: ffc78c5dfe3817bb59481f6e27e5e5fe7539ffca1e931fe8752fa31e98fee9e4c1880a2d3ce40496549c16b764524c0fad70c147d6f8a7467d8f1ba9c990d6dc
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
@@ -9,12 +9,13 @@ gemspec
|
|
9
9
|
gem 'backtrace', '>0', require: false
|
10
10
|
gem 'concurrent-ruby', '~>1.2', require: false
|
11
11
|
gem 'cucumber', '~>9.2', require: false
|
12
|
-
gem 'donce', '
|
12
|
+
gem 'donce', '>=0.2.3', require: false
|
13
13
|
gem 'faraday', '>0', require: false
|
14
14
|
gem 'loog', '>0', require: false
|
15
15
|
gem 'minitest', '~>5.25', require: false
|
16
16
|
gem 'minitest-reporters', '~>1.7', require: false
|
17
17
|
gem 'minitest-retry', '~>0.2', require: false
|
18
|
+
gem 'os', '>0', require: false
|
18
19
|
gem 'qbash', '>0', require: false
|
19
20
|
gem 'rake', '~>13.2', require: false
|
20
21
|
gem 'random-port', '>0', require: false
|
@@ -22,9 +23,9 @@ gem 'rubocop', '~>1.75', require: false
|
|
22
23
|
gem 'rubocop-minitest', '>0', require: false
|
23
24
|
gem 'rubocop-performance', '>0', require: false
|
24
25
|
gem 'rubocop-rake', '>0', require: false
|
25
|
-
gem 'rubocop-rspec', '>0', require: false
|
26
26
|
gem 'simplecov', '~>0.22', require: false
|
27
27
|
gem 'simplecov-cobertura', '~>2.1', require: false
|
28
28
|
gem 'threads', '~>0.4', require: false
|
29
29
|
gem 'typhoeus', '>0', require: false
|
30
|
+
gem 'webmock', '~>3.23', require: false
|
30
31
|
gem 'yard', '~>0.9', require: false
|
data/Gemfile.lock
CHANGED
@@ -12,6 +12,8 @@ PATH
|
|
12
12
|
GEM
|
13
13
|
remote: https://rubygems.org/
|
14
14
|
specs:
|
15
|
+
addressable (2.8.7)
|
16
|
+
public_suffix (>= 2.0.2, < 7.0)
|
15
17
|
ansi (1.5.0)
|
16
18
|
ast (2.4.3)
|
17
19
|
backtrace (0.4.0)
|
@@ -19,6 +21,9 @@ GEM
|
|
19
21
|
bigdecimal (3.1.9)
|
20
22
|
builder (3.3.0)
|
21
23
|
concurrent-ruby (1.3.5)
|
24
|
+
crack (1.0.0)
|
25
|
+
bigdecimal
|
26
|
+
rexml
|
22
27
|
cucumber (9.2.1)
|
23
28
|
builder (~> 3.2)
|
24
29
|
cucumber-ci-environment (> 9, < 11)
|
@@ -46,7 +51,7 @@ GEM
|
|
46
51
|
cucumber-tag-expressions (6.1.2)
|
47
52
|
diff-lcs (1.6.1)
|
48
53
|
docile (1.4.1)
|
49
|
-
donce (0.2.
|
54
|
+
donce (0.2.3)
|
50
55
|
backtrace (~> 0.3)
|
51
56
|
os (~> 1.1)
|
52
57
|
qbash (~> 0.3)
|
@@ -81,6 +86,7 @@ GEM
|
|
81
86
|
ffi (>= 1.15.5)
|
82
87
|
rake
|
83
88
|
forwardable (1.3.3)
|
89
|
+
hashdiff (1.1.2)
|
84
90
|
json (2.10.2)
|
85
91
|
jsonrpc-client (0.1.4)
|
86
92
|
faraday
|
@@ -113,6 +119,7 @@ GEM
|
|
113
119
|
racc
|
114
120
|
pkg-config (1.6.1)
|
115
121
|
prism (1.4.0)
|
122
|
+
public_suffix (6.0.1)
|
116
123
|
qbash (0.4.0)
|
117
124
|
backtrace (> 0)
|
118
125
|
elapsed (> 0)
|
@@ -129,7 +136,7 @@ GEM
|
|
129
136
|
rubyzip (~> 2.3)
|
130
137
|
regexp_parser (2.10.0)
|
131
138
|
rexml (3.4.1)
|
132
|
-
rubocop (1.75.
|
139
|
+
rubocop (1.75.3)
|
133
140
|
json (~> 2.3)
|
134
141
|
language_server-protocol (~> 3.17.0.2)
|
135
142
|
lint_roller (~> 1.1.0)
|
@@ -154,9 +161,6 @@ GEM
|
|
154
161
|
rubocop-rake (0.7.1)
|
155
162
|
lint_roller (~> 1.1)
|
156
163
|
rubocop (>= 1.72.1)
|
157
|
-
rubocop-rspec (3.6.0)
|
158
|
-
lint_roller (~> 1.1)
|
159
|
-
rubocop (~> 1.72, >= 1.72.1)
|
160
164
|
ruby-progressbar (1.13.0)
|
161
165
|
rubyzip (2.4.1)
|
162
166
|
scrypt (3.0.8)
|
@@ -184,6 +188,10 @@ GEM
|
|
184
188
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
185
189
|
unicode-emoji (4.0.4)
|
186
190
|
uri (1.0.3)
|
191
|
+
webmock (3.25.1)
|
192
|
+
addressable (>= 2.8.0)
|
193
|
+
crack (>= 0.3.2)
|
194
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
187
195
|
websocket-driver (0.7.7)
|
188
196
|
base64
|
189
197
|
websocket-extensions (>= 0.1.0)
|
@@ -203,13 +211,14 @@ DEPENDENCIES
|
|
203
211
|
backtrace (> 0)
|
204
212
|
concurrent-ruby (~> 1.2)
|
205
213
|
cucumber (~> 9.2)
|
206
|
-
donce (
|
214
|
+
donce (>= 0.2.3)
|
207
215
|
erc20!
|
208
216
|
faraday (> 0)
|
209
217
|
loog (> 0)
|
210
218
|
minitest (~> 5.25)
|
211
219
|
minitest-reporters (~> 1.7)
|
212
220
|
minitest-retry (~> 0.2)
|
221
|
+
os (> 0)
|
213
222
|
qbash (> 0)
|
214
223
|
rake (~> 13.2)
|
215
224
|
random-port (> 0)
|
@@ -217,11 +226,11 @@ DEPENDENCIES
|
|
217
226
|
rubocop-minitest (> 0)
|
218
227
|
rubocop-performance (> 0)
|
219
228
|
rubocop-rake (> 0)
|
220
|
-
rubocop-rspec (> 0)
|
221
229
|
simplecov (~> 0.22)
|
222
230
|
simplecov-cobertura (~> 2.1)
|
223
231
|
threads (~> 0.4)
|
224
232
|
typhoeus (> 0)
|
233
|
+
webmock (~> 3.23)
|
225
234
|
yard (~> 0.9)
|
226
235
|
|
227
236
|
BUNDLED WITH
|
data/Rakefile
CHANGED
data/lib/erc20/erc20.rb
CHANGED
data/lib/erc20/wallet.rb
CHANGED
@@ -121,7 +121,7 @@ class ERC20::Wallet
|
|
121
121
|
data = "0x#{func}000000000000000000000000#{address[2..].downcase}"
|
122
122
|
r = jsonrpc.eth_call({ to: @contract, data: data }, 'latest')
|
123
123
|
b = r[2..].to_i(16)
|
124
|
-
|
124
|
+
log_it(:debug, "The balance of #{address} is #{b} ERC20 tokens")
|
125
125
|
b
|
126
126
|
end
|
127
127
|
|
@@ -138,7 +138,7 @@ class ERC20::Wallet
|
|
138
138
|
raise 'Invalid format of the address' unless /^0x[0-9a-fA-F]{40}$/.match?(address)
|
139
139
|
r = jsonrpc.eth_getBalance(address, 'latest')
|
140
140
|
b = r[2..].to_i(16)
|
141
|
-
|
141
|
+
log_it(:debug, "The balance of #{address} is #{b} ETHs")
|
142
142
|
b
|
143
143
|
end
|
144
144
|
|
@@ -159,7 +159,7 @@ class ERC20::Wallet
|
|
159
159
|
raise "Amount (#{amount}) must be an Integer" unless amount.is_a?(Integer)
|
160
160
|
raise "Amount (#{amount}) must be a positive Integer" unless amount.positive?
|
161
161
|
gas = jsonrpc.eth_estimateGas({ from:, to: @contract, data: to_pay_data(to, amount) }, 'latest').to_i(16)
|
162
|
-
|
162
|
+
log_it(:debug, "It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
|
163
163
|
gas
|
164
164
|
end
|
165
165
|
|
@@ -177,7 +177,7 @@ class ERC20::Wallet
|
|
177
177
|
block = jsonrpc.eth_getBlockByNumber('latest', false)
|
178
178
|
raise "Can't get gas price, try again later" if block.nil?
|
179
179
|
gwei = block['baseFeePerGas'].to_i(16)
|
180
|
-
|
180
|
+
log_it(:debug, "The cost of one gas unit is #{gwei} gwei")
|
181
181
|
gwei
|
182
182
|
end
|
183
183
|
|
@@ -235,7 +235,7 @@ class ERC20::Wallet
|
|
235
235
|
hex = "0x#{tx.hex}"
|
236
236
|
jsonrpc.eth_sendRawTransaction(hex)
|
237
237
|
end
|
238
|
-
|
238
|
+
log_it(:debug, "Sent #{amount} ERC20 tokens from #{from} to #{address}: #{tnx}")
|
239
239
|
tnx.downcase
|
240
240
|
end
|
241
241
|
|
@@ -279,7 +279,7 @@ class ERC20::Wallet
|
|
279
279
|
hex = "0x#{tx.hex}"
|
280
280
|
jsonrpc.eth_sendRawTransaction(hex)
|
281
281
|
end
|
282
|
-
|
282
|
+
log_it(:debug, "Sent #{amount} ETHs from #{from} to #{address}: #{tnx}")
|
283
283
|
tnx.downcase
|
284
284
|
end
|
285
285
|
|
@@ -308,7 +308,7 @@ class ERC20::Wallet
|
|
308
308
|
# @param [Boolean] raw TRUE if you need to get JSON events as they arrive from Websockets
|
309
309
|
# @param [Integer] delay How many seconds to wait between +eth_subscribe+ calls
|
310
310
|
# @param [Integer] subscription_id Unique ID of the subscription
|
311
|
-
def accept(addresses, active = [], raw: false, delay: 1, subscription_id: rand(99_999))
|
311
|
+
def accept(addresses, active = [], raw: false, delay: 1, subscription_id: rand(99_999), &)
|
312
312
|
raise 'Addresses can\'t be nil' unless addresses
|
313
313
|
raise 'Addresses must respond to .to_a()' unless addresses.respond_to?(:to_a)
|
314
314
|
raise 'Active can\'t be nil' unless active
|
@@ -318,71 +318,27 @@ class ERC20::Wallet
|
|
318
318
|
raise 'Subscription ID must be an Integer' unless subscription_id.is_a?(Integer)
|
319
319
|
raise 'Subscription ID must be a positive Integer' unless subscription_id.positive?
|
320
320
|
EventMachine.run do
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
before = active.to_a
|
341
|
-
attempt.each do |a|
|
342
|
-
active.append(a) unless before.include?(a)
|
343
|
-
end
|
344
|
-
log.debug(
|
345
|
-
"Subscribed ##{subscription_id} to #{active.to_a.size} addresses at #{log_url}: " \
|
346
|
-
"#{active.to_a.map { |a| a[0..6] }.join(', ')}"
|
347
|
-
)
|
348
|
-
elsif data['method'] == 'eth_subscription' && data.dig('params', 'result')
|
349
|
-
event = data['params']['result']
|
350
|
-
if raw
|
351
|
-
log.debug("New event arrived from #{event['address']}")
|
352
|
-
else
|
353
|
-
event = {
|
354
|
-
amount: event['data'].to_i(16),
|
355
|
-
from: "0x#{event['topics'][1][26..].downcase}",
|
356
|
-
to: "0x#{event['topics'][2][26..].downcase}",
|
357
|
-
txn: event['transactionHash'].downcase
|
358
|
-
}
|
359
|
-
log.debug(
|
360
|
-
"Payment of #{event[:amount]} tokens arrived " \
|
361
|
-
"from #{event[:from]} to #{event[:to]} in #{event[:txn]}"
|
362
|
-
)
|
363
|
-
end
|
364
|
-
yield event
|
365
|
-
end
|
366
|
-
end
|
367
|
-
end
|
368
|
-
end
|
369
|
-
ws.on(:close) do
|
370
|
-
safe do
|
371
|
-
verbose do
|
372
|
-
log.debug("Disconnected from #{log_url}")
|
373
|
-
end
|
374
|
-
end
|
375
|
-
end
|
376
|
-
ws.on(:error) do |e|
|
377
|
-
safe do
|
378
|
-
verbose do
|
379
|
-
log.debug("Error at #{log_url}: #{e.message}")
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|
321
|
+
reaccept(addresses, active, raw:, delay:, subscription_id:, &)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
# @param [Array<String>] addresses Addresses to monitor
|
328
|
+
# @param [Array] active List of addresses that we are actually listening to
|
329
|
+
# @param [Boolean] raw TRUE if you need to get JSON events as they arrive from Websockets
|
330
|
+
# @param [Integer] delay How many seconds to wait between +eth_subscribe+ calls
|
331
|
+
# @param [Integer] subscription_id Unique ID of the subscription
|
332
|
+
# @return [Websocket]
|
333
|
+
def reaccept(addresses, active, raw:, delay:, subscription_id:, &)
|
334
|
+
u = url(http: false)
|
335
|
+
log_it(:debug, "Connecting ##{subscription_id} to #{u.hostname}:#{u.port}...")
|
336
|
+
contract = @contract
|
337
|
+
log_url = "ws#{@ssl ? 's' : ''}://#{u.hostname}:#{u.port}"
|
338
|
+
ws = Faye::WebSocket::Client.new(u.to_s, [], proxy: @proxy ? { origin: @proxy } : {}, ping: 60)
|
339
|
+
timer =
|
383
340
|
EventMachine.add_periodic_timer(delay) do
|
384
341
|
next if active.to_a.sort == addresses.to_a.sort
|
385
|
-
attempt = addresses.to_a
|
386
342
|
ws.send(
|
387
343
|
{
|
388
344
|
jsonrpc: '2.0',
|
@@ -401,16 +357,76 @@ class ERC20::Wallet
|
|
401
357
|
]
|
402
358
|
}.to_json
|
403
359
|
)
|
404
|
-
|
405
|
-
|
360
|
+
log_it(
|
361
|
+
:debug,
|
362
|
+
"Requested to subscribe ##{subscription_id} to #{addresses.to_a.size} addresses: " \
|
406
363
|
"#{addresses.to_a.map { |a| a[0..6] }.join(', ')}"
|
407
364
|
)
|
408
365
|
end
|
366
|
+
ws.on(:open) do
|
367
|
+
safe do
|
368
|
+
verbose do
|
369
|
+
log_it(:debug, "Connected ##{subscription_id} to #{log_url}")
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
ws.on(:message) do |msg|
|
374
|
+
safe do
|
375
|
+
verbose do
|
376
|
+
data = to_json(msg)
|
377
|
+
if data['id']
|
378
|
+
before = active.to_a.uniq
|
379
|
+
addresses.to_a.each do |a|
|
380
|
+
next if before.include?(a)
|
381
|
+
active.append(a)
|
382
|
+
end
|
383
|
+
log_it(
|
384
|
+
:debug,
|
385
|
+
"Subscribed ##{subscription_id} to #{active.to_a.size} addresses at #{log_url}: " \
|
386
|
+
"#{active.to_a.map { |a| a[0..6] }.join(', ')}"
|
387
|
+
)
|
388
|
+
elsif data['method'] == 'eth_subscription' && data.dig('params', 'result')
|
389
|
+
event = data['params']['result']
|
390
|
+
if raw
|
391
|
+
log_it(:debug, "New event arrived from #{event['address']}")
|
392
|
+
else
|
393
|
+
event = {
|
394
|
+
amount: event['data'].to_i(16),
|
395
|
+
from: "0x#{event['topics'][1][26..].downcase}",
|
396
|
+
to: "0x#{event['topics'][2][26..].downcase}",
|
397
|
+
txn: event['transactionHash'].downcase
|
398
|
+
}
|
399
|
+
log_it(
|
400
|
+
:debug,
|
401
|
+
"Payment of #{event[:amount]} tokens arrived at ##{subscription_id} " \
|
402
|
+
"from #{event[:from]} to #{event[:to]} in #{event[:txn]}"
|
403
|
+
)
|
404
|
+
end
|
405
|
+
yield event
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
ws.on(:close) do
|
411
|
+
safe do
|
412
|
+
verbose do
|
413
|
+
log_it(:debug, "Disconnected ##{subscription_id} from #{log_url}")
|
414
|
+
sleep(delay)
|
415
|
+
active.clear
|
416
|
+
timer.cancel
|
417
|
+
reaccept(addresses, active, raw:, delay:, subscription_id: subscription_id + 1, &)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
ws.on(:error) do |e|
|
422
|
+
safe do
|
423
|
+
verbose do
|
424
|
+
log_it(:debug, "Failed ##{subscription_id} at #{log_url}: #{e.message}")
|
425
|
+
end
|
426
|
+
end
|
409
427
|
end
|
410
428
|
end
|
411
429
|
|
412
|
-
private
|
413
|
-
|
414
430
|
def to_json(msg)
|
415
431
|
JSON.parse(msg.data)
|
416
432
|
rescue StandardError
|
@@ -420,7 +436,7 @@ class ERC20::Wallet
|
|
420
436
|
def verbose
|
421
437
|
yield
|
422
438
|
rescue StandardError => e
|
423
|
-
|
439
|
+
log_it(:error, Backtrace.new(e).to_s)
|
424
440
|
raise e
|
425
441
|
end
|
426
442
|
|
@@ -460,4 +476,12 @@ class ERC20::Wallet
|
|
460
476
|
amt_padded = ('0' * (64 - amt_hex.size)) + amt_hex
|
461
477
|
"0x#{func}#{to_padded}#{amt_padded}"
|
462
478
|
end
|
479
|
+
|
480
|
+
def log_it(method, msg)
|
481
|
+
if @log.respond_to?(method)
|
482
|
+
@log.__send__(method, msg)
|
483
|
+
elsif @log.respond_to?(:puts)
|
484
|
+
@log.puts(msg)
|
485
|
+
end
|
486
|
+
end
|
463
487
|
end
|
data/test/erc20/test_wallet.rb
CHANGED
@@ -7,7 +7,9 @@ require 'backtrace'
|
|
7
7
|
require 'donce'
|
8
8
|
require 'eth'
|
9
9
|
require 'faraday'
|
10
|
-
require '
|
10
|
+
require 'fileutils'
|
11
|
+
require 'json'
|
12
|
+
require 'os'
|
11
13
|
require 'random-port'
|
12
14
|
require 'shellwords'
|
13
15
|
require 'threads'
|
@@ -31,18 +33,21 @@ class TestWallet < ERC20::Test
|
|
31
33
|
WALTER = '91f9111b1744d55361e632771a4e53839e9442a9fef45febc0a5c838c686a15b'
|
32
34
|
|
33
35
|
def test_checks_balance_on_mainnet
|
36
|
+
WebMock.enable_net_connect!
|
34
37
|
b = mainnet.balance(STABLE)
|
35
38
|
refute_nil(b)
|
36
39
|
assert_equal(8_000_000, b) # this is $8 USDT
|
37
40
|
end
|
38
41
|
|
39
42
|
def test_checks_eth_balance_on_mainnet
|
43
|
+
WebMock.enable_net_connect!
|
40
44
|
b = mainnet.eth_balance(STABLE)
|
41
45
|
refute_nil(b)
|
42
46
|
assert_equal(4_200_000_000_000_000, b) # this is 0.0042 ETH
|
43
47
|
end
|
44
48
|
|
45
49
|
def test_checks_balance_of_absent_address
|
50
|
+
WebMock.enable_net_connect!
|
46
51
|
a = '0xEB2fE8872A6f1eDb70a2632Effffffffffffffff'
|
47
52
|
b = mainnet.balance(a)
|
48
53
|
refute_nil(b)
|
@@ -50,6 +55,7 @@ class TestWallet < ERC20::Test
|
|
50
55
|
end
|
51
56
|
|
52
57
|
def test_checks_gas_estimate_on_mainnet
|
58
|
+
WebMock.enable_net_connect!
|
53
59
|
b = mainnet.gas_estimate(STABLE, Eth::Key.new(priv: JEFF).address.to_s, 44_000)
|
54
60
|
refute_nil(b)
|
55
61
|
assert_predicate(b, :positive?)
|
@@ -57,28 +63,45 @@ class TestWallet < ERC20::Test
|
|
57
63
|
end
|
58
64
|
|
59
65
|
def test_fails_with_invalid_infura_key
|
66
|
+
WebMock.enable_net_connect!
|
60
67
|
skip('Apparently, even with invalid key, Infura returns balance')
|
61
68
|
w = ERC20::Wallet.new(
|
62
69
|
contract: ERC20::Wallet.USDT,
|
63
70
|
host: 'mainnet.infura.io',
|
64
71
|
http_path: '/v3/invalid-key-here',
|
65
|
-
log:
|
72
|
+
log: fake_loog
|
66
73
|
)
|
67
|
-
assert_raises(StandardError) {
|
74
|
+
assert_raises(StandardError) { w.balance(STABLE) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_logs_to_stdout
|
78
|
+
WebMock.disable_net_connect!
|
79
|
+
stub_request(:post, 'https://example.org/').to_return(
|
80
|
+
body: { jsonrpc: '2.0', id: 42, result: '0x1F1F1F' }.to_json,
|
81
|
+
headers: { 'Content-Type' => 'application/json' }
|
82
|
+
)
|
83
|
+
w = ERC20::Wallet.new(
|
84
|
+
host: 'example.org',
|
85
|
+
http_path: '/',
|
86
|
+
log: $stdout
|
87
|
+
)
|
88
|
+
w.balance(STABLE)
|
68
89
|
end
|
69
90
|
|
70
91
|
def test_checks_balance_on_testnet
|
92
|
+
WebMock.enable_net_connect!
|
71
93
|
b = testnet.balance(STABLE)
|
72
94
|
refute_nil(b)
|
73
95
|
assert_predicate(b, :zero?)
|
74
96
|
end
|
75
97
|
|
76
98
|
def test_checks_balance_on_polygon
|
99
|
+
WebMock.enable_net_connect!
|
77
100
|
w = ERC20::Wallet.new(
|
78
101
|
contract: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
79
102
|
host: 'polygon-mainnet.infura.io',
|
80
103
|
http_path: "/v3/#{env('INFURA_KEY')}",
|
81
|
-
log:
|
104
|
+
log: fake_loog
|
82
105
|
)
|
83
106
|
b = w.balance(STABLE)
|
84
107
|
refute_nil(b)
|
@@ -86,6 +109,7 @@ class TestWallet < ERC20::Test
|
|
86
109
|
end
|
87
110
|
|
88
111
|
def test_checks_gas_estimate_on_hardhat
|
112
|
+
WebMock.enable_net_connect!
|
89
113
|
sum = 100_000
|
90
114
|
on_hardhat do |wallet|
|
91
115
|
b1 = wallet.gas_estimate(
|
@@ -98,6 +122,7 @@ class TestWallet < ERC20::Test
|
|
98
122
|
end
|
99
123
|
|
100
124
|
def test_checks_balance_on_hardhat
|
125
|
+
WebMock.enable_net_connect!
|
101
126
|
on_hardhat do |wallet|
|
102
127
|
b = wallet.balance(Eth::Key.new(priv: JEFF).address.to_s)
|
103
128
|
assert_equal(123_000_100_000, b)
|
@@ -105,6 +130,7 @@ class TestWallet < ERC20::Test
|
|
105
130
|
end
|
106
131
|
|
107
132
|
def test_checks_eth_balance_on_hardhat
|
133
|
+
WebMock.enable_net_connect!
|
108
134
|
on_hardhat do |wallet|
|
109
135
|
b = wallet.balance(Eth::Key.new(priv: WALTER).address.to_s)
|
110
136
|
assert_equal(456_000_000_000, b)
|
@@ -112,6 +138,7 @@ class TestWallet < ERC20::Test
|
|
112
138
|
end
|
113
139
|
|
114
140
|
def test_checks_balance_on_hardhat_in_threads
|
141
|
+
WebMock.enable_net_connect!
|
115
142
|
on_hardhat do |wallet|
|
116
143
|
Threads.new.assert do
|
117
144
|
b = wallet.balance(Eth::Key.new(priv: JEFF).address.to_s)
|
@@ -121,6 +148,7 @@ class TestWallet < ERC20::Test
|
|
121
148
|
end
|
122
149
|
|
123
150
|
def test_pays_on_hardhat
|
151
|
+
WebMock.enable_net_connect!
|
124
152
|
on_hardhat do |wallet|
|
125
153
|
to = Eth::Key.new(priv: WALTER).address.to_s
|
126
154
|
before = wallet.balance(to)
|
@@ -135,6 +163,7 @@ class TestWallet < ERC20::Test
|
|
135
163
|
end
|
136
164
|
|
137
165
|
def test_eth_pays_on_hardhat
|
166
|
+
WebMock.enable_net_connect!
|
138
167
|
on_hardhat do |wallet|
|
139
168
|
to = Eth::Key.new(priv: WALTER).address.to_s
|
140
169
|
before = wallet.eth_balance(to)
|
@@ -149,6 +178,7 @@ class TestWallet < ERC20::Test
|
|
149
178
|
end
|
150
179
|
|
151
180
|
def test_pays_on_hardhat_in_threads
|
181
|
+
WebMock.enable_net_connect!
|
152
182
|
on_hardhat do |wallet|
|
153
183
|
to = Eth::Key.new(priv: WALTER).address.to_s
|
154
184
|
before = wallet.balance(to)
|
@@ -162,6 +192,7 @@ class TestWallet < ERC20::Test
|
|
162
192
|
end
|
163
193
|
|
164
194
|
def test_pays_eth_on_hardhat_in_threads
|
195
|
+
WebMock.enable_net_connect!
|
165
196
|
on_hardhat do |wallet|
|
166
197
|
to = Eth::Key.new(priv: WALTER).address.to_s
|
167
198
|
before = wallet.eth_balance(to)
|
@@ -175,6 +206,7 @@ class TestWallet < ERC20::Test
|
|
175
206
|
end
|
176
207
|
|
177
208
|
def test_accepts_payments_on_hardhat
|
209
|
+
WebMock.enable_net_connect!
|
178
210
|
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
179
211
|
jeff = Eth::Key.new(priv: JEFF).address.to_s.downcase
|
180
212
|
on_hardhat do |wallet|
|
@@ -186,7 +218,7 @@ class TestWallet < ERC20::Test
|
|
186
218
|
event = e
|
187
219
|
end
|
188
220
|
rescue StandardError => e
|
189
|
-
|
221
|
+
fake_loog.error(Backtrace.new(e))
|
190
222
|
end
|
191
223
|
wait_for { !active.empty? }
|
192
224
|
sum = 77_000
|
@@ -201,7 +233,40 @@ class TestWallet < ERC20::Test
|
|
201
233
|
end
|
202
234
|
end
|
203
235
|
|
236
|
+
def test_accepts_payments_on_hardhat_after_disconnect
|
237
|
+
skip('Works only on macOS') unless OS.mac?
|
238
|
+
WebMock.enable_net_connect!
|
239
|
+
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
240
|
+
Dir.mktmpdir do |home|
|
241
|
+
die = File.join(home, 'die.txt')
|
242
|
+
on_hardhat(die:) do |wallet|
|
243
|
+
active = []
|
244
|
+
events = []
|
245
|
+
daemon =
|
246
|
+
Thread.new do
|
247
|
+
wallet.accept([walter], active, subscription_id: 42) do |e|
|
248
|
+
events.append(e)
|
249
|
+
end
|
250
|
+
rescue StandardError => e
|
251
|
+
fake_loog.error(Backtrace.new(e))
|
252
|
+
end
|
253
|
+
wait_for { !active.empty? }
|
254
|
+
wallet.pay(JEFF, walter, 4_567)
|
255
|
+
wait_for { events.size == 1 }
|
256
|
+
FileUtils.touch(die)
|
257
|
+
on_hardhat(port: wallet.port) do
|
258
|
+
wallet.pay(JEFF, walter, 3_456)
|
259
|
+
wait_for { events.size > 1 }
|
260
|
+
daemon.kill
|
261
|
+
daemon.join(30)
|
262
|
+
assert_equal(3, events.size)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
204
268
|
def test_accepts_many_payments_on_hardhat
|
269
|
+
WebMock.enable_net_connect!
|
205
270
|
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
206
271
|
on_hardhat do |wallet|
|
207
272
|
active = []
|
@@ -213,7 +278,7 @@ class TestWallet < ERC20::Test
|
|
213
278
|
events.add(e)
|
214
279
|
end
|
215
280
|
rescue StandardError => e
|
216
|
-
|
281
|
+
fake_loog.error(Backtrace.new(e))
|
217
282
|
end
|
218
283
|
wait_for { !active.empty? }
|
219
284
|
sum = 1_234
|
@@ -228,6 +293,7 @@ class TestWallet < ERC20::Test
|
|
228
293
|
end
|
229
294
|
|
230
295
|
def test_accepts_payments_with_failures_on_hardhat
|
296
|
+
WebMock.enable_net_connect!
|
231
297
|
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
232
298
|
on_hardhat do |wallet|
|
233
299
|
active = []
|
@@ -253,6 +319,7 @@ class TestWallet < ERC20::Test
|
|
253
319
|
end
|
254
320
|
|
255
321
|
def test_accepts_payments_on_changing_addresses_on_hardhat
|
322
|
+
WebMock.enable_net_connect!
|
256
323
|
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
257
324
|
jeff = Eth::Key.new(priv: JEFF).address.to_s.downcase
|
258
325
|
addresses = Primitivo.new([walter])
|
@@ -265,7 +332,7 @@ class TestWallet < ERC20::Test
|
|
265
332
|
event = e
|
266
333
|
end
|
267
334
|
rescue StandardError => e
|
268
|
-
|
335
|
+
fake_loog.error(Backtrace.new(e))
|
269
336
|
end
|
270
337
|
wait_for { active.to_a.include?(walter) }
|
271
338
|
sum1 = 453_000
|
@@ -285,6 +352,7 @@ class TestWallet < ERC20::Test
|
|
285
352
|
end
|
286
353
|
|
287
354
|
def test_accepts_payments_on_hardhat_via_proxy
|
355
|
+
WebMock.enable_net_connect!
|
288
356
|
via_proxy do |proxy|
|
289
357
|
walter = Eth::Key.new(priv: WALTER).address.to_s.downcase
|
290
358
|
jeff = Eth::Key.new(priv: JEFF).address.to_s.downcase
|
@@ -298,7 +366,7 @@ class TestWallet < ERC20::Test
|
|
298
366
|
event = e
|
299
367
|
end
|
300
368
|
rescue StandardError => e
|
301
|
-
|
369
|
+
fake_loog.error(Backtrace.new(e))
|
302
370
|
end
|
303
371
|
wait_for { !active.empty? }
|
304
372
|
sum = 55_000
|
@@ -312,6 +380,7 @@ class TestWallet < ERC20::Test
|
|
312
380
|
end
|
313
381
|
|
314
382
|
def test_accepts_payments_on_mainnet
|
383
|
+
WebMock.enable_net_connect!
|
315
384
|
active = []
|
316
385
|
failed = false
|
317
386
|
net = mainnet
|
@@ -322,7 +391,7 @@ class TestWallet < ERC20::Test
|
|
322
391
|
end
|
323
392
|
rescue StandardError => e
|
324
393
|
failed = true
|
325
|
-
|
394
|
+
fake_loog.error(Backtrace.new(e))
|
326
395
|
end
|
327
396
|
wait_for { !active.empty? }
|
328
397
|
daemon.kill
|
@@ -331,6 +400,7 @@ class TestWallet < ERC20::Test
|
|
331
400
|
end
|
332
401
|
|
333
402
|
def test_checks_balance_via_proxy
|
403
|
+
WebMock.enable_net_connect!
|
334
404
|
b = nil
|
335
405
|
via_proxy do |proxy|
|
336
406
|
on_hardhat do |w|
|
@@ -342,17 +412,19 @@ class TestWallet < ERC20::Test
|
|
342
412
|
end
|
343
413
|
|
344
414
|
def test_checks_balance_via_proxy_on_mainnet
|
415
|
+
WebMock.enable_net_connect!
|
345
416
|
via_proxy do |proxy|
|
346
417
|
w = ERC20::Wallet.new(
|
347
418
|
host: 'mainnet.infura.io',
|
348
419
|
http_path: "/v3/#{env('INFURA_KEY')}",
|
349
|
-
proxy:, log:
|
420
|
+
proxy:, log: fake_loog
|
350
421
|
)
|
351
422
|
assert_equal(8_000_000, w.balance(STABLE))
|
352
423
|
end
|
353
424
|
end
|
354
425
|
|
355
426
|
def test_pays_on_mainnet
|
427
|
+
WebMock.enable_net_connect!
|
356
428
|
skip('This is live, must be run manually')
|
357
429
|
w = mainnet
|
358
430
|
print 'Enter Ethereum ERC20 private key (64 chars): '
|
@@ -384,7 +456,12 @@ class TestWallet < ERC20::Test
|
|
384
456
|
ws_path: "/#{env('GETBLOCK_WS_KEY')}"
|
385
457
|
}
|
386
458
|
].map do |server|
|
387
|
-
ERC20::Wallet.new(
|
459
|
+
ERC20::Wallet.new(
|
460
|
+
host: server[:host],
|
461
|
+
http_path: server[:http_path],
|
462
|
+
ws_path: server[:ws_path],
|
463
|
+
log: fake_loog
|
464
|
+
)
|
388
465
|
end.sample
|
389
466
|
end
|
390
467
|
|
@@ -401,7 +478,12 @@ class TestWallet < ERC20::Test
|
|
401
478
|
ws_path: "/#{env('GETBLOCK_SEPOILA_KEY')}"
|
402
479
|
}
|
403
480
|
].map do |server|
|
404
|
-
ERC20::Wallet.new(
|
481
|
+
ERC20::Wallet.new(
|
482
|
+
host: server[:host],
|
483
|
+
http_path: server[:http_path],
|
484
|
+
ws_path: server[:ws_path],
|
485
|
+
log: fake_loog
|
486
|
+
)
|
405
487
|
end.sample
|
406
488
|
end
|
407
489
|
|
@@ -409,7 +491,7 @@ class TestWallet < ERC20::Test
|
|
409
491
|
ERC20::Wallet.new(
|
410
492
|
contract: wallet.contract, chain: wallet.chain,
|
411
493
|
host: donce_host, port: wallet.port, http_path: wallet.http_path, ws_path: wallet.ws_path,
|
412
|
-
ssl: wallet.ssl, proxy:, log:
|
494
|
+
ssl: wallet.ssl, proxy:, log: fake_loog
|
413
495
|
)
|
414
496
|
end
|
415
497
|
|
@@ -419,20 +501,38 @@ class TestWallet < ERC20::Test
|
|
419
501
|
image: 'yegor256/squid-proxy:latest',
|
420
502
|
ports: { port => 3128 },
|
421
503
|
env: { 'USERNAME' => 'jeffrey', 'PASSWORD' => 'swordfish' },
|
422
|
-
root: true, log:
|
504
|
+
root: true, log: fake_loog
|
423
505
|
) do
|
424
506
|
yield "http://jeffrey:swordfish@localhost:#{port}"
|
425
507
|
end
|
426
508
|
end
|
427
509
|
end
|
428
510
|
|
429
|
-
def on_hardhat
|
430
|
-
RandomPort::Pool::SINGLETON.acquire do |
|
511
|
+
def on_hardhat(port: nil, die: nil)
|
512
|
+
RandomPort::Pool::SINGLETON.acquire do |rnd|
|
513
|
+
port = rnd if port.nil?
|
514
|
+
if die
|
515
|
+
killer = [
|
516
|
+
'&',
|
517
|
+
'HARDHAT_PID=$!;',
|
518
|
+
'export HARDHAT_PID;',
|
519
|
+
'while true; do',
|
520
|
+
" if [ -e #{Shellwords.escape(File.join('/die', File.basename(die)))} ]; then",
|
521
|
+
' kill -9 "${HARDHAT_PID}";',
|
522
|
+
' break;',
|
523
|
+
' else',
|
524
|
+
' sleep 0.1;',
|
525
|
+
' fi;',
|
526
|
+
'done'
|
527
|
+
].join(' ')
|
528
|
+
end
|
529
|
+
cmd = "npx hardhat node #{killer if die}"
|
431
530
|
donce(
|
432
531
|
home: File.join(__dir__, '../../hardhat'),
|
433
532
|
ports: { port => 8545 },
|
434
|
-
|
435
|
-
|
533
|
+
volumes: die ? { File.dirname(die) => '/die' } : {},
|
534
|
+
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
535
|
+
log: fake_loog
|
436
536
|
) do
|
437
537
|
wait_for_port(port)
|
438
538
|
cmd = [
|
@@ -445,13 +545,13 @@ class TestWallet < ERC20::Test
|
|
445
545
|
home: File.join(__dir__, '../../hardhat'),
|
446
546
|
command: "/bin/bash -c #{Shellwords.escape(cmd)}",
|
447
547
|
build_args: { 'HOST' => donce_host, 'PORT' => port },
|
448
|
-
log:
|
548
|
+
log: fake_loog,
|
449
549
|
root: true
|
450
550
|
).split("\n").last
|
451
551
|
wallet = ERC20::Wallet.new(
|
452
552
|
contract:, chain: 4242,
|
453
553
|
host: 'localhost', port:, http_path: '/', ws_path: '/', ssl: false,
|
454
|
-
log:
|
554
|
+
log: fake_loog
|
455
555
|
)
|
456
556
|
yield wallet
|
457
557
|
end
|
data/test/test__helper.rb
CHANGED
@@ -51,12 +51,14 @@ class Primitivo
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
require 'webmock/minitest'
|
55
|
+
|
54
56
|
# Test.
|
55
57
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
56
58
|
# Copyright:: Copyright (c) 2025 Yegor Bugayenko
|
57
59
|
# License:: MIT
|
58
60
|
class ERC20::Test < Minitest::Test
|
59
|
-
def
|
61
|
+
def fake_loog
|
60
62
|
ENV['RAKE'] ? Loog::ERRORS : Loog::VERBOSE
|
61
63
|
end
|
62
64
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erc20
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
10
|
+
date: 2025-04-23 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: eth
|