Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

View File

@@ -0,0 +1,777 @@
# encoding: UTF-8
# frozen_string_literal: true
describe WalletService do
let!(:blockchain) { create(:blockchain, 'fake-testnet') }
let!(:currency) { create(:currency, :fake) }
let(:wallet) { create(:wallet, :fake_hot) }
let(:member) { create(:member) }
let(:fake_wallet_adapter) { FakeWallet.new }
let(:fake_blockchain_adapter) { FakeBlockchain.new }
let(:service) { WalletService.new(wallet) }
before do
Peatio::Blockchain.registry.expects(:[])
.with(:fake)
.returns(fake_blockchain_adapter.class)
.at_least_once
Peatio::Wallet.registry.expects(:[])
.with(:fake)
.returns(fake_wallet_adapter.class)
.at_least_once
Blockchain.any_instance.stubs(:blockchain_api).returns(BlockchainService.new(blockchain))
end
context :create_address! do
let(:account) { create(:member, :level_3, :barong).get_account(currency) }
let(:blockchain_address) do
{ address: :fake_address,
secret: :changeme,
details: { uid: account.member.uid } }
end
before do
service.adapter.expects(:create_address!).returns(blockchain_address)
end
it 'creates address' do
expect(service.create_address!(account, nil)).to eq blockchain_address
end
end
context :build_withdrawal! do
let(:withdrawal) { create(:btc_withdraw, rid: 'fake-address', amount: 100, currency: currency, member: member) }
let(:transaction) do
Peatio::Transaction.new(hash: '0xfake',
to_address: withdrawal.rid,
amount: withdrawal.amount,
currency_id: currency.id)
end
before do
member.get_account(currency).update!(balance: 1000)
service.adapter.expects(:create_transaction!).returns(transaction)
end
it 'sends withdrawal' do
expect(service.build_withdrawal!(withdrawal)).to eq transaction
end
end
context :spread_between_wallets do
# Single wallet:
# * Deposit fits exactly.
# * Deposit doesn't fit.
# Two wallets:
# * Deposit fits to first wallet.
# * Deposit fits to second wallet.
# * Partial spread between first and second.
# * Deposit doesn't fit to both wallets.
# * Negative min_collection_amount.
# Three wallets:
# * Partial spread between first and second.
# * Partial spread between first and third.
# * Partial spread between first, second and third.
# * Deposit doesn't fit to all wallets.
let(:deposit) { Deposit.new(amount: 1.2, currency_id: :fake) }
context 'Single wallet' do
context 'single wallet available' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 8.8,
max_balance: 10,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: deposit.amount,
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to single wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'single wallet available + skip_deposit_collection' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 8.8,
max_balance: 10,
min_collection_amount: deposit.amount,
skip_deposit_collection: true }]
end
let(:expected_spread) do
[{:amount=>"1.2", :currency_id=>"fake", :status=>"skipped", :to_address=>"destination-wallet-1"}]
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'returns spread with skipped transaction' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
end
end
context 'Single wallet is full' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10,
max_balance: 10,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: deposit.amount,
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
end
context 'Two wallets' do
context 'Deposit fits to first wallet' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 5,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 100.0,
max_balance: 100,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: deposit.amount,
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Deposit fits to second wallet' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 95,
max_balance: 100,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-2',
status: 'pending',
amount: deposit.amount,
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Partial spread between first and second' do
let(:deposit) { Deposit.new(amount: 10, currency_id: :fake) }
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 5,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 90,
max_balance: 100,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: '5.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '5.0',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Two wallets are full' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 100,
max_balance: 100,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '1.2',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'different min_collection_amount' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 100,
max_balance: 100,
min_collection_amount: 2 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '1.2',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to single wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'tiny min_collection_amount' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10,
max_balance: 10,
min_collection_amount: 2 },
{ address: 'destination-wallet-2',
balance: 100,
max_balance: 100,
min_collection_amount: 3 }.as_json].map(&:symbolize_keys)
end
let(:expected_spread) { [] }
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to single wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
end
context 'Three wallets' do
context 'Partial spread between first and second' do
let(:deposit) { Deposit.new(amount: 10, currency_id: :fake) }
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 5,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 95,
max_balance: 100,
min_collection_amount: 1 },
{ address: 'destination-wallet-3',
balance: 1001.0,
max_balance: 1000,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: '5.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '5.0',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Partial spread between first and third' do
let(:deposit) { Deposit.new(amount: 10, currency_id: :fake) }
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 5,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 100,
max_balance: 100,
min_collection_amount: 1 },
{ address: 'destination-wallet-3',
balance: 995.0,
max_balance: 1000,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: '5.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-3',
status: 'pending',
amount: '5.0',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Three wallets are full' do
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 10.1,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 100.0,
max_balance: 100,
min_collection_amount: 1 },
{ address: 'destination-wallet-3',
balance: 1001.0,
max_balance: 1000,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-3',
status: 'pending',
amount: deposit.amount,
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads everything to last wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Partial spread between first, second and third' do
let(:deposit) { Deposit.new(amount: 10, currency_id: :fake) }
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 7,
max_balance: 10,
min_collection_amount: 1 },
{ address: 'destination-wallet-2',
balance: 97,
max_balance: 100,
min_collection_amount: 1 },
{ address: 'destination-wallet-3',
balance: 995.0,
max_balance: 1000,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'pending',
amount: '3.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '3.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-3',
status: 'pending',
amount: '4.0',
currency_id: currency.id }.as_json].map(&:symbolize_keys)
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads between wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Partial spread between first, second and third + skip deposit collection option' do
let(:deposit) { Deposit.new(amount: 10, currency_id: :fake) }
let(:destination_wallets) do
[{ address: 'destination-wallet-1',
balance: 7,
max_balance: 10,
min_collection_amount: 1,
skip_deposit_collection: true },
{ address: 'destination-wallet-2',
balance: 97,
max_balance: 100,
min_collection_amount: 1 },
{ address: 'destination-wallet-3',
balance: 995.0,
max_balance: 1000,
min_collection_amount: 1 }]
end
let(:expected_spread) do
[{ to_address: 'destination-wallet-1',
status: 'skipped',
amount: '3.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-2',
status: 'pending',
amount: '3.0',
currency_id: currency.id },
{ to_address: 'destination-wallet-3',
status: 'pending',
amount: '4.0',
currency_id: currency.id}]
end
subject { service.send(:spread_between_wallets, deposit, destination_wallets) }
it 'spreads between wallet' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
end
end
context :spread_deposit do
let!(:deposit_wallet) { create(:wallet, :fake_deposit) }
let!(:hot_wallet) { create(:wallet, :fake_hot) }
let!(:cold_wallet) { create(:wallet, :fake_cold) }
let(:service) { WalletService.new(deposit_wallet) }
let(:amount) { 2 }
let(:deposit) { create(:deposit_btc, amount: amount, currency: currency) }
let(:expected_spread) do
[{ to_address: 'fake-cold',
status: 'pending',
amount: '2.0',
currency_id: currency.id }]
end
subject { service.spread_deposit(deposit) }
context 'hot wallet is full and cold wallet balance is not available' do
before do
# Hot wallet balance is full and cold wallet balance is not available.
Wallet.any_instance.stubs(:current_balance).returns(hot_wallet.max_balance, 'N/A')
end
it 'spreads everything to cold wallet' do
expect(Wallet.active.withdraw.joins(:currencies).where(currencies: { id: deposit.currency_id }).count).to eq 2
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'hot wallet is full, warm and cold wallet balances are not available' do
let!(:warm_wallet) { create(:wallet, :fake_warm) }
before do
# Hot wallet is full, warm and cold wallet balances are not available.
Wallet.any_instance.stubs(:current_balance).returns(hot_wallet.max_balance, 'N/A', 'N/A')
end
it 'skips warm wallet and spreads everything to cold wallet' do
expect(Wallet.active.withdraw.joins(:currencies).where(currencies: { id: deposit.currency_id }).count).to eq 3
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'there is no active wallets' do
before { Wallet.stubs(:active).returns(Wallet.none) }
it 'raises an error' do
expect{ subject }.to raise_error(StandardError)
end
end
context 'currency price recalculation' do
let(:deposit) { create(:deposit_btc, amount: amount, currency: currency) }
context 'collect to hot wallet' do
let(:expected_spread) do
[{ to_address: 'fake-hot',
status: 'pending',
amount: deposit.amount.to_s,
currency_id: currency.id }]
end
before do
# Deposit with amount 2 and currency price 42
hot_wallet.update!(max_balance: 100)
deposit.currency.update!(price: 42)
# Hot wallet balance is empty
Wallet.any_instance.stubs(:current_balance).with(deposit.currency).returns(0)
Currency.any_instance.unstub(:price)
end
it 'skip hot wallet and collect to cold' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'collect to cold wallet' do
let(:expected_spread) do
[{ to_address: 'fake-cold',
status: 'pending',
amount: deposit.amount.to_s,
currency_id: currency.id }]
end
before do
# Hot wallet balance is ful.
deposit.currency.update!(price: 42)
Wallet.any_instance.stubs(:current_balance).with(deposit.currency).returns(deposit.amount)
Currency.any_instance.unstub(:price)
end
it 'skip hot wallet and collect to cold' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'split to hot and cold wallet' do
let(:expected_spread) do
[{ to_address: 'fake-hot',
status: 'pending',
amount: '1.38095238',
currency_id: currency.id },
{ to_address: 'fake-cold',
status: 'pending',
amount: '0.61904762',
currency_id: currency.id }]
end
before do
# Deposit with amount 2 and currency price 42
hot_wallet.update!(max_balance: 100)
# Hot wallet balance is full and cold wallet balance is not available.
deposit.currency.update!(price: 42)
Wallet.any_instance.stubs(:current_balance).with(deposit.currency).returns(deposit.amount / 2)
Currency.any_instance.unstub(:price)
end
it 'skip hot wallet and collect to cold' do
expect(subject.map(&:as_json).map(&:symbolize_keys)).to contain_exactly(*expected_spread)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
end
end
context :collect_deposit do
let!(:deposit_wallet) { create(:wallet, :fake_deposit) }
let!(:hot_wallet) { create(:wallet, :fake_hot) }
let!(:cold_wallet) { create(:wallet, :fake_cold) }
let(:amount) { 2 }
let(:deposit) { create(:deposit_btc, amount: amount, currency: currency) }
let(:fake_wallet_adapter) { FakeWallet.new }
let(:service) { WalletService.new(deposit_wallet) }
context 'Spread deposit with single entry' do
let(:spread_deposit) do
[Peatio::Transaction.new(to_address: 'fake-cold',
amount: '2.0',
currency_id: currency.id)]
end
let(:transaction) do
[Peatio::Transaction.new(hash: '0xfake',
to_address: cold_wallet.address,
amount: deposit.amount,
currency_id: currency.id)]
end
subject { service.collect_deposit!(deposit, spread_deposit) }
before do
service.adapter.expects(:create_transaction!).returns(transaction.first)
end
it 'creates single transaction' do
expect(subject).to contain_exactly(*transaction)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
context 'Spread deposit with two entry' do
let(:spread_deposit) do
[Peatio::Transaction.new(to_address: 'fake-hot',
amount: '2.0',
currency_id: currency.id),
Peatio::Transaction.new(to_address: 'fake-hot',
amount: '2.0',
currency_id: currency.id)]
end
let(:transaction) do
[{ hash: '0xfake',
to_address: hot_wallet.address,
amount: deposit.amount,
currency_id: currency.id },
{ hash: '0xfake1',
to_address: cold_wallet.address,
amount: deposit.amount,
currency_id: currency.id }].map { |t| Peatio::Transaction.new(t)}
end
subject { service.collect_deposit!(deposit, spread_deposit) }
before do
service.adapter.expects(:create_transaction!).with(spread_deposit.first, subtract_fee: true).returns(transaction.first)
service.adapter.expects(:create_transaction!).with(spread_deposit.second, subtract_fee: true).returns(transaction.second)
end
it 'creates two transactions' do
expect(subject).to contain_exactly(*transaction)
expect(subject).to all(be_a(Peatio::Transaction))
end
end
end
context :deposit_collection_fees do
let!(:fee_wallet) { create(:wallet, :fake_fee) }
let!(:deposit_wallet) { create(:wallet, :fake_deposit) }
let(:amount) { 2 }
let(:deposit) { create(:deposit_btc, amount: amount, currency: currency) }
let(:fake_wallet_adapter) { FakeWallet.new }
let(:service) { WalletService.new(fee_wallet) }
let(:spread_deposit) do
[Peatio::Transaction.new(to_address: 'fake-cold',
amount: '2.0',
currency_id: currency.id)]
end
let(:transactions) do
[Peatio::Transaction.new( hash: '0xfake',
to_address: deposit.address,
amount: '0.01',
currency_id: currency.id,
options: { gas_limit: 21_000, gas_price: 10_000_000_000 })]
end
subject { service.deposit_collection_fees!(deposit, spread_deposit) }
context 'Adapter collect fees for transaction' do
before do
deposit.update!(spread: spread_deposit.map(&:as_json))
service.adapter.expects(:prepare_deposit_collection!).returns(transactions)
end
it 'returns transaction' do
expect(subject).to contain_exactly(*transactions)
expect(subject).to all(be_a(Peatio::Transaction))
deposit.spread.map { |s| s.key?(:options) }
end
end
context "Adapter doesn't perform any actions before collect deposit" do
it 'retunrs empty array' do
expect(subject.blank?).to be true
end
end
end
end