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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 429ad43c63c2126e8f57d5adcbe391c573f34f0a4d1174d19427f7f0b121aab2
4
- data.tar.gz: e354d3bc37396d2c1b35da5ca1800ce01ce90905b9ad9bd64e0617c71e1350b1
3
+ metadata.gz: 01ef1fcd61cd21540d932e28c65ed8afa08891e0cc20b498202eb146809d4993
4
+ data.tar.gz: c8e4593fb968dcfbad8e27d24aa974a37946b444eb42b4022139c48c51ffc5ca
5
5
  SHA512:
6
- metadata.gz: fc5eb0f95f5269cd94b848a05839ac4939b60efef2b0036a2ab693d6f16cc6a1c0d025d5ba580c2f5ce47232c494a64de224b66a47a155464b1401ac022d82f3
7
- data.tar.gz: 83b0d56783372f8a90eb0e9d89f6eed15101eb7eb0787882833baa73b1d97163b08b175f80c350d1f236e74e363ee9c1ffd4412437baa9630bfa4ee87196d8e2
6
+ metadata.gz: 38e4e84ea00f176c34a9f3f15ff0e77473f31fb29196b72d7e7b70af38e7aa8877015ff3249822b55665dfadac2cecb909a46d4c62554bcf5c85d8e1a1d30744
7
+ data.tar.gz: ffc78c5dfe3817bb59481f6e27e5e5fe7539ffca1e931fe8752fa31e98fee9e4c1880a2d3ce40496549c16b764524c0fad70c147d6f8a7467d8f1ba9c990d6dc
data/.rubocop.yml CHANGED
@@ -11,7 +11,6 @@ AllCops:
11
11
  SuggestExtensions: false
12
12
  NewCops: enable
13
13
  plugins:
14
- - rubocop-rspec
15
14
  - rubocop-performance
16
15
  - rubocop-rake
17
16
  - rubocop-minitest
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', '>0', require: false
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.0)
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.2)
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 (> 0)
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
@@ -39,7 +39,6 @@ require 'rubocop/rake_task'
39
39
  desc 'Run RuboCop on all directories'
40
40
  RuboCop::RakeTask.new(:rubocop) do |task|
41
41
  task.fail_on_error = true
42
- task.requires << 'rubocop-rspec'
43
42
  end
44
43
 
45
44
  require 'cucumber/rake/task'
data/lib/erc20/erc20.rb CHANGED
@@ -25,5 +25,5 @@
25
25
  # License:: MIT
26
26
  module ERC20
27
27
  # Current version of the gem (changed by the +.rultor.yml+ on every release)
28
- VERSION = '0.1.3'
28
+ VERSION = '0.1.5'
29
29
  end
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
- @log.debug("The balance of #{address} is #{b} ERC20 tokens")
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
- @log.debug("The balance of #{address} is #{b} ETHs")
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
- @log.debug("It would take #{gas} gas units to send #{amount} tokens from #{from} to #{to}")
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
- @log.debug("The cost of one gas unit is #{gwei} gwei")
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
- @log.debug("Sent #{amount} ERC20 tokens from #{from} to #{address}: #{tnx}")
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
- @log.debug("Sent #{amount} ETHs from #{from} to #{address}: #{tnx}")
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
- u = url(http: false)
322
- @log.debug("Connecting to #{u.hostname}:#{u.port}...")
323
- ws = Faye::WebSocket::Client.new(u.to_s, [], proxy: @proxy ? { origin: @proxy } : {})
324
- log = @log
325
- contract = @contract
326
- attempt = []
327
- log_url = "ws#{@ssl ? 's' : ''}://#{u.hostname}:#{u.port}"
328
- ws.on(:open) do
329
- safe do
330
- verbose do
331
- log.debug("Connected to #{log_url}")
332
- end
333
- end
334
- end
335
- ws.on(:message) do |msg|
336
- safe do
337
- verbose do
338
- data = to_json(msg)
339
- if data['id']
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
- log.debug(
405
- "Requested to subscribe ##{subscription_id} to #{addresses.to_a.size} addresses at #{log_url}: " \
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
- @log.error(Backtrace.new(e).to_s)
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
@@ -102,7 +102,7 @@ class TestFakeWallet < ERC20::Test
102
102
  event = e
103
103
  end
104
104
  rescue StandardError => e
105
- loog.error(Backtrace.new(e))
105
+ fake_loog.error(Backtrace.new(e))
106
106
  end
107
107
  wait_for { !active.to_a.empty? }
108
108
  wait_for { !event.nil? }
@@ -7,7 +7,9 @@ require 'backtrace'
7
7
  require 'donce'
8
8
  require 'eth'
9
9
  require 'faraday'
10
- require 'loog'
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: loog
72
+ log: fake_loog
66
73
  )
67
- assert_raises(StandardError) { p w.balance(STABLE) }
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: loog
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
- loog.error(Backtrace.new(e))
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
- loog.error(Backtrace.new(e))
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
- loog.error(Backtrace.new(e))
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
- loog.error(Backtrace.new(e))
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
- loog.error(Backtrace.new(e))
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: loog
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(host: server[:host], http_path: server[:http_path], ws_path: server[:ws_path], log: loog)
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(host: server[:host], http_path: server[:http_path], ws_path: server[:ws_path], log: loog)
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: loog
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: loog
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 |port|
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
- command: 'npx hardhat node',
435
- log: loog
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: loog,
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: loog
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 loog
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.3
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-20 00:00:00.000000000 Z
10
+ date: 2025-04-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: eth