Skip to content

Commit

Permalink
Implement ETH fee for ERC20 tokens #141
Browse files Browse the repository at this point in the history
- show fee in ETH
- use proper rate for fee
- fix rounding in confirmation controller
  • Loading branch information
mNizhurin committed Feb 12, 2019
1 parent a05fc4a commit 4aec902
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 54 deletions.
20 changes: 18 additions & 2 deletions BankWallet/BankWallet/Modules/Send/SendInteractor.swift
Expand Up @@ -97,9 +97,15 @@ extension SendInteractor: ISendInteractor {
}
if let coinValue = sendState.coinValue {
let feeValue = adapter.fee(for: coinValue.value, address: input.address)
sendState.feeCoinValue = CoinValue(coinCode: coinCode, value: feeValue)
sendState.feeCoinValue = CoinValue(coinCode: state.adapter.feeCoinCode ?? coinCode, value: feeValue)
}
if let rateValue = state.rateValue, let feeCoinValue = sendState.feeCoinValue {
let rateValue: Decimal?
if state.adapter.feeCoinCode != nil {
rateValue = state.feeRateValue
} else {
rateValue = state.rateValue
}
if let rateValue = rateValue, let feeCoinValue = sendState.feeCoinValue {
sendState.feeCurrencyValue = CurrencyValue(currency: baseCurrency, value: rateValue * feeCoinValue.value)
}

Expand Down Expand Up @@ -184,6 +190,16 @@ extension SendInteractor: ISendInteractor {
self?.delegate?.didUpdateRate()
})
.disposed(by: disposeBag)

if let feeCoinCode = state.adapter.feeCoinCode {
rateStorage.nonExpiredLatestRateValueObservable(forCoinCode: feeCoinCode, currencyCode: currencyManager.baseCurrency.code)
.take(1)
.subscribe(onNext: { [weak self] rateValue in
self?.state.feeRateValue = rateValue
self?.delegate?.didUpdateRate()
})
.disposed(by: disposeBag)
}
}

}
1 change: 1 addition & 0 deletions BankWallet/BankWallet/Modules/Send/SendModule.swift
Expand Up @@ -110,6 +110,7 @@ enum FeeError {
class SendInteractorState {
let adapter: IAdapter
var rateValue: Decimal?
var feeRateValue: Decimal?

init(adapter: IAdapter) {
self.adapter = adapter
Expand Down
Expand Up @@ -11,7 +11,7 @@ class SendConfirmationValueItem: BaseActionItem {
case .coinValue(let coinValue):
value = ValueFormatter.instance.format(coinValue: coinValue)
case .currencyValue(let currencyValue):
value = ValueFormatter.instance.format(currencyValue: currencyValue)
value = ValueFormatter.instance.format(currencyValue: currencyValue, roundingMode: .up)
}

super.init(cellType: SendConfirmationValueItemView.self, tag: tag, required: true)
Expand Down
116 changes: 65 additions & 51 deletions BankWallet/BankWalletTests/Modules/Send/SendInteractorTests.swift
Expand Up @@ -22,7 +22,7 @@ class SendInteractorTests: XCTestCase {

private var mockAdapter: MockIAdapter!
private var interactorState: SendInteractorState!
private var input = SendUserInput()
private var input: SendUserInput!

private var interactor: SendInteractor!

Expand All @@ -46,6 +46,7 @@ class SendInteractorTests: XCTestCase {
when(mock.baseCurrency.get).thenReturn(baseCurrency)
}
stub(mockAdapter) { mock in
when(mock.feeCoinCode.get).thenReturn(nil)
when(mock.coin.get).thenReturn(Coin(title: "some", code: coinCode, type: .bitcoin))
when(mock.decimal.get).thenReturn(0)
when(mock.balance.get).thenReturn(balance)
Expand All @@ -61,6 +62,7 @@ class SendInteractorTests: XCTestCase {
when(mock.fiatDecimal.get).thenReturn(fiatDecimal)
when(mock.maxDecimal.get).thenReturn(maxDecimal)
}
input = SendUserInput()

interactor = SendInteractor(currencyManager: mockCurrencyManager, rateStorage: mockRateStorage, localStorage: mockLocalStorage, pasteboardManager: mockPasteboardManager, state: interactorState, appConfigProvider: mockAppConfigProvider)
interactor.delegate = mockDelegate
Expand All @@ -77,6 +79,8 @@ class SendInteractorTests: XCTestCase {
mockAdapter = nil
interactorState = nil

input = nil

interactor = nil

super.tearDown()
Expand Down Expand Up @@ -386,18 +390,19 @@ class SendInteractorTests: XCTestCase {
XCTAssertEqual(state.feeCurrencyValue, CurrencyValue(currency: baseCurrency, value: fee * rateValue))
}

func testState_FeeCurrencyValue_CoinType_InsufficientBalance() {
func testState_FeeCurrencyValue_CurrencyType() {
let rateValue: Decimal = 987.65
let fee: Decimal = 0.123
let amount: Decimal = 123.45
let address = "address"
let coinAmount = amount / rateValue

stub(mockAdapter) { mock in
when(mock.fee(for: equal(to: amount), address: equal(to: address))).thenReturn(fee)
when(mock.fee(for: equal(to: coinAmount), address: equal(to: address))).thenReturn(fee)
}

interactorState.rateValue = rateValue
input.inputType = .coin
input.inputType = .currency
input.amount = amount
input.address = address

Expand All @@ -406,25 +411,45 @@ class SendInteractorTests: XCTestCase {
XCTAssertEqual(state.feeCurrencyValue, CurrencyValue(currency: baseCurrency, value: fee * rateValue))
}

func testState_FeeCurrencyValue_CurrencyType() {
let rateValue: Decimal = 987.65
func testState_feeCurrencyValue_currencyType_erc20() {
let feeCoinCode = "ETH"
let feeRateValue: Decimal = 789.65
let fee: Decimal = 0.123
let amount: Decimal = 123.45
let address = "address"
let coinAmount = amount / rateValue

stub(mockAdapter) { mock in
when(mock.fee(for: equal(to: coinAmount), address: equal(to: address))).thenReturn(fee)
when(mock.feeCoinCode.get).thenReturn(feeCoinCode)
when(mock.fee(for: equal(to: amount), address: equal(to: address))).thenReturn(fee)
}

interactorState.rateValue = rateValue
input.inputType = .currency
interactorState.feeRateValue = feeRateValue
input.amount = amount
input.address = address

let state = interactor.state(forUserInput: input)

XCTAssertEqual(state.feeCurrencyValue, CurrencyValue(currency: baseCurrency, value: fee * rateValue))
XCTAssertEqual(state.feeCurrencyValue, CurrencyValue(currency: baseCurrency, value: fee * feeRateValue))
}

func testState_erc20FeeCoinCode() {
let feeCoinCode = "ETH"
let fee: Decimal = 0.123
let amount: Decimal = 123.45
let address: String = "address"

input.inputType = .coin
input.amount = amount
input.address = address

stub(mockAdapter) { mock in
when(mock.feeCoinCode.get).thenReturn(feeCoinCode)
when(mock.fee(for: any(), address: any())).thenReturn(fee)
}

let state = interactor.state(forUserInput: input)

XCTAssertEqual(state.feeCoinValue, CoinValue(coinCode: feeCoinCode, value: fee))
}

func testCopyAddress() {
Expand All @@ -448,6 +473,35 @@ class SendInteractorTests: XCTestCase {
verify(mockDelegate).didUpdateRate()
}

func testFetchFeeRate() {
let rateValue: Decimal = 987.65
let feeCoinCode = "ETH"

stub(mockAdapter) { mock in
when(mock.feeCoinCode.get).thenReturn(feeCoinCode)
}
stub(mockRateStorage) { mock in
when(mock.nonExpiredLatestRateValueObservable(forCoinCode: equal(to: coinCode), currencyCode: equal(to: baseCurrency.code))).thenReturn(Observable.just(0))
when(mock.nonExpiredLatestRateValueObservable(forCoinCode: equal(to: feeCoinCode), currencyCode: equal(to: baseCurrency.code))).thenReturn(Observable.just(rateValue))
}

interactor.fetchRate()

XCTAssertEqual(interactorState.feeRateValue, rateValue)
verify(mockDelegate, times(2)).didUpdateRate()
}

func testFetchFeeRate_noFeeCoinCode() {
stub(mockRateStorage) { mock in
when(mock.nonExpiredLatestRateValueObservable(forCoinCode: equal(to: coinCode), currencyCode: equal(to: baseCurrency.code))).thenReturn(Observable.just(0))
}

interactor.fetchRate()

XCTAssertNil(interactorState.feeRateValue)
verify(mockRateStorage, never()).nonExpiredLatestRateValueObservable(forCoinCode: equal(to: "ETH"), currencyCode: equal(to: baseCurrency.code))
}

func testDefaultInputType() {
let inputType = SendInputType.currency
interactorState.rateValue = 2
Expand Down Expand Up @@ -489,44 +543,4 @@ class SendInteractorTests: XCTestCase {
verify(mockLocalStorage).sendInputType.set(equal(to: inputType))
}

// func testTotalBalanceMinusFee_Coin() {
// let fee: Decimal = 0.45
// let rateValue: Decimal = 987.65
// let amount: Decimal = 123.45
// let address = "address"
//
// interactorState.rateValue = rateValue
// input.inputType = .coin
// input.amount = amount
// input.address = address
//
// stub(mockAdapter) { mock in
// when(mock.fee(for: equal(to: input.amount), address: equal(to: address), senderPay: false)).thenReturn(fee)
// }
//
// let balanceMinusFee = interactor.totalBalanceMinusFee(forInputType: input.inputType, address: address)
// let expectedBalanceMinusFee: Decimal = 123
// XCTAssertEqual(balanceMinusFee, expectedBalanceMinusFee)
// }

// func testTotalBalanceMinusFee_Currency() {
// let fee: Decimal = 0.45
// let rateValue: Decimal = 987.65
// let amount: Decimal = 123.45
// let address = "address"
//
// interactorState.rateValue = rateValue
// input.inputType = .currency
// input.amount = amount
// input.address = address
//
// stub(mockAdapter) { mock in
// when(mock.fee(for: equal(to: input.amount), address: equal(to: address), senderPay: false)).thenReturn(fee)
// }
//
// let balanceMinusFee = interactor.totalBalanceMinusFee(forInputType: input.inputType, address: address)
// let expectedBalanceMinusFee: Decimal = 123 * rateValue
// XCTAssertEqual(balanceMinusFee, expectedBalanceMinusFee)
// }

}

0 comments on commit 4aec902

Please sign in to comment.