Initial commit
This commit is contained in:
463
spec/lib/bitcoin/blockchain_spec.rb
Normal file
463
spec/lib/bitcoin/blockchain_spec.rb
Normal file
@@ -0,0 +1,463 @@
|
||||
describe Bitcoin::Blockchain do
|
||||
context :features do
|
||||
it 'defaults' do
|
||||
blockchain1 = Bitcoin::Blockchain.new
|
||||
expect(blockchain1.features).to eq Bitcoin::Blockchain::DEFAULT_FEATURES
|
||||
end
|
||||
|
||||
it 'override defaults' do
|
||||
blockchain2 = Bitcoin::Blockchain.new(cash_addr_format: true)
|
||||
expect(blockchain2.features[:cash_addr_format]).to be_truthy
|
||||
end
|
||||
|
||||
it 'custom feautures' do
|
||||
blockchain3 = Bitcoin::Blockchain.new(custom_feature: :custom)
|
||||
expect(blockchain3.features.keys).to contain_exactly(*Bitcoin::Blockchain::SUPPORTED_FEATURES)
|
||||
end
|
||||
end
|
||||
|
||||
context :configure do
|
||||
let(:blockchain) { Bitcoin::Blockchain.new }
|
||||
it 'default settings' do
|
||||
expect(blockchain.settings).to eq({})
|
||||
end
|
||||
|
||||
it 'currencies and server configuration' do
|
||||
currencies = Currency.where(type: :coin).first(2).map(&:to_blockchain_api_settings)
|
||||
settings = { server: 'http://user:password@127.0.0.1:18332',
|
||||
currencies: currencies,
|
||||
something: :custom }
|
||||
blockchain.configure(settings)
|
||||
expect(blockchain.settings).to eq(settings.slice(*Peatio::Blockchain::Abstract::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :transaction_sources do
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { '127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
def request_raw_transaction(transaction)
|
||||
{
|
||||
jsonrpc: '1.0',
|
||||
method: :getrawtransaction,
|
||||
params: [transaction.hash, 1]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
context 'transaction 3 vins' do
|
||||
|
||||
let(:transaction_file_name) { 'b3aa85765aa52cf7eb0a7f0eb7ac3447b9b1a82b9323bdd8d73fc305073a3711.json' }
|
||||
let(:transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin1_transaction_file_name) { 'vin-1ec4bf89b77f0d72ee41f41c97a6d380bd69e0221bf182b993b64bb37d017b57.json' }
|
||||
let(:vin1_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin1_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin2_transaction_file_name) { 'vin-8bde1da3d2315e09f910cb9782018fe243740c56cd7ca78a19016b169e74180a.json' }
|
||||
let(:vin2_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin2_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:vin3_transaction_file_name) { 'vin-64a04cad438b64e260bc0b832bde79913d618d3206e834de66bbcb1304629d61.json' }
|
||||
let(:vin3_transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', vin3_transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:transaction) { Peatio::Transaction.new(hash: 'b3aa85765aa52cf7eb0a7f0eb7ac3447b9b1a82b9323bdd8d73fc305073a3711') }
|
||||
let(:vin1_transaction) { Peatio::Transaction.new(hash: '1ec4bf89b77f0d72ee41f41c97a6d380bd69e0221bf182b993b64bb37d017b57') }
|
||||
let(:vin2_transaction) { Peatio::Transaction.new(hash: '8bde1da3d2315e09f910cb9782018fe243740c56cd7ca78a19016b169e74180a') }
|
||||
let(:vin3_transaction) { Peatio::Transaction.new(hash: '64a04cad438b64e260bc0b832bde79913d618d3206e834de66bbcb1304629d61') }
|
||||
|
||||
before do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(transaction))
|
||||
.to_return(body: transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin1_transaction))
|
||||
.to_return(body: vin1_transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin2_transaction))
|
||||
.to_return(body: vin2_transaction_data.to_json)
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(vin3_transaction))
|
||||
.to_return(body: vin3_transaction_data.to_json)
|
||||
end
|
||||
|
||||
it do
|
||||
addresses = blockchain.transaction_sources(transaction)
|
||||
expect(addresses).to eq(['1GsV7tXxXhfdp7kEFoFkgQLUWyv2xgPjnQ'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'miner transaction' do
|
||||
let(:transaction) { Peatio::Transaction.new(hash: '1da4b5a135cc0fee9d3aeb5428a898fd12fe5b1b777fd291dbbdbb72c2da8759') }
|
||||
let(:transaction_file_name) { 'miner_transaction.json' }
|
||||
let(:transaction_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', transaction_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_raw_transaction(transaction))
|
||||
.to_return(body: transaction_data.to_json)
|
||||
end
|
||||
|
||||
it do
|
||||
addresses = blockchain.transaction_sources(transaction)
|
||||
expect(addresses).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :latest_block_number do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { '127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
it 'returns latest block number' do
|
||||
block_number = 1489174
|
||||
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getblockcount,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: block_number,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
expect(blockchain.latest_block_number).to eq(block_number)
|
||||
end
|
||||
|
||||
it 'raises error if there is error in response body' do
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getblockcount,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: nil,
|
||||
error: { code: -32601, message: 'Method not found' },
|
||||
id: nil }.to_json)
|
||||
|
||||
expect{ blockchain.latest_block_number }.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
|
||||
it 'keeps alive' do
|
||||
stub_request(:post, endpoint)
|
||||
.to_return(body: { result: 1489174,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
.with(headers: { 'Connection': 'keep-alive',
|
||||
'Keep-Alive': '30' })
|
||||
|
||||
blockchain.latest_block_number
|
||||
end
|
||||
end
|
||||
|
||||
context :fetch_block! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:block_file_name) { '1354419-1354420.json' }
|
||||
let(:block_data) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', block_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:start_block) { block_data.first['result']['height'] }
|
||||
let(:latest_block) { block_data.last['result']['height'] }
|
||||
|
||||
def request_block_hash_body(block_height)
|
||||
{ jsonrpc: '1.0',
|
||||
method: :getblockhash,
|
||||
params: [block_height]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def request_block_body(block_hash)
|
||||
{ jsonrpc: '1.0',
|
||||
method: :getblock,
|
||||
params: [block_hash, 2]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
before do
|
||||
block_data.each do |blk|
|
||||
# stub get_block_hash
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_block_hash_body(blk['result']['height']))
|
||||
.to_return(body: {result: blk['result']['hash']}.to_json)
|
||||
|
||||
# stub get_block
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_block_body(blk['result']['hash']))
|
||||
.to_return(body: blk.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
let(:currency) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:endpoint) { 'http://127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap { |b| b.configure(server: server, currencies: [currency]) }
|
||||
end
|
||||
|
||||
context 'first block' do
|
||||
subject { blockchain.fetch_block!(start_block) }
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
expect(subject.count).to eq(14)
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'last block' do
|
||||
subject { blockchain.fetch_block!(latest_block) }
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
expect(subject.count).to eq(20)
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :build_transaction do
|
||||
|
||||
let(:tx_file_name) { '1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'bitcoin-data', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency.id}]
|
||||
end
|
||||
|
||||
let(:currency) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap { |b| b.configure(currencies: [currency.to_blockchain_api_settings]) }
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
|
||||
context 'multiple currencies' do
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency1.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency1.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>0,
|
||||
:to_address=>"mg4KVGerD3rYricWC8CoBaayDp1YCKMfvL",
|
||||
:amount=>0.325e0,
|
||||
:status=>"success",
|
||||
:currency_id=>currency2.id},
|
||||
{:hash=>"1858591d8ce638c37d5fcd92b9b33ee96be1b950e593cf0cbf45e6bfb1ad8a22",
|
||||
:txout=>1,
|
||||
:to_address=>"mqaBwWDjJCE2Egsf6pfysgD5ZBrfsP7NkA",
|
||||
:amount=>0.1964466932e2,
|
||||
:status=>"success",
|
||||
:currency_id=>currency2.id}]
|
||||
end
|
||||
|
||||
let(:currency1) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:currency2) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap do |b|
|
||||
b.configure(currencies: [currency1.to_blockchain_api_settings, currency2.to_blockchain_api_settings])
|
||||
end
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction per each currency' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'three vout transaction' do
|
||||
let(:tx_file_name) { '1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0.json' }
|
||||
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>0,
|
||||
:to_address=>"2N5WyM3QT1Kb6fvkSZj3Xvcx2at7Ydm5VmL",
|
||||
:amount=>0.1e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"},
|
||||
{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>1,
|
||||
:to_address=>"2MzDFuDK9ZEEiRsuCDFkPdeHQLGvwbC9ufG",
|
||||
:amount=>0.2e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"},
|
||||
{:hash=>"1da5cd163a9aaf830093115ac3ac44355e0bcd15afb59af78f84ad4084973ad0",
|
||||
:txout=>2,
|
||||
:to_address=>"2MuvCKKi1MzGtvZqvcbqn5twjA2v5XLaTWe",
|
||||
:amount=>0.11749604e0,
|
||||
:status=>"success",
|
||||
:currency_id=>"btc"}]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for each vout' do
|
||||
expect(blockchain.send(:build_transaction, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance_of_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:server) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:blockchain) do
|
||||
Bitcoin::Blockchain.new.tap {|b| b.configure(server: server)}
|
||||
end
|
||||
|
||||
|
||||
let(:response) do
|
||||
[
|
||||
[
|
||||
[
|
||||
"mh2e7YHio7fTjLXHZ3KRXDfU52RbwQbhtK",
|
||||
0,
|
||||
""
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"mkuYucVhRQDiSHszbZ9M7d7vygKymyZ549",
|
||||
0.14458097,
|
||||
"my_imported_address_from_electrum"
|
||||
],
|
||||
[
|
||||
"mpD544rTPbRNDr9yzK9MTGS4ckfVxUNY42",
|
||||
0.24997197,
|
||||
"imported_address_from_bitgo"
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:18332')
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :listaddressgroupings,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: response,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
end
|
||||
|
||||
context 'address with balance is defined' do
|
||||
it 'requests rpc listaddressgroupings and finds address balance' do
|
||||
address = 'mpD544rTPbRNDr9yzK9MTGS4ckfVxUNY42'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :btc)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.24997197'.to_d)
|
||||
end
|
||||
|
||||
it 'requests rpc listaddressgroupings and finds address with zero balance' do
|
||||
address = 'mh2e7YHio7fTjLXHZ3KRXDfU52RbwQbhtK'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :btc)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0'.to_d)
|
||||
end
|
||||
end
|
||||
|
||||
context 'address is not defined' do
|
||||
it 'requests rpc listaddressgroupings and do not find address' do
|
||||
address = '1PoxQx6Pk5NwWN1yyBx2jifFvS9eJAksdf'
|
||||
expect{ blockchain.load_balance_of_address!(address, :btc)}.to raise_error(Peatio::Blockchain::UnavailableAddressBalanceError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'client error is raised' do
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:18332')
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :listaddressgroupings,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: nil,
|
||||
error: { code: -32601, message: 'Method not found' },
|
||||
id: nil }.to_json)
|
||||
end
|
||||
|
||||
it 'raise wrapped client error' do
|
||||
expect{ blockchain.load_balance_of_address!('anything', :btc)}.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
196
spec/lib/bitcoin/wallet_spec.rb
Normal file
196
spec/lib/bitcoin/wallet_spec.rb
Normal file
@@ -0,0 +1,196 @@
|
||||
describe Bitcoin::Wallet do
|
||||
let(:wallet) { Bitcoin::Wallet.new }
|
||||
|
||||
context :configure do
|
||||
let(:settings) { { wallet: {}, currency: {} }}
|
||||
it 'requires wallet' do
|
||||
expect{ wallet.configure(settings.except(:wallet)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect{ wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
expect{ wallet.configure(settings.except(:currency)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect{ wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'sets settings attribute' do
|
||||
wallet.configure(settings)
|
||||
expect(wallet.settings).to eq(settings.slice(*Bitcoin::Wallet::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :create_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:uri) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:uri_without_authority) { 'http://127.0.0.1:18332' }
|
||||
|
||||
let(:uri_with_wallet) { 'http://user:password@127.0.0.1:18332/wallet/testwallet' }
|
||||
let(:uri_with_wallet_no_authority) { 'http://127.0.0.1:18332/wallet/testwallet' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri },
|
||||
currency: {}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'request rpc and creates new address' do
|
||||
address = '2N4qYjye5yENLEkz4UkLFxzPaxJatF3kRwf'
|
||||
stub_request(:post, uri_without_authority)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getnewaddress,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: address,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
expect(result.as_json.symbolize_keys).to eq(address: address)
|
||||
end
|
||||
|
||||
it 'works with wallet path' do
|
||||
wallet.configure({
|
||||
wallet: {
|
||||
address: 'something',
|
||||
uri: uri_with_wallet },
|
||||
currency: {}
|
||||
})
|
||||
address = '2N4qYjye5yENLEkz4UkLFxzPaxJatF3kRwf'
|
||||
stub_request(:post, uri_with_wallet_no_authority)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getnewaddress,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: address,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
expect(result.as_json.symbolize_keys).to eq(address: address)
|
||||
end
|
||||
end
|
||||
|
||||
context :create_transaction! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:uri) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:uri_without_authority) { 'http://127.0.0.1:18332' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri },
|
||||
currency: {}
|
||||
}
|
||||
end
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(amount: 1.1, to_address: '2N4qYjye5yENLEkz4UkLFxzPaxJatF3kRwf')
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'requests rpc and sends transaction without subtract fees' do
|
||||
txid = 'ab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri_without_authority)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :sendtoaddress,
|
||||
params: [
|
||||
transaction.to_address,
|
||||
transaction.amount,
|
||||
'',
|
||||
'',
|
||||
false
|
||||
] }.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1,
|
||||
to_address: '2N4qYjye5yENLEkz4UkLFxzPaxJatF3kRwf',
|
||||
hash: txid,
|
||||
status: 'pending')
|
||||
end
|
||||
|
||||
it 'requests rpc and sends transaction with subtract fees' do
|
||||
txid = 'ab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri_without_authority)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :sendtoaddress,
|
||||
params: [
|
||||
transaction.to_address,
|
||||
transaction.amount,
|
||||
'',
|
||||
'',
|
||||
true
|
||||
] }.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction, subtract_fee: true)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1,
|
||||
to_address: '2N4qYjye5yENLEkz4UkLFxzPaxJatF3kRwf',
|
||||
hash: txid,
|
||||
status: 'pending')
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:uri) { 'http://user:password@127.0.0.1:18332' }
|
||||
let(:uri_without_authority) { 'http://127.0.0.1:18332' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri },
|
||||
currency: {}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'requests rpc with getbalance call' do
|
||||
balance = '6.72201169'
|
||||
stub_request(:post, uri_without_authority)
|
||||
.with(body: { jsonrpc: '1.0',
|
||||
method: :getbalance,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: balance,
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq(balance.to_d)
|
||||
end
|
||||
end
|
||||
end
|
||||
10
spec/lib/decimal_spec.rb
Normal file
10
spec/lib/decimal_spec.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe String do
|
||||
it { expect('1.0'.to_d).to eq(BigDecimal('1.0')) }
|
||||
it { expect('1.'.to_d).to eq(BigDecimal('1.0')) }
|
||||
it { expect('1'.to_d).to eq(BigDecimal('1.0')) }
|
||||
it { expect('-1'.to_d).to eq(BigDecimal('-1.0')) }
|
||||
it { expect(''.to_d).to eq(BigDecimal('0.0')) }
|
||||
it { expect { 'decimal'.to_d }.to raise_error ArgumentError }
|
||||
end
|
||||
467
spec/lib/ethereum/blockchain_spec.rb
Normal file
467
spec/lib/ethereum/blockchain_spec.rb
Normal file
@@ -0,0 +1,467 @@
|
||||
describe Ethereum::Blockchain do
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:ring) do
|
||||
Currency.find_by(id: :ring)
|
||||
end
|
||||
|
||||
let!(:tom) do
|
||||
create(:currency, :tom)
|
||||
end
|
||||
|
||||
let!(:address_1) { create(:whitelisted_smart_contract, :address_1, address: '0x6c0b51971650d28821ce30b15b02b9826a20b129') }
|
||||
let!(:address_2) { create(:whitelisted_smart_contract, :address_2) }
|
||||
let!(:address_3) { create(:whitelisted_smart_contract, :address_3) }
|
||||
let!(:address_4) { create(:whitelisted_smart_contract, :address_4) }
|
||||
let!(:address_5) { create(:whitelisted_smart_contract, :address_5) }
|
||||
|
||||
let(:blockchain) do
|
||||
Ethereum::Blockchain.new.tap { |b| b.configure(server: server, currencies: [eth.to_blockchain_api_settings, trst.to_blockchain_api_settings, ring.to_blockchain_api_settings, tom.to_blockchain_api_settings], whitelisted_addresses: [address_1, address_2, address_3, address_4, address_5]) }
|
||||
end
|
||||
|
||||
let(:server) { 'http://127.0.0.1:8545' }
|
||||
let(:endpoint) { 'http://127.0.0.1:8545' }
|
||||
|
||||
context :features do
|
||||
it 'defaults' do
|
||||
blockchain1 = Ethereum::Blockchain.new
|
||||
expect(blockchain1.features).to eq Ethereum::Blockchain::DEFAULT_FEATURES
|
||||
end
|
||||
|
||||
it 'override defaults' do
|
||||
blockchain2 = Ethereum::Blockchain.new(cash_addr_format: true)
|
||||
expect(blockchain2.features[:cash_addr_format]).to be_truthy
|
||||
end
|
||||
|
||||
it 'custom feautures' do
|
||||
blockchain3 = Ethereum::Blockchain.new(custom_feature: :custom)
|
||||
expect(blockchain3.features.keys).to contain_exactly(*Ethereum::Blockchain::SUPPORTED_FEATURES)
|
||||
end
|
||||
end
|
||||
|
||||
context :configure do
|
||||
let(:blockchain) { Ethereum::Blockchain.new }
|
||||
it 'default settings' do
|
||||
expect(blockchain.settings).to eq({})
|
||||
end
|
||||
|
||||
it 'currencies and server configuration' do
|
||||
currencies = Currency.where(type: :coin).first(2).map(&:to_blockchain_api_settings)
|
||||
settings = { server: server,
|
||||
currencies: currencies,
|
||||
something: :custom }
|
||||
blockchain.configure(settings)
|
||||
expect(blockchain.settings).to eq(settings.slice(*Peatio::Blockchain::Abstract::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :latest_block_number do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:blockchain) do
|
||||
Ethereum::Blockchain.new.tap { |b| b.configure(server: server) }
|
||||
end
|
||||
|
||||
let(:method) { :eth_blockNumber }
|
||||
|
||||
it 'returns latest block number' do
|
||||
block_number = '0x16b916'
|
||||
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_blockNumber,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { result: block_number,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
expect(blockchain.latest_block_number).to eq(block_number.to_i(16))
|
||||
end
|
||||
|
||||
it 'raises error if there is error in response body' do
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: method,
|
||||
params: [] }.to_json)
|
||||
.to_return(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
error: { code: -32601, message: "The method #{method} does not exist/is not available" },
|
||||
id: 1 }.to_json)
|
||||
|
||||
expect{ blockchain.latest_block_number }.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
|
||||
it 'keeps alive' do
|
||||
stub_request(:post, endpoint)
|
||||
.to_return(body: { result: '0x16b916',
|
||||
error: nil,
|
||||
id: nil }.to_json)
|
||||
.with(headers: { 'Connection': 'keep-alive',
|
||||
'Keep-Alive': '30' })
|
||||
|
||||
blockchain.latest_block_number
|
||||
end
|
||||
end
|
||||
|
||||
context :fetch_block! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:block_file_name) { '2621840-2621842.json' }
|
||||
|
||||
let(:block_data) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby', block_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:transaction_receipt_data) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby/transaction-receipts', block_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:start_block) { block_data.first['result']['number'].hex }
|
||||
let(:latest_block) { block_data.last['result']['number'].hex }
|
||||
|
||||
def request_block_body(block_height)
|
||||
{ jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_getBlockByNumber,
|
||||
params: [block_height, true]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def request_receipt_block_body(block_hash)
|
||||
{ jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_getTransactionReceipt,
|
||||
params: [block_hash]
|
||||
}.to_json
|
||||
end
|
||||
|
||||
before do
|
||||
Ethereum::Client.any_instance.stubs(:rpc_call_id).returns(1)
|
||||
block_data.each do |blk|
|
||||
# stub get_block_hash
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_block_body(blk['result']['number']))
|
||||
.to_return(body: blk.to_json )
|
||||
end
|
||||
|
||||
transaction_receipt_data.each do |blk|
|
||||
# stub get_receipt
|
||||
stub_request(:post, endpoint)
|
||||
.with(body: request_receipt_block_body(blk['result']['transactionHash']))
|
||||
.to_return(body: blk.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
context 'first block' do
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"0xb60e22c6eed3dc8cd7bc5c7e38c50aa355c55debddbff5c1c4837b995b8ee96d",
|
||||
:amount=>1.to_d,
|
||||
:to_address=>"0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa",
|
||||
:from_addresses=>['0xb3ebc7b5b631e8d145f383c8cd07f0f00dd56a30'],
|
||||
:txout=>26,
|
||||
:block_number=>2621840,
|
||||
:status=>"pending",
|
||||
:currency_id=>eth.id}]
|
||||
end
|
||||
|
||||
subject { blockchain.fetch_block!(start_block) }
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
subject.transactions.each_with_index do |t, i|
|
||||
expect(t.as_json).to eq(expected_transactions[i].as_json)
|
||||
end
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'last block' do
|
||||
subject { blockchain.fetch_block!(latest_block) }
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"0x0338e2a59db18596afff8b7a0db3669cc231c7333064640bedf3a73c1c1c31ed",
|
||||
:amount=>18.75.to_d,
|
||||
:to_address=>"0xc4d276bf32b71cdddb18f3b4d258f057a5ffda03",
|
||||
:from_addresses=>['0x31b98d14007bdee637298086988a0bbd31184523'],
|
||||
:txout=>13,
|
||||
:block_number=>2621842,
|
||||
:status=>"pending",
|
||||
:currency_id=>eth.id},
|
||||
{:hash=>"0x826555325cec51c4d39b327e563ce3e8ee87e27be5911383f528724a62f0da5d",
|
||||
:amount=>2.to_d,
|
||||
:to_address=>"0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa",
|
||||
:from_addresses=>['0x4de22dd63afb3bec965dbb734c15fba58800c923'],
|
||||
:txout=>8,
|
||||
:block_number=>2621842,
|
||||
:status=>"success",
|
||||
:currency_id=>trst.id},
|
||||
{:hash=>"0x826555325cec51c4d39b327e563ce3e8ee87e27be5911383f528724a62f0da5d",
|
||||
:amount=>2.to_d,
|
||||
:to_address=>"0x4b6a630ff1f66604d31952bdce2e4950efc99821",
|
||||
:from_addresses=>['0x4de22dd63afb3bec965dbb734c15fba58800c923'],
|
||||
:txout=>9,
|
||||
:block_number=>2621842,
|
||||
:status=>"success",
|
||||
:currency_id=>ring.id},
|
||||
{:hash=>"0xd5cc0d1d5dd35f4b57572b440fb4ef39a4ab8035657a21692d1871353bfbceea",
|
||||
:amount=>2.to_d,
|
||||
:to_address=>"0xe3cb6897d83691a8eb8458140a1941ce1d6e6dac",
|
||||
:from_addresses=>['0x4de22dd63afb3bec965dbb734c15fba58800c923'],
|
||||
:txout=>9,
|
||||
:block_number=>2621842,
|
||||
:status=>"failed",
|
||||
:currency_id=>trst.id},
|
||||
{:hash=>"0x5ab0f1a1f29da4e4ddb021c28e2383ec6bde03fb04a8e25c49a1ae5ae34b6f58",
|
||||
:amount=>0.039082.to_d,
|
||||
:to_address=>"0x40968978cf4e6b53ca161c5ba6918a926a8d5ac2",
|
||||
:from_addresses=>['0x210169700dd7131df7fc4885804a0c92c45e63f6'],
|
||||
:txout=>131,
|
||||
:block_number=>8292243,
|
||||
:status=>"pending",
|
||||
:currency_id=>eth.id},
|
||||
{:hash=>"0xeb92797eb91f53ce7bb68abaf3fd3198980d971dd42f9fcb6eb1272ef3ef2a0e",
|
||||
:to_address=>"0xbbd602bb278edff65cbc967b9b62095ad5be23a3",
|
||||
:from_addresses=>["0x095273adb73e55a8710e448c49eaee16fe115527"],
|
||||
:amount=>2436832050000.to_d,
|
||||
:currency_id=>tom.id,
|
||||
:block_number=>11684206,
|
||||
:status=>"success",
|
||||
:txout=>3}
|
||||
]
|
||||
end
|
||||
|
||||
it 'builds expected number of transactions' do
|
||||
subject.transactions.each_with_index do |t, i|
|
||||
expect(t.as_json).to eq(expected_transactions[i].as_json)
|
||||
end
|
||||
end
|
||||
|
||||
it 'all transactions are valid' do
|
||||
expect(subject.all?(&:valid?)).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :build_transaction do
|
||||
|
||||
context :eth_transaction do
|
||||
|
||||
let(:tx_file_name) { '0xb60e22c6eed3dc8cd7bc5c7e38c50aa355c55debddbff5c1c4837b995b8ee96d.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby', 'transactions', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"0xb60e22c6eed3dc8cd7bc5c7e38c50aa355c55debddbff5c1c4837b995b8ee96d",
|
||||
:to_address=>"0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa",
|
||||
:from_addresses=>["0xb3ebc7b5b631e8d145f383c8cd07f0f00dd56a30"],
|
||||
:txout=>26,
|
||||
:amount=>1.to_d,
|
||||
:block_number=>2621840,
|
||||
:currency_id=>eth.id,
|
||||
:status=>'pending'}]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transactions, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
|
||||
context :erc20_transaction do
|
||||
|
||||
let(:tx_file_name) { '0x826555325cec51c4d39b327e563ce3e8ee87e27be5911383f528724a62f0da5d.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby', 'transactions', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"0x826555325cec51c4d39b327e563ce3e8ee87e27be5911383f528724a62f0da5d",
|
||||
:to_address=>"0xe3cb6897d83691a8eb8458140a1941ce1d6e6daa",
|
||||
:from_addresses=>["0x4de22dd63afb3bec965dbb734c15fba58800c923"],
|
||||
:amount=>2.to_d,
|
||||
:currency_id=>trst.id,
|
||||
:block_number=>2621842,
|
||||
:status=>"success",
|
||||
:txout=>8},
|
||||
{:hash=>"0x826555325cec51c4d39b327e563ce3e8ee87e27be5911383f528724a62f0da5d",
|
||||
:to_address=>"0x4b6a630ff1f66604d31952bdce2e4950efc99821",
|
||||
:from_addresses=>["0x4de22dd63afb3bec965dbb734c15fba58800c923"],
|
||||
:amount=>2.to_d,
|
||||
:currency_id=>ring.id,
|
||||
:block_number=>2621842,
|
||||
:status=>"success",
|
||||
:txout=>9}]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transactions, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
|
||||
context :pending_erc20_transaction do
|
||||
let(:tx_file_name) { '0xb44861cb188356c67bec27a35abab1b7ef33fc402330faa9ac4d863ef621d7b1.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby', 'transactions', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:expected_transactions) do
|
||||
[]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transactions, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :tom_whitelisted_smart_contract_transaction do
|
||||
let(:tx_file_name) { '0xeb92797eb91f53ce7bb68abaf3fd3198980d971dd42f9fcb6eb1272ef3ef2a0e.json' }
|
||||
|
||||
let(:tx_hash) do
|
||||
Rails.root.join('spec', 'resources', 'ethereum-data', 'rinkeby', 'transactions', tx_file_name)
|
||||
.yield_self { |file_path| File.open(file_path) }
|
||||
.yield_self { |file| JSON.load(file) }
|
||||
end
|
||||
|
||||
let(:expected_transactions) do
|
||||
[{:hash=>"0xeb92797eb91f53ce7bb68abaf3fd3198980d971dd42f9fcb6eb1272ef3ef2a0e",
|
||||
:to_address=>"0xbbd602bb278edff65cbc967b9b62095ad5be23a3",
|
||||
:from_addresses=>["0x095273adb73e55a8710e448c49eaee16fe115527"],
|
||||
:amount=>2436832050000.to_d,
|
||||
:currency_id=>tom.id,
|
||||
:block_number=>11684206,
|
||||
:status=>"success",
|
||||
:txout=>3}
|
||||
]
|
||||
end
|
||||
|
||||
it 'builds formatted transactions for passed transaction' do
|
||||
expect(blockchain.send(:build_transactions, tx_hash)).to contain_exactly(*expected_transactions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance_of_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:response1) do
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
result: "0x71a5c4e9fe8a100",
|
||||
id: 1
|
||||
}
|
||||
end
|
||||
|
||||
let(:response2) do
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
result: "0x7a120",
|
||||
id: 1
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_getBalance,
|
||||
params:
|
||||
[
|
||||
'0x1c077de4aa6fa6fa023a9e31b8bdddeb0b44c774',
|
||||
'latest'
|
||||
] }.to_json)
|
||||
.to_return(body: response1.to_json)
|
||||
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_call,
|
||||
params:
|
||||
[
|
||||
{
|
||||
to: "0x87099add3bcc0821b5b151307c147215f839a110",
|
||||
data: "0x70a082310000000000000000000000001c077de4aa6fa6fa023a9e31b8bdddeb0b44c774"
|
||||
},
|
||||
'latest'
|
||||
] }.to_json)
|
||||
.to_return(body: response2.to_json)
|
||||
end
|
||||
|
||||
context 'get balance of eth/erc20 address' do
|
||||
it 'requests rpc eth_getBalance and get balance' do
|
||||
address = '0x1c077de4aa6fa6fa023a9e31b8bdddeb0b44c774'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :eth)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.51182300042'.to_d)
|
||||
end
|
||||
|
||||
it 'requests rpc eth_call and get token balance' do
|
||||
address = '0x1c077de4aa6fa6fa023a9e31b8bdddeb0b44c774'
|
||||
|
||||
result = blockchain.load_balance_of_address!(address, :trst)
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.5'.to_d)
|
||||
end
|
||||
|
||||
it 'raise undefined currency error' do
|
||||
expect { blockchain.load_balance_of_address!('something', :usdt).to raise(Ethereum::Blockchain::UndefinedCurrencyError) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'client error is raised' do
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_getBalance,
|
||||
params:
|
||||
[
|
||||
'anything',
|
||||
'latest'
|
||||
] }.to_json)
|
||||
.to_return(body: { jsonrpc: '2.0',
|
||||
error: { code: -32601, message: 'Method not found' },
|
||||
id: 1 }.to_json)
|
||||
end
|
||||
|
||||
it 'raise wrapped client error' do
|
||||
expect { blockchain.load_balance_of_address!('anything', :eth) }.to raise_error(Peatio::Blockchain::ClientError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
558
spec/lib/ethereum/wallet_spec.rb
Normal file
558
spec/lib/ethereum/wallet_spec.rb
Normal file
@@ -0,0 +1,558 @@
|
||||
describe Ethereum::Wallet do
|
||||
let(:wallet) { Ethereum::Wallet.new }
|
||||
|
||||
context :configure do
|
||||
let(:settings) { { wallet: {}, currency: {} } }
|
||||
it 'requires wallet' do
|
||||
expect { wallet.configure(settings.except(:wallet)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
expect { wallet.configure(settings.except(:currency)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'sets settings attribute' do
|
||||
wallet.configure(settings)
|
||||
expect(wallet.settings).to eq(settings.slice(*Ethereum::Wallet::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :create_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8545' }
|
||||
let(:uri_with_path) { 'http://127.0.0.1:8545/path/extra' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri },
|
||||
currency: {}
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
PasswordGenerator.stubs(:generate).returns('pass@word')
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'request rpc and creates new address' do
|
||||
address = '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa'
|
||||
stub_request(:post, uri)
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :personal_newAccount,
|
||||
params: ['pass@word'] }.to_json)
|
||||
.to_return(body: { jsonrpc: '2.0',
|
||||
result: address,
|
||||
id: 1 }.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
expect(result.as_json.symbolize_keys).to eq(address: address, secret: 'pass@word')
|
||||
end
|
||||
|
||||
it 'works with wallet path' do
|
||||
wallet.configure({
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri_with_path },
|
||||
currency: {}
|
||||
})
|
||||
address = '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa'
|
||||
stub_request(:post, uri_with_path)
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :personal_newAccount,
|
||||
params: ['pass@word'] }.to_json)
|
||||
.to_return(body: { jsonrpc: '2.0',
|
||||
result: address,
|
||||
id: 1 }.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
expect(result.as_json.symbolize_keys).to eq(address: address, secret: 'pass@word')
|
||||
end
|
||||
end
|
||||
|
||||
context :create_transaction! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:ring) do
|
||||
Currency.find_by(id: :ring)
|
||||
end
|
||||
|
||||
let(:deposit_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :deposit) }
|
||||
let(:hot_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :hot) }
|
||||
let(:fee_wallet) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :fee) }
|
||||
let(:deposit_wallet_trst) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :deposit) }
|
||||
let(:hot_wallet_trst) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :hot) }
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8545' }
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(amount: 1.1.to_d, to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa')
|
||||
end
|
||||
|
||||
context 'eth transaction with subtract fees' do
|
||||
let(:value) { 1_099_979_000_000_000_000 }
|
||||
|
||||
let(:gas_limit) { 21_000 }
|
||||
let(:gas_price) { 1_000_000_000 }
|
||||
let(:gas_price_hex) { '0x' + gas_price.to_s(16) }
|
||||
|
||||
let(:request_body) do
|
||||
{ jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: :personal_sendTransaction,
|
||||
params: [{
|
||||
from: deposit_wallet_eth.address.downcase,
|
||||
to: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
value: '0x' + (value.to_s 16),
|
||||
gas: '0x' + (gas_limit.to_s 16),
|
||||
gasPrice: gas_price_hex
|
||||
}, 'changeme'] }
|
||||
end
|
||||
|
||||
let(:eth_GasPrice) do
|
||||
{
|
||||
"jsonrpc": '2.0',
|
||||
"id": 1,
|
||||
"method": 'eth_gasPrice',
|
||||
"params": []
|
||||
}
|
||||
end
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: deposit_wallet_eth.to_wallet_api_settings,
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'requests rpc and sends transaction' do
|
||||
txid = '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: request_body.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction, subtract_fee: true)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.099979.to_s,
|
||||
currency_id: 'eth',
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: txid,
|
||||
status: 'pending',
|
||||
options: { 'gas_limit' => 21_000, 'gas_price' => 1_000_000_000, 'subtract_fee' => true })
|
||||
end
|
||||
|
||||
context 'without subtract fees' do
|
||||
let(:value) { 1_100_000_000_000_000_000 }
|
||||
|
||||
let(:request_body) do
|
||||
{ jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: :personal_sendTransaction,
|
||||
params: [{
|
||||
from: deposit_wallet_eth.address.downcase,
|
||||
to: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
value: '0x' + (value.to_s 16),
|
||||
gas: '0x' + (gas_limit.to_s 16),
|
||||
gasPrice: gas_price_hex
|
||||
}, 'changeme'] }
|
||||
end
|
||||
|
||||
it 'requests rpc and sends transaction' do
|
||||
txid = '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: request_body.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
currency_id: 'eth',
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: txid,
|
||||
status: 'pending',
|
||||
options: { 'gas_limit' => 21_000, 'gas_price' => 1_000_000_000.0 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom gas_price and subcstract fees' do
|
||||
let(:value) { 1_099_976_900_000_000_000 }
|
||||
|
||||
let(:gas_price) { 1_100_000_000 }
|
||||
let(:gas_mode) { :standard }
|
||||
|
||||
let(:request_body) do
|
||||
{ jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: :personal_sendTransaction,
|
||||
params: [{
|
||||
from: deposit_wallet_eth.address.downcase,
|
||||
to: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
value: '0x' + (value.to_s 16),
|
||||
gas: '0x' + (gas_limit.to_s 16),
|
||||
gasPrice: gas_price_hex
|
||||
}, 'changeme'] }
|
||||
end
|
||||
|
||||
before do
|
||||
settings[:currency][:options] = { gas_price: gas_mode }
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it do
|
||||
txid = '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: request_body.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
result = wallet.create_transaction!(transaction, subtract_fee: true)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.0999769.to_s,
|
||||
currency_id: 'eth',
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: txid,
|
||||
status: 'pending',
|
||||
options: { 'gas_limit' => 21_000, 'gas_price' => 1_100_000_000, 'subtract_fee' => true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'erc20 transaction' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: deposit_wallet_trst.to_wallet_api_settings,
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:gas_price) { 1_000_000_000 }
|
||||
let(:gas_price_hex) { '0x' + gas_price.to_s(16) }
|
||||
|
||||
let(:eth_GasPrice) do
|
||||
{
|
||||
"jsonrpc": '2.0',
|
||||
"id": 1,
|
||||
"method": 'eth_gasPrice',
|
||||
"params": []
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_body) do
|
||||
{ jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: :personal_sendTransaction,
|
||||
params: [{
|
||||
from: deposit_wallet_eth.address.downcase,
|
||||
to: trst.options.fetch('erc20_contract_address'),
|
||||
data: '0xa9059cbb0000000000000000000000006d6cabaa7232d7f45b143b445114f7e92350a2aa000000000000000000000000000000000000000000000000000000000010c8e0',
|
||||
gas: '0x15f90',
|
||||
gasPrice: gas_price_hex
|
||||
}, 'changeme'] }
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it do
|
||||
txid = '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: request_body.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: txid,
|
||||
status: 'pending',
|
||||
options: { 'erc20_contract_address' => '0x87099add3bcc0821b5b151307c147215f839a110', 'gas_limit' => 90_000, 'gas_price' => 1_000_000_000 })
|
||||
end
|
||||
end
|
||||
|
||||
context :prepare_deposit_collection! do
|
||||
let(:value) { '0xa3b5840f4000' }
|
||||
|
||||
let(:gas_price) { 1_000_000_000 }
|
||||
let(:gas_price_hex) { '0x' + gas_price.to_s(16) }
|
||||
|
||||
let(:eth_GasPrice) do
|
||||
{
|
||||
"jsonrpc": '2.0',
|
||||
"id": 1,
|
||||
"method": 'eth_gasPrice',
|
||||
"params": []
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_body) do
|
||||
{ jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: :personal_sendTransaction,
|
||||
params: [{
|
||||
from: fee_wallet.address.downcase,
|
||||
to: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
value: value,
|
||||
gas: '0x5208',
|
||||
gasPrice: '0x3b9aca00'
|
||||
}, 'changeme'] }
|
||||
end
|
||||
|
||||
let(:spread_deposit) do
|
||||
[{ to_address: 'fake-hot',
|
||||
amount: '2.0',
|
||||
currency_id: trst.id },
|
||||
{ to_address: 'fake-hot',
|
||||
amount: '2.0',
|
||||
currency_id: trst.id }]
|
||||
end
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: fee_wallet.to_wallet_api_settings,
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it do
|
||||
txid = '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017'
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
|
||||
stub_request(:post, uri)
|
||||
.with(body: request_body.to_json)
|
||||
.to_return(body: { result: txid,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
result = wallet.prepare_deposit_collection!(transaction, spread_deposit, trst.to_blockchain_api_settings)
|
||||
expect(result.first.as_json.symbolize_keys).to eq(amount: '0.00018',
|
||||
currency_id: 'eth',
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: txid,
|
||||
status: 'pending',
|
||||
options: {"gas_limit"=>21000, "gas_price"=>1000000000})
|
||||
end
|
||||
|
||||
context 'erc20_contract_address is not configured properly in currency' do
|
||||
it 'returns empty array' do
|
||||
currency = trst.to_blockchain_api_settings.deep_dup
|
||||
currency[:options].delete(:erc20_contract_address)
|
||||
expect(wallet.prepare_deposit_collection!(transaction, spread_deposit, currency)).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context '#calculate_gas_price' do
|
||||
let(:gas_price) { 1_000_000_000 }
|
||||
let(:gas_price_hex) { '0x' + gas_price.to_s(16) }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: fee_wallet.to_wallet_api_settings,
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:eth_GasPrice) do
|
||||
{
|
||||
"jsonrpc": '2.0',
|
||||
"id": 1,
|
||||
"method": 'eth_gasPrice',
|
||||
"params": []
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
stub_request(:post, uri)
|
||||
.with(body: eth_GasPrice.to_json)
|
||||
.to_return(body: { result: gas_price_hex,
|
||||
error: nil,
|
||||
id: 1 }.to_json)
|
||||
end
|
||||
|
||||
it do
|
||||
options = { gas_price: 'standard' }
|
||||
expect(wallet.send(:calculate_gas_price, options)).to eq gas_price
|
||||
end
|
||||
|
||||
it do
|
||||
options = { gas_price: 'fast' }
|
||||
expect(wallet.send(:calculate_gas_price, options)).to eq gas_price * 1.1
|
||||
end
|
||||
|
||||
it do
|
||||
options = { gas_price: 'safelow' }
|
||||
expect(wallet.send(:calculate_gas_price, options)).to eq gas_price * 0.9
|
||||
end
|
||||
|
||||
it do
|
||||
options = { gas_price: 12_346_789.to_s(16) }
|
||||
expect(wallet.send(:calculate_gas_price, options)).to eq gas_price
|
||||
end
|
||||
|
||||
it do
|
||||
expect(wallet.send(:calculate_gas_price)).to eq gas_price
|
||||
end
|
||||
|
||||
it do
|
||||
expect(wallet.send(:calculate_gas_price, {})).to eq gas_price
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance_of_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:hot_wallet_trst) { Wallet.joins(:currencies).find_by(currencies: { id: :trst }, kind: :hot) }
|
||||
let(:hot_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :hot) }
|
||||
|
||||
let(:response1) do
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
result: '0x71a5c4e9fe8a100',
|
||||
id: 1
|
||||
}
|
||||
end
|
||||
|
||||
let(:response2) do
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
result: '0x7a120',
|
||||
id: 1
|
||||
}
|
||||
end
|
||||
|
||||
let(:settings1) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: 'http://127.0.0.1:8545' },
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:settings2) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: 'http://127.0.0.1:8545' },
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_getBalance,
|
||||
params:
|
||||
%w[
|
||||
something
|
||||
latest
|
||||
] }.to_json)
|
||||
.to_return(body: response1.to_json)
|
||||
|
||||
stub_request(:post, 'http://127.0.0.1:8545')
|
||||
.with(body: { jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: :eth_call,
|
||||
params:
|
||||
[
|
||||
{
|
||||
to: '0x87099add3bcc0821b5b151307c147215f839a110',
|
||||
data: '0x70a082310000000000000000000000000000000000000000000000000000000something'
|
||||
},
|
||||
'latest'
|
||||
] }.to_json)
|
||||
.to_return(body: response2.to_json)
|
||||
end
|
||||
|
||||
it 'requests rpc eth_getBalance and get balance' do
|
||||
wallet.configure(settings1)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.51182300042'.to_d)
|
||||
end
|
||||
|
||||
it 'requests rpc eth_call and get token balance' do
|
||||
wallet.configure(settings2)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.5'.to_d)
|
||||
end
|
||||
end
|
||||
end
|
||||
19
spec/lib/influxdb_spec.rb
Normal file
19
spec/lib/influxdb_spec.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe Peatio::InfluxDB do
|
||||
context 'host sharding' do
|
||||
before do
|
||||
Peatio::InfluxDB.instance_variable_set(:@clients, {})
|
||||
Peatio::InfluxDB.stubs(:config).returns({ host: %w[inflxudb-0 inflxudb-1] })
|
||||
end
|
||||
|
||||
after do
|
||||
Peatio::InfluxDB.instance_variable_set(:@clients, {})
|
||||
end
|
||||
|
||||
it do
|
||||
expect(Peatio::InfluxDB.client(keyshard: 'btcusd').config.hosts).to eq(['inflxudb-1'])
|
||||
expect(Peatio::InfluxDB.client(keyshard: 'ethusd').config.hosts).to eq(['inflxudb-0'])
|
||||
end
|
||||
end
|
||||
end
|
||||
186
spec/lib/opendax_cloud/wallet_spec.rb
Normal file
186
spec/lib/opendax_cloud/wallet_spec.rb
Normal file
@@ -0,0 +1,186 @@
|
||||
describe OpendaxCloud::Wallet do
|
||||
let(:wallet) { OpendaxCloud::Wallet.new }
|
||||
|
||||
before(:each) { ENV['PLATFORM_ID'] = 'opendax'}
|
||||
after(:each) { ENV.delete('PLATFORM_ID') }
|
||||
context :configure do
|
||||
let(:settings) { { wallet: {}, currency: {} } }
|
||||
it 'requires wallet' do
|
||||
expect { wallet.configure(settings.except(:wallet)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
expect { wallet.configure(settings.except(:currency)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'sets settings attribute' do
|
||||
wallet.configure(settings)
|
||||
expect(wallet.settings).to eq(settings.slice(*Ethereum::Wallet::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :create_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri
|
||||
},
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
address: '0xa57e810B5f96b049F9030AfE1f1004630818EC72',
|
||||
currency_id: 'eth',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'should create an address' do
|
||||
stub_request(:post, uri + '/address/new')
|
||||
.with(body: {currency_id: 'eth'}.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
|
||||
expect(result.as_json.symbolize_keys).to eq(address: uri_result[:address],
|
||||
details: uri_result.except(:address, :passphrase).with_indifferent_access)
|
||||
end
|
||||
end
|
||||
|
||||
context :create_transaction! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:deposit_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :deposit) }
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000' }
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(amount: 1.1.to_d, to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa')
|
||||
end
|
||||
|
||||
context 'eth transaction' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: {
|
||||
settings: deposit_wallet_eth.settings,
|
||||
uri: uri,
|
||||
address: deposit_wallet_eth.address,
|
||||
},
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
options: { 'tid': 'TIDDAD0E517F2' }
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
currency_id: 'eth',
|
||||
to: transaction.to_address,
|
||||
amount: transaction.amount.to_d,
|
||||
}
|
||||
end
|
||||
|
||||
it 'should create transaction' do
|
||||
stub_request(:post, uri + '/tx/send')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
options: {"tid"=>"TIDDAD0E517F2"},
|
||||
status: 'pending')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000' }
|
||||
|
||||
context 'eth load_balance' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri },
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'balance': 5.1
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
currency_id: 'eth',
|
||||
}
|
||||
end
|
||||
|
||||
it 'should return wallet balance' do
|
||||
stub_request(:post, uri + '/address/balance')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
wallet.configure(settings)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('5.1'.to_d)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
532
spec/lib/owhdwallet/wallet_spec.rb
Normal file
532
spec/lib/owhdwallet/wallet_spec.rb
Normal file
@@ -0,0 +1,532 @@
|
||||
describe OWHDWallet::Wallet do
|
||||
let(:wallet) { OWHDWallet::Wallet.new }
|
||||
|
||||
context :configure do
|
||||
let(:settings) { { wallet: {}, currency: {} } }
|
||||
it 'requires wallet' do
|
||||
expect { wallet.configure(settings.except(:wallet)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'requires currency' do
|
||||
expect { wallet.configure(settings.except(:currency)) }.to raise_error(Peatio::Wallet::MissingSettingError)
|
||||
|
||||
expect { wallet.configure(settings) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'sets settings attribute' do
|
||||
wallet.configure(settings)
|
||||
expect(wallet.settings).to eq(settings.slice(*Ethereum::Wallet::SUPPORTED_SETTINGS))
|
||||
end
|
||||
end
|
||||
|
||||
context :create_address! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000/api/v2/hdwallet' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri
|
||||
},
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
address: '0xa57e810B5f96b049F9030AfE1f1004630818EC72',
|
||||
coin_type: 'eth',
|
||||
passphrase: 'vault:v1:4vDyZo/zxr5C91vsVFxohE7HiX/rsQ==',
|
||||
wallet_index: 2
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'should create an address' do
|
||||
stub_request(:post, uri + '/wallet/new')
|
||||
.with(body: {coin_type: 'eth'}.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
|
||||
expect(result.as_json.symbolize_keys).to eq(address: uri_result[:address], secret: uri_result[:passphrase],
|
||||
details: uri_result.except(:address, :passphrase).with_indifferent_access)
|
||||
end
|
||||
|
||||
context 'erc-20' do
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri
|
||||
},
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'should create address for erc-20 coin' do
|
||||
stub_request(:post, uri + '/wallet/new')
|
||||
.with(body: {coin_type: 'eth'}.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_address!(uid: 'UID123')
|
||||
|
||||
expect(result.as_json.symbolize_keys).to eq(address: uri_result[:address], secret: uri_result[:passphrase],
|
||||
details: uri_result.except(:address, :passphrase).with_indifferent_access)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return error' do
|
||||
stub_request(:post, uri + '/wallet/new')
|
||||
.with(body: {coin_type: 'eth'}.to_json)
|
||||
.to_return(status: 422, body: {'error': 'wallet sealed'}.to_json)
|
||||
|
||||
expect { wallet.create_address!(uid: 'UID123') }.to raise_error(Peatio::Wallet::ClientError)
|
||||
end
|
||||
end
|
||||
|
||||
context :prepare_deposit_collection! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:url) { 'http://127.0.0.1:8000/api/v2/hdwallet' }
|
||||
|
||||
let(:fee_wallet) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :fee) }
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:spread_deposit) do
|
||||
[{ to_address: 'fake-hot',
|
||||
amount: '2.0',
|
||||
currency_id: trst.id },
|
||||
{ to_address: 'fake-hot',
|
||||
amount: '2.0',
|
||||
currency_id: trst.id }]
|
||||
end
|
||||
|
||||
let(:infura_url) { 'https://rinkeby.infura.io/v3/08825a23f9454f998e6e7ba60bb6f023' }
|
||||
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: {
|
||||
address: fee_wallet.address,
|
||||
uri: url,
|
||||
gateway_url: infura_url,
|
||||
wallet_index: 1,
|
||||
secret: 'changeme'
|
||||
},
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'eth',
|
||||
gas_limit: trst.options.fetch('gas_limit'),
|
||||
gas_speed: 'standard',
|
||||
spread_size: spread_deposit.size,
|
||||
to: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
gateway_url: infura_url,
|
||||
wallet_index: 1,
|
||||
passphrase: 'changeme'
|
||||
}
|
||||
end
|
||||
|
||||
let(:response) do
|
||||
{
|
||||
tx: '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017',
|
||||
gas_price: 1000000000
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(amount: 1.1.to_d, to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa')
|
||||
end
|
||||
|
||||
it do
|
||||
stub_request(:post, url + '/tx/before_collect')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: response.to_json)
|
||||
|
||||
result = wallet.prepare_deposit_collection!(transaction, spread_deposit, trst.to_blockchain_api_settings)
|
||||
expect(result.first.as_json.symbolize_keys).to eq(amount: '1.1',
|
||||
currency_id: 'eth',
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: '0xab6ada9608f4cebf799ee8be20fe3fb84b0d08efcdb0d962df45d6fce70cb017',
|
||||
status: 'pending',
|
||||
options: {"gas_limit"=>90000, "gas_price"=>1000000000})
|
||||
end
|
||||
|
||||
context 'erc20_contract_address is not configured properly in currency' do
|
||||
it 'returns empty array' do
|
||||
currency = trst.to_blockchain_api_settings.deep_dup
|
||||
currency[:options].delete(:erc20_contract_address)
|
||||
expect(wallet.prepare_deposit_collection!(transaction, spread_deposit, currency)).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :create_transaction! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:btc) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:deposit_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :deposit) }
|
||||
let(:hot_wallet_eth) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :hot) }
|
||||
let(:fee_wallet) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :fee) }
|
||||
let(:deposit_wallet_trst) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :deposit) }
|
||||
let(:hot_wallet_trst) { Wallet.joins(:currencies).find_by(currencies: { id: :eth }, kind: :hot) }
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000/api/v2/hdwallet' }
|
||||
|
||||
let(:transaction) do
|
||||
Peatio::Transaction.new(amount: 1.1.to_d, to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa')
|
||||
end
|
||||
|
||||
let(:infura_url) { 'https://rinkeby.infura.io/v3/08825a23f9454f998e6e7ba60bb6f023' }
|
||||
context 'eth transaction' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: {
|
||||
settings: deposit_wallet_eth.settings,
|
||||
uri: uri,
|
||||
address: deposit_wallet_eth.address,
|
||||
secret: 'changeme',
|
||||
wallet_index: 1,
|
||||
gateway_url: infura_url,
|
||||
},
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'tx': '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc'
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'eth',
|
||||
to: transaction.to_address,
|
||||
amount: (transaction.amount.to_d * eth.base_factor).to_i,
|
||||
gateway_url: infura_url,
|
||||
wallet_index: 1,
|
||||
passphrase: 'changeme',
|
||||
gas_speed: 'standard',
|
||||
subtract_fee: false,
|
||||
gas_limit: eth.options['gas_limit'],
|
||||
}
|
||||
end
|
||||
|
||||
it 'should create transaction' do
|
||||
stub_request(:post, uri + '/tx/send')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc',
|
||||
status: 'pending')
|
||||
end
|
||||
end
|
||||
|
||||
context 'erc20 transaction' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet: {
|
||||
settings: deposit_wallet_trst.settings,
|
||||
uri: uri,
|
||||
address: deposit_wallet_trst.address,
|
||||
secret: 'changeme',
|
||||
wallet_index: 1,
|
||||
gateway_url: infura_url,
|
||||
},
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'tx': '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc'
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'eth',
|
||||
to: transaction.to_address,
|
||||
amount: (transaction.amount.to_d * trst.base_factor).to_i,
|
||||
gateway_url: infura_url,
|
||||
wallet_index: 1,
|
||||
passphrase: 'changeme',
|
||||
gas_speed: 'standard',
|
||||
subtract_fee: false,
|
||||
contract_address: trst.options["erc20_contract_address"],
|
||||
gas_limit: trst.options['gas_limit']
|
||||
}
|
||||
end
|
||||
|
||||
it 'should create transaction' do
|
||||
stub_request(:post, uri + '/tx/send')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc',
|
||||
status: 'pending')
|
||||
end
|
||||
end
|
||||
|
||||
context 'btc transaction' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri,
|
||||
secret: 'changeme',
|
||||
wallet_index: 1,
|
||||
gateway_url: infura_url },
|
||||
currency: btc.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'tx': '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc'
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'btc',
|
||||
to: transaction.to_address,
|
||||
amount: (transaction.amount.to_d * btc.base_factor).to_i,
|
||||
gateway_url: infura_url,
|
||||
wallet_index: 1,
|
||||
passphrase: 'changeme'
|
||||
}
|
||||
end
|
||||
|
||||
it 'should create transaction' do
|
||||
stub_request(:post, uri + '/tx/send')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
result = wallet.create_transaction!(transaction)
|
||||
expect(result.as_json.symbolize_keys).to eq(amount: 1.1.to_s,
|
||||
to_address: '0x6d6cabaa7232d7f45b143b445114f7e92350a2aa',
|
||||
hash: '0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc',
|
||||
status: 'pending')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context :load_balance! do
|
||||
around do |example|
|
||||
WebMock.disable_net_connect!
|
||||
example.run
|
||||
WebMock.allow_net_connect!
|
||||
end
|
||||
|
||||
let(:eth) do
|
||||
Currency.find_by(id: :eth)
|
||||
end
|
||||
|
||||
let(:btc) do
|
||||
Currency.find_by(id: :btc)
|
||||
end
|
||||
|
||||
let(:trst) do
|
||||
Currency.find_by(id: :trst)
|
||||
end
|
||||
|
||||
let(:uri) { 'http://127.0.0.1:8000/api/v2/hdwallet' }
|
||||
let(:infura_url) { 'https://rinkeby.infura.io/v3/08825a23f9454f998e6e7ba60bb6f023' }
|
||||
|
||||
context 'eth load_balance' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri,
|
||||
gateway_url: infura_url},
|
||||
currency: eth.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'balance': 5000000000000000000
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'eth',
|
||||
gateway_url: infura_url,
|
||||
address: 'something'
|
||||
}
|
||||
end
|
||||
|
||||
it 'should return wallet balance' do
|
||||
stub_request(:post, uri + '/wallet/balance')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
wallet.configure(settings)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('5'.to_d)
|
||||
end
|
||||
end
|
||||
|
||||
context 'erc-20 load balance' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri,
|
||||
gateway_url: infura_url},
|
||||
currency: trst.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'eth',
|
||||
gateway_url: infura_url,
|
||||
address: 'something',
|
||||
contract_address: trst.options['erc20_contract_address']
|
||||
}
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'balance': 100000
|
||||
}
|
||||
end
|
||||
|
||||
it 'should return wallet balance' do
|
||||
stub_request(:post, uri + '/wallet/balance')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
wallet.configure(settings)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('0.1'.to_d)
|
||||
end
|
||||
end
|
||||
|
||||
context 'btc load balance' do
|
||||
let(:settings) do
|
||||
{
|
||||
wallet:
|
||||
{ address: 'something',
|
||||
uri: uri,
|
||||
gateway_url: infura_url},
|
||||
currency: btc.to_blockchain_api_settings
|
||||
}
|
||||
end
|
||||
|
||||
let(:uri_result) do
|
||||
{
|
||||
'balance': 23.2
|
||||
}
|
||||
end
|
||||
|
||||
let(:request_params) do
|
||||
{
|
||||
coin_type: 'btc',
|
||||
gateway_url: infura_url,
|
||||
address: 'something',
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
wallet.configure(settings)
|
||||
end
|
||||
|
||||
it 'should return wallet balance' do
|
||||
stub_request(:post, uri + '/wallet/balance')
|
||||
.with(body: request_params.to_json)
|
||||
.to_return(body: uri_result.to_json)
|
||||
|
||||
wallet.configure(settings)
|
||||
result = wallet.load_balance!
|
||||
expect(result).to be_a(BigDecimal)
|
||||
expect(result).to eq('23.2'.to_d)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
spec/lib/peatio/airdrop_spec.rb
Normal file
43
spec/lib/peatio/airdrop_spec.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Peatio::Airdrop do
|
||||
let(:test_file) { File.read(Rails.root.join('spec', 'resources', 'airdrops', file_name)) }
|
||||
let(:file_name) { '1.csv' }
|
||||
let(:src_user) { create(:member, role: :admin) }
|
||||
let(:uids) { %w[UID120 UID121 UID122 UID123 UID124] }
|
||||
|
||||
before do
|
||||
deposit = create(:deposit_btc, member: src_user, amount: 1000)
|
||||
deposit.accept!
|
||||
deposit.process!
|
||||
deposit.dispatch!
|
||||
uids.each do |uid|
|
||||
create(:member, uid: uid)
|
||||
end
|
||||
end
|
||||
|
||||
subject(:airdop) { Peatio::Airdrop.new.process(src_user, { file: { tempfile: test_file } }) }
|
||||
|
||||
it 'credited all users' do
|
||||
expect { airdop }.to change { Transfer.count }.by(5)
|
||||
uids.each do |uid|
|
||||
member = Member.find_by_uid(uid)
|
||||
expect(member.get_account(:btc).balance).to eq 100
|
||||
end
|
||||
end
|
||||
|
||||
it 'doesnt create for unexisting user' do
|
||||
Member.last.delete
|
||||
expect { airdop }.to change { Transfer.count }.by(4)
|
||||
end
|
||||
|
||||
it 'doesnt create transfers if src_user has insufficient funds (enough for the part of airdrop)' do
|
||||
src_user.get_account(:btc).update(balance: 200)
|
||||
expect { airdop }.to change { Transfer.count }.by(0)
|
||||
end
|
||||
|
||||
it 'doesnt create transfers if src_user has insufficient funds (zero balance)' do
|
||||
src_user.get_account(:btc).update(balance: 0)
|
||||
expect { airdop }.to change { Transfer.count }.by(0)
|
||||
end
|
||||
end
|
||||
55
spec/lib/peatio/opendax_spec.rb
Normal file
55
spec/lib/peatio/opendax_spec.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Peatio::Upstream::Opendax do
|
||||
let(:upstream_opendax_config) do
|
||||
{
|
||||
"driver": 'opendax',
|
||||
"source": 'btcusd',
|
||||
"target": 'btcusd',
|
||||
"rest": 'http://localhost',
|
||||
"websocket": 'wss://localhost'
|
||||
}.stringify_keys
|
||||
end
|
||||
|
||||
let(:opendax) { Peatio::Upstream::Opendax.new(upstream_opendax_config) }
|
||||
|
||||
let(:msg) do
|
||||
{
|
||||
'btcusd.trades' =>
|
||||
{ 'trades' =>
|
||||
[{ 'tid' => 247_646_537,
|
||||
'taker_type' => 'buy',
|
||||
'date' => 1_584_437_804,
|
||||
'price' => '5194.0',
|
||||
'amount' => '0.01710500' }] }
|
||||
}
|
||||
end
|
||||
|
||||
let(:subscribe_msg) do
|
||||
{
|
||||
'success' =>
|
||||
{ 'message' => 'subscribed',
|
||||
'streams' => ['btcusd.trades'] }
|
||||
}
|
||||
end
|
||||
|
||||
let(:trade) do
|
||||
{
|
||||
tid: 247_646_537,
|
||||
amount: '0.01710500',
|
||||
price: '5194.0',
|
||||
date: 1_584_437_804,
|
||||
taker_type: 'buy'
|
||||
}.stringify_keys
|
||||
end
|
||||
|
||||
it 'detects trade' do
|
||||
opendax.expects(:notify_public_trade).with(trade)
|
||||
opendax.ws_read_public_message(msg)
|
||||
end
|
||||
|
||||
it 'doesnt notify about public trade' do
|
||||
opendax.expects(:notify_public_trade).never
|
||||
opendax.ws_read_public_message(subscribe_msg)
|
||||
end
|
||||
end
|
||||
49
spec/lib/tasks/job_spec.rb
Normal file
49
spec/lib/tasks/job_spec.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe 'job.rake' do
|
||||
context "Rake::Task['job:liabilities:compact_orders']" do
|
||||
let(:member1) { create(:member) }
|
||||
let(:member2) { create(:member) }
|
||||
let(:job) { Job.last }
|
||||
let!(:liabilities) do
|
||||
(0..9).to_a.reverse.each do |day|
|
||||
100.times { create(:liability, member_id: [member1.id, member2.id].sample, created_at: Time.now - day.day, updated_at: Time.now - day.day) }
|
||||
end
|
||||
end
|
||||
|
||||
subject { Rake::Task['job:liabilities:compact_orders'] }
|
||||
|
||||
before(:each) { DatabaseCleaner.clean }
|
||||
after(:each) do
|
||||
DatabaseCleaner.strategy = :truncation
|
||||
subject.reenable
|
||||
end
|
||||
|
||||
it 'call rake task with default time range', clean_database_with_truncation: true do
|
||||
# default values
|
||||
min = (Time.now - 1.week).beginning_of_day.to_s(:db)
|
||||
max = (Time.now - 6.day).beginning_of_day.to_s(:db)
|
||||
|
||||
counter = Operations::Liability.where("LOWER(reference_type) = LOWER('Order') AND created_at BETWEEN '#{min}' AND '#{max}'").count
|
||||
result = ActiveRecord::Base.connection.query("SELECT NULL, code, currency_id, member_id, SUM(debit), SUM(credit) FROM liabilities WHERE (LOWER(reference_type) = LOWER('Order') AND created_at BETWEEN '#{min}' AND '#{max}') GROUP BY code, member_id, currency_id, DATE(created_at)")
|
||||
subject.invoke
|
||||
expect(job.name).to eq('compact_orders')
|
||||
expect(job.counter).to eq(counter)
|
||||
expect(job.error_code).to eq(0)
|
||||
expect(Operations::Liability.where(reference_type: 'CompactOrders').count).to eq(result.count)
|
||||
end
|
||||
|
||||
it 'call rake task with time range', clean_database_with_truncation: true do
|
||||
min = (Time.now - 2.day).beginning_of_day.to_s(:db)
|
||||
max = (Time.now - 1.day).beginning_of_day.to_s(:db)
|
||||
|
||||
counter = Operations::Liability.where("LOWER(reference_type) = LOWER('Order') AND created_at BETWEEN '#{min}' AND '#{max}'").count
|
||||
result = ActiveRecord::Base.connection.query("SELECT NULL, code, currency_id, member_id, SUM(debit), SUM(credit) FROM liabilities WHERE (LOWER(reference_type) = LOWER('Order') AND created_at BETWEEN '#{min}' AND '#{max}') GROUP BY code, member_id, currency_id, DATE(created_at)")
|
||||
subject.invoke(min, max)
|
||||
expect(job.name).to eq('compact_orders')
|
||||
expect(job.counter).to eq(counter)
|
||||
expect(job.error_code).to eq(0)
|
||||
expect(Operations::Liability.where(reference_type: 'CompactOrders').count).to eq(result.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
123
spec/lib/tasks/revert_spec.rb
Normal file
123
spec/lib/tasks/revert_spec.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe 'revert.rake' do
|
||||
let(:bob) { create(:member, :level_3, email: 'bob@gmail.com') }
|
||||
let(:alice) { create(:member, :level_3, email: 'alice@gmail.com') }
|
||||
let(:bob_accounting_balance) { Operations::Liability.where(member_id: bob.id).sum(:credit) - Operations::Liability.where(member_id: bob.id).sum(:debit) }
|
||||
let(:alice_accounting_balance) { Operations::Liability.where(member_id: alice.id).sum(:credit) - Operations::Liability.where(member_id: alice.id).sum(:debit) }
|
||||
let(:bob_legacy_balance) { bob.get_account(:usd).balance }
|
||||
let(:alice_legacy_balance) { alice.get_account(:btc).balance }
|
||||
subject { Rake::Task['revert:trading_activity'] }
|
||||
|
||||
after(:each) { subject.reenable }
|
||||
|
||||
context 'simple case' do
|
||||
let(:price) { 10.to_d }
|
||||
let(:volume) { 5.to_d }
|
||||
let(:ask) { ::Matching::LimitOrder.new create(:order_ask, :btcusd, price: price, volume: volume, member: alice).to_matching_attributes }
|
||||
let(:bid) { ::Matching::LimitOrder.new create(:order_bid, :btcusd, price: price, volume: volume, member: bob).to_matching_attributes }
|
||||
|
||||
let(:executor) do
|
||||
Matching::Executor.new(
|
||||
action: 'execute',
|
||||
trade: {
|
||||
market_id: 'btcusd',
|
||||
maker_order_id: ask.id,
|
||||
taker_order_id: bid.id,
|
||||
strike_price: price.to_s('F'),
|
||||
amount: volume.to_s('F'),
|
||||
total: (price * volume).to_s('F')
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
# Deposit 5 btc to Alice
|
||||
# Deposit 50 usd to Bob
|
||||
deposit = create(:deposit_btc, member_id: alice.id, amount: 5)
|
||||
deposit.accept!
|
||||
deposit.process!
|
||||
deposit.dispatch!
|
||||
create(:deposit_usd, member_id: bob.id, amount: 50).accept!
|
||||
alice.get_account(:btc).lock_funds(5)
|
||||
bob.get_account(:usd).lock_funds(50)
|
||||
executor.execute!
|
||||
end
|
||||
|
||||
it 'revert trading activities' do
|
||||
subject.invoke(bob.email)
|
||||
expect(alice_legacy_balance).to eq(5)
|
||||
expect(bob_legacy_balance).to eq(50)
|
||||
expect(Operations.validate_accounting_equation(Operations::Liability.all +
|
||||
Operations::Asset.all +
|
||||
Operations::Revenue.all +
|
||||
Operations::Expense.all)).to eq(true)
|
||||
expect(alice_legacy_balance).to eq(alice_accounting_balance)
|
||||
expect(bob_legacy_balance).to eq(bob_accounting_balance)
|
||||
end
|
||||
end
|
||||
|
||||
context 'several trades' do
|
||||
let(:price) { 10.to_d }
|
||||
let(:volume) { 5.to_d }
|
||||
let(:volume1) { 3.to_d }
|
||||
let(:volume2) { 2.to_d }
|
||||
let(:ask) { ::Matching::LimitOrder.new create(:order_ask, :btcusd, price: price, volume: volume, member: alice).to_matching_attributes }
|
||||
let(:bid) { ::Matching::LimitOrder.new create(:order_bid, :btcusd, price: price, volume: volume1, member: bob).to_matching_attributes }
|
||||
let(:bid1) { ::Matching::LimitOrder.new create(:order_bid, :btcusd, price: price, volume: volume2, member: bob).to_matching_attributes }
|
||||
|
||||
let(:executor1) do
|
||||
Matching::Executor.new(
|
||||
action: 'execute',
|
||||
trade: {
|
||||
market_id: 'btcusd',
|
||||
maker_order_id: ask.id,
|
||||
taker_order_id: bid.id,
|
||||
strike_price: price.to_s('F'),
|
||||
amount: volume1.to_s('F'),
|
||||
total: (price * volume1).to_s('F')
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let(:executor2) do
|
||||
Matching::Executor.new(
|
||||
action: 'execute',
|
||||
trade: {
|
||||
market_id: 'btcusd',
|
||||
maker_order_id: ask.id,
|
||||
taker_order_id: bid1.id,
|
||||
strike_price: price.to_s('F'),
|
||||
amount: volume2.to_s('F'),
|
||||
total: (price * volume2).to_s('F')
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
# Deposit 5 btc to Alice
|
||||
# Deposit 50 usd to Bob
|
||||
deposit = create(:deposit_btc, member_id: alice.id, amount: 5)
|
||||
deposit.accept!
|
||||
deposit.process!
|
||||
deposit.dispatch!
|
||||
create(:deposit_usd, member_id: bob.id, amount: 50).accept!
|
||||
alice.get_account(:btc).lock_funds(5)
|
||||
bob.get_account(:usd).lock_funds(50)
|
||||
executor1.execute!
|
||||
executor2.execute!
|
||||
end
|
||||
|
||||
it 'reverts trading activities' do
|
||||
subject.invoke(bob.email)
|
||||
expect(alice_legacy_balance).to eq(5)
|
||||
expect(bob_legacy_balance).to eq(50)
|
||||
expect(Operations.validate_accounting_equation(Operations::Liability.all +
|
||||
Operations::Asset.all +
|
||||
Operations::Revenue.all +
|
||||
Operations::Expense.all)).to eq(true)
|
||||
expect(alice_legacy_balance).to eq(alice_accounting_balance)
|
||||
expect(bob_legacy_balance).to eq(bob_accounting_balance)
|
||||
end
|
||||
end
|
||||
end
|
||||
22
spec/lib/uuid_spec.rb
Normal file
22
spec/lib/uuid_spec.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe UUID do
|
||||
let(:uuid) { UUID.generate }
|
||||
let(:invalid_uuid) { "bcfbd6f6-8c-4bcd-b76a-ed68a01347a5" }
|
||||
let(:type) { UUID::Type.new }
|
||||
let(:correct_uuid) { UUID.validate(uuid) }
|
||||
let(:incorrect_uuid) { UUID.validate(invalid_uuid) }
|
||||
|
||||
it 'seizlizes and deserializes correctly' do
|
||||
bytes = type.serialize(uuid)
|
||||
|
||||
expect(bytes.bytesize).to eq 16
|
||||
expect(type.deserialize(bytes)).to eq uuid
|
||||
end
|
||||
|
||||
it 'validates uuid correctly' do
|
||||
expect(correct_uuid).to be_truthy
|
||||
expect(incorrect_uuid).to be_falsy
|
||||
end
|
||||
end
|
||||
52
spec/lib/vault_totp_spec.rb
Normal file
52
spec/lib/vault_totp_spec.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe Vault::TOTP do
|
||||
let(:uid) { 'uid' }
|
||||
let(:email) { 'email' }
|
||||
|
||||
describe '.with_human_error' do
|
||||
it 'renders human error when vault is down' do
|
||||
expect do
|
||||
described_class.with_human_error do
|
||||
raise Vault::VaultError, 'Message connection refused message'
|
||||
end
|
||||
end.to raise_error(described_class::Error, '2FA server is under maintenance')
|
||||
end
|
||||
|
||||
it 'renders human error when code was used twice' do
|
||||
expect do
|
||||
described_class.with_human_error do
|
||||
raise Vault::VaultError, 'Message code already used message'
|
||||
end
|
||||
end.to raise_error(described_class::Error,
|
||||
'This code was already used. Wait until the next time period')
|
||||
end
|
||||
|
||||
it 'renders error when called without block' do
|
||||
expect do
|
||||
described_class.with_human_error
|
||||
end.to raise_error(ArgumentError, 'Block is required')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.validate?' do
|
||||
before do
|
||||
described_class.stubs(:write_data).returns( OpenStruct.new({data: data}) )
|
||||
described_class.stubs(:read_data).returns( OpenStruct.new({data: data}) )
|
||||
end
|
||||
let(:data) { { valid: true } }
|
||||
|
||||
subject { described_class.validate?(uid, 'code') }
|
||||
|
||||
context 'when valid' do
|
||||
before { described_class.stubs(:exist?).returns( true ) }
|
||||
it { is_expected.to eq true }
|
||||
end
|
||||
|
||||
context 'when invalid' do
|
||||
before { described_class.stubs(:exist?).returns( true ) }
|
||||
let(:data) { { valid: false } }
|
||||
it { is_expected.to eq false }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user