# frozen_string_literal: true describe Deposit do let(:member) { create(:member) } let(:amount) { 100.to_d } let(:deposit) { create(:deposit_usd, member: member, amount: amount, currency: currency) } let(:currency) { Currency.find(:usd) } it 'computes fee' do expect(deposit.fee).to eql 0.to_d expect(deposit.amount).to eql 100.to_d end context 'fee is set to fixed value of 10' do before { Currency.any_instance.expects(:deposit_fee).once.returns(10) } it 'computes fee' do expect(deposit.fee).to eql 10.to_d expect(deposit.amount).to eql 90.to_d end end context 'fee exceeds amount' do before { Currency.any_instance.expects(:deposit_fee).once.returns(1.1) } let(:amount) { 1 } let(:deposit) { build(:deposit_usd, member: member, amount: amount, currency: currency) } it 'fails validation' do expect(deposit.save).to eq false expect(deposit.errors.full_messages).to eq ['Amount must be greater than 0.0', 'Amount must be greater than or equal to 0.0'] end end it 'automatically generates TID if it is blank' do expect(create(:deposit_btc).tid).not_to be_blank end it 'doesn\'t generate TID if it is not blank' do expect(create(:deposit_btc, tid: 'TID1234567890xyz').tid).to eq 'TID1234567890xyz' end it 'validates uniqueness of TID' do record1 = create(:deposit_btc) record2 = build(:deposit_btc, tid: record1.tid) record2.save expect(record2.errors.full_messages.first).to match(/tid has already been taken/i) end it 'uppercases automatically generated TID' do record = create(:deposit_btc) expect(record.tid).to eq record.tid.upcase end context 'calculates confirmations' do let(:deposit) { create(:deposit_btc) } it 'uses height from blockchain by default' do deposit.blockchain.stubs(:processed_height).returns(100) deposit.stubs(:block_number).returns(90) expect(deposit.confirmations).to eql(10) end end context :spread_between_wallets! do let(:spread) do [Peatio::Transaction.new(to_address: 'to-address-1', amount: 1.2), Peatio::Transaction.new(to_address: 'to-address-2', amount: 2.5)] end let(:deposit) { create(:deposit_btc, amount: 3.7) } before do WalletService.any_instance.expects(:spread_deposit).returns(spread) end it 'spreads deposit between wallets' do expect(deposit.spread).to eq([]) expect(deposit.spread_between_wallets!).to be_truthy expect(deposit.reload.spread).to eq(spread.map(&:as_json).map(&:symbolize_keys)) expect(deposit.spread_between_wallets!).to be_falsey end end context :accept do context :record_complete_operations! do subject { deposit } it 'creates single asset operation' do expect { subject.accept! }.to change { Operations::Asset.count }.by(1) end it 'credits assets with correct amount' do subject.accept! asset_operation = Operations::Asset.find_by(reference: subject) expect(asset_operation.credit).to eq(subject.amount) end it 'creates single liability operation' do expect { subject.accept! }.to change { Operations::Liability.count }.by(1) end it 'credits liabilities with correct amount' do subject.accept! liability_operation = Operations::Liability.find_by(reference: subject) expect(liability_operation.credit).to eq(subject.amount) end context 'zero deposit fee' do it 'doesn\'t create revenue operation' do expect { subject.accept! }.to_not change { Operations::Revenue.count } end end context 'greater than zero deposit fee' do let(:currency) do Currency.find(:usd).tap { |c| c.update(deposit_fee: 0.01) } end let(:deposit) do create(:deposit_usd, member: member, amount: amount, currency: currency) end it 'creates single revenue operation' do expect { subject.accept! }.to change { Operations::Revenue.count }.by(1) end it 'credits revenues with fee amount' do subject.accept! revenue_operation = Operations::Revenue.find_by(reference: subject) expect(revenue_operation.credit).to eq(subject.fee) end it 'creates revenue from member' do expect { subject.accept! }.to change { Operations::Revenue.where(member: member).count }.by(1) end end it 'credits both legacy and operations based member balance for fiat deposit' do subject.accept! %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) end end context 'coin deposit' do context 'without locked funds' do context 'credits both legacy and operations based member balance for coin deposit' do subject { create(:deposit_btc, amount: 3.7) } it do subject.accept! %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) end expect(subject.member.balance_for(currency: subject.currency, kind: :locked)).to eq 0 expect(subject.member.balance_for(currency: subject.currency, kind: :main)).to eq 3.7 # Transfer funds directly to main account on deposit accepted step (1 liability) liabilities = Operations::Liability.where(currency_id: subject.currency.id, member_id: subject.member.id, reference_type: 'Deposit') expect(liabilities.count).to eq 1 account = Operations::Account.find_by(kind: :main, currency_type: subject.currency.type, type: 'liability') expect(liabilities.first.code).to eq account.code end end end context 'with locked funds' do before { Peatio::App.config.stubs(:deposit_funds_locked).returns(true) } context 'credits both legacy and operations based member balance for coin deposit' do subject { create(:deposit_btc, amount: 3.7) } it do subject.accept! %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) end expect(subject.member.balance_for(currency: subject.currency, kind: :locked)).to eq 3.7 expect(subject.member.balance_for(currency: subject.currency, kind: :main)).to eq 0 # Lock funds on deposit accepted step (1 liability) liabilities = Operations::Liability.where(currency_id: subject.currency.id, member_id: subject.member.id, reference_type: 'Deposit') expect(liabilities.count).to eq 1 account = Operations::Account.find_by(kind: :locked, currency_type: subject.currency.type, type: 'liability', scope: 'member') expect(liabilities.first.code).to eq account.code end end end end end end context :process do let(:crypto_deposit) { create(:deposit_btc, amount: 3.7) } it 'doesnt process fiat deposit' do deposit.accept! expect(deposit.process!).to eq false end it 'process coin deposit' do crypto_deposit = create(:deposit_btc, amount: 3.7) crypto_deposit.accept! expect(crypto_deposit.process!).to eq true expect(crypto_deposit.processing?).to eq true end end context :processing do let(:crypto_deposit) { create(:deposit_btc, amount: 3.7) } before do Peatio::App.config.stubs(:deposit_funds_locked).returns(true) crypto_deposit.accept! crypto_deposit.process! end subject { crypto_deposit } it 'commit deposit to collected' do # Minus locked and plus main accounts expect { subject.dispatch! }.to change { Operations::Liability.count }.by(2) %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) end expect(crypto_deposit.aasm_state).to eq('collected') end it 'dispatch deposit to skipped' do # Minus locked and plus main accounts expect { subject.skip! }.to change { Operations::Liability.count }.by(0) %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) end expect(crypto_deposit.aasm_state).to eq('skipped') end end context :dispatch do let(:crypto_deposit) { create(:deposit_btc, amount: 3.7) } subject { crypto_deposit } context 'with locked funds' do before do Peatio::App.config.stubs(:deposit_funds_locked).returns(true) crypto_deposit.accept! crypto_deposit.process! end it 'dispatches deposit' do subject.dispatch! %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) liabilities = Operations::Liability.where(currency_id: subject.currency.id, member_id: subject.member.id) expect(liabilities.count).to eq 3 # Lock funds on deposit accepted step (1 liability) account = Operations::Account.find_by(kind: :locked, currency_type: subject.currency.type, type: 'liability', scope: 'member') expect(liabilities.first.code).to eq account.code # Moved funds from locked to main (2 liabilities) # debit account = Operations::Account.find_by(kind: :locked, currency_type: subject.currency.type, type: 'liability', scope: 'member') expect(liabilities.second.code).to eq account.code # credit account = Operations::Account.find_by(kind: :main, currency_type: subject.currency.type, type: 'liability', scope: 'member') expect(liabilities.last.code).to eq account.code end end end context 'without locked funds' do before do crypto_deposit.accept! crypto_deposit.process! end it 'dispatches deposit' do subject.dispatch! %i[main locked].each do |kind| expect( subject.member.balance_for(currency: subject.currency, kind: kind) ).to eq( subject.member.legacy_balance_for(currency: subject.currency, kind: kind) ) # There is only one liability with transferring funds directly to main account liabilities = Operations::Liability.where(currency_id: subject.currency.id, member_id: subject.member.id, reference_type: 'Deposit') expect(liabilities.count).to eq 1 account = Operations::Account.find_by(kind: :main, currency_type: subject.currency.type, type: 'liability') expect(account.code).to eq liabilities.first.code end end end end end