Skip to content

Commit

Permalink
PWN-880 - Fix swap fee calculations (#2188)
Browse files Browse the repository at this point in the history
* PWN-880 - fix ATA fees and transfer fees calculations
* PWN-880 - fix percent for getPlatformFeePercent
* PWN-880 - fix BPS calculation
* PWN-880 - comment fixes
  • Loading branch information
gslevinkov authored Feb 9, 2024
1 parent 1f95526 commit bdce0fd
Show file tree
Hide file tree
Showing 20 changed files with 76 additions and 72 deletions.
8 changes: 4 additions & 4 deletions app/src/main/java/org/p2p/wallet/jupiter/JupiterSwapModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import org.p2p.wallet.jupiter.repository.transaction.JupiterSwapTransactionMappe
import org.p2p.wallet.jupiter.repository.transaction.JupiterSwapTransactionRemoteRepository
import org.p2p.wallet.jupiter.repository.transaction.JupiterSwapTransactionRepository
import org.p2p.wallet.jupiter.repository.v6.JupiterSwapRoutesRemoteV6Repository
import org.p2p.wallet.jupiter.repository.v6.JupiterSwapRoutesV6Mapper
import org.p2p.wallet.jupiter.repository.v6.JupiterSwapRoutesRepositoryV6Mapper
import org.p2p.wallet.jupiter.repository.v6.JupiterSwapRoutesV6Repository
import org.p2p.wallet.jupiter.statemanager.SwapCoroutineScope
import org.p2p.wallet.jupiter.statemanager.SwapProfiler
Expand All @@ -49,7 +49,7 @@ import org.p2p.wallet.jupiter.statemanager.handler.SwapStateTokenAZeroHandler
import org.p2p.wallet.jupiter.statemanager.rate.SwapRateTickerManager
import org.p2p.wallet.jupiter.statemanager.token_selector.SwapInitialTokenSelector
import org.p2p.wallet.jupiter.statemanager.token_selector.SwapInitialTokensData
import org.p2p.wallet.jupiter.statemanager.validator.MinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapMinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapValidator
import org.p2p.wallet.jupiter.ui.info.SwapInfoMapper
import org.p2p.wallet.jupiter.ui.main.JupiterSwapContract
Expand Down Expand Up @@ -106,7 +106,7 @@ object JupiterSwapModule : InjectionModule {
factoryOf(::JupiterSwapTransactionRpcErrorMapper)
factoryOf(::JupiterSwapInteractor)
factoryOf(::SwapUserTokensChangeHandler)
factoryOf(::MinimumSolAmountValidator)
factoryOf(::SwapMinimumSolAmountValidator)
factoryOf(::SwapValidator)
factoryOf(::SwapStateRoutesRefresher)
factoryOf(::SwapWidgetMapper)
Expand Down Expand Up @@ -155,7 +155,7 @@ object JupiterSwapModule : InjectionModule {
private fun Module.initV6Api() {
factory { get<Retrofit>(named(JUPITER_RETROFIT_V6_QUALIFIER)).create<SwapJupiterV6Api>() }
factoryOf(::JupiterSwapRoutesRemoteV6Repository) bind JupiterSwapRoutesV6Repository::class
factoryOf(::JupiterSwapRoutesV6Mapper)
factoryOf(::JupiterSwapRoutesRepositoryV6Mapper)
}

private fun Module.initJupiterSwapStateManager() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ data class JupiterSwapRoutePlanV6(
)

data class SwapKeyAppFees(
// maybe totalFees, maybe else,
// I don't know what fees, no doc at the moment
// totalFeeAndDeposits + transfer fee if exists + some other fee
val totalFees: BigInteger,
val signatureFee: BigInteger,
val ataDeposits: BigInteger,
val ataDepositsInSol: BigInteger,
// signatureFee + ataDeposits + minimumSolForTransaction
val totalFeeAndDeposits: BigInteger,
val minimumSolForTransaction: BigInteger,
val platformFeeTokenB: BigInteger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface SwapTokensDao {
suspend fun findTokensByMints(mints: Set<String>): List<SwapTokenEntity>

// todo: should be done with pagination
@Query("SELECT * from swap_tokens")
@Query("SELECT * from swap_tokens LIMIT 150")
suspend fun getAllSwapTokens(): List<SwapTokenEntity>

@Query("SELECT COUNT(*) from swap_tokens")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import org.p2p.wallet.utils.retryOnException
class JupiterSwapRoutesRemoteV6Repository(
private val apiV6: SwapJupiterV6Api,
private val dispatchers: CoroutineDispatchers,
private val mapper: JupiterSwapRoutesV6Mapper,
private val mapper: JupiterSwapRoutesRepositoryV6Mapper,
private val validator: JupiterSwapRouteValidator,
) : JupiterSwapRoutesV6Repository {
override suspend fun getSwapRoutesForSwapPair(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.json.JSONObject
import java.math.BigDecimal
import java.math.BigInteger
import org.p2p.core.crypto.toBase58Instance
import org.p2p.core.utils.divideSafe
import org.p2p.core.utils.orZero
import org.p2p.core.utils.toJsonObject
import org.p2p.wallet.jupiter.api.response.SwapJupiterV6QuoteResponse
Expand All @@ -13,7 +14,7 @@ import org.p2p.wallet.jupiter.repository.model.JupiterSwapRouteV6
import org.p2p.wallet.jupiter.repository.model.SwapKeyAppFees
import org.p2p.wallet.utils.mapAsStrings

class JupiterSwapRoutesV6Mapper(
class JupiterSwapRoutesRepositoryV6Mapper(
private val gson: Gson
) {
fun fromNetwork(
Expand Down Expand Up @@ -46,7 +47,7 @@ class JupiterSwapRoutesV6Mapper(
val fees = SwapKeyAppFees(
totalFees = response.keyAppFees.fee.toBigInteger(),
signatureFee = feesJson.optLong("signatureFee").toBigInteger(),
ataDeposits = ataDeposits,
ataDepositsInSol = ataDeposits,
platformFeeTokenB = platformFeeAmount,
platformFeePercent = platformFeePercent,
totalFeeAndDeposits = feesJson.optLong("totalFeeAndDeposits").toBigInteger(),
Expand Down Expand Up @@ -74,8 +75,6 @@ class JupiterSwapRoutesV6Mapper(
}

private fun getPlatformFeePercent(fee: SwapJupiterV6QuoteResponse.PlatformFeeResponse?): BigDecimal {
return fee?.feeBps
?.divide(100.toBigDecimal())
.orZero()
return fee?.feeBps?.divideSafe(100.toBigDecimal()).orZero()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import org.p2p.wallet.jupiter.repository.model.JupiterSwapRouteV6
import org.p2p.wallet.jupiter.repository.model.SwapFailure
import org.p2p.wallet.jupiter.repository.transaction.JupiterSwapTransactionRepository
import org.p2p.wallet.jupiter.repository.v6.JupiterSwapRoutesV6Repository
import org.p2p.wallet.jupiter.statemanager.validator.MinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapMinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapValidator
import org.p2p.wallet.swap.model.Slippage

class SwapStateRoutesRefresher(
private val tokenKeyProvider: TokenKeyProvider,
private val swapRoutesRepository: JupiterSwapRoutesV6Repository,
private val swapTransactionRepository: JupiterSwapTransactionRepository,
private val minSolBalanceValidator: MinimumSolAmountValidator,
private val minSolBalanceValidator: SwapMinimumSolAmountValidator,
private val swapValidator: SwapValidator,
private val swapProfiler: SwapProfiler,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.p2p.wallet.jupiter.statemanager.SwapFeatureException
import org.p2p.wallet.rpc.repository.amount.RpcAmountRepository
import org.p2p.wallet.swap.model.Slippage

class MinimumSolAmountValidator(
class SwapMinimumSolAmountValidator(
private val rpcAmountRepository: RpcAmountRepository
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class JupiterSwapSettingsPresenter(
super.attach(view)

stateManager.observe()
.mapLatest { handleFeatureState(it) }
.mapLatest(::handleFeatureState)
.launchIn(this)

rateTickerManager.observe()
Expand Down Expand Up @@ -217,21 +217,22 @@ class JupiterSwapSettingsPresenter(
private suspend fun getContentListByFeatureState(
state: SwapState,
): List<AnyCellItem> {
val jupiterSolToken = swapTokensRepository.requireWrappedSol()
return when (state) {
SwapState.InitialLoading -> {
emptyList()
}
is SwapState.TokenAZero -> {
emptyMapper.mapEmptyList(tokenB = state.tokenB)
emptyMapper.mapEmptyList(tokenB = state.tokenB, jupiterSolToken)
}
is SwapState.TokenANotZero -> {
emptyMapper.mapEmptyList(tokenB = state.tokenB)
emptyMapper.mapEmptyList(tokenB = state.tokenB, jupiterSolToken)
}
is SwapState.LoadingRoutes,
is SwapState.LoadingTransaction,
is SwapState.RoutesLoaded,
is SwapState.SwapLoaded -> {
loadingMapper.mapLoadingList()
loadingMapper.mapLoadingList(jupiterSolToken)
}
is SwapState.SwapException -> {
getContentListByFeatureState(state.previousFeatureState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class SwapContentSettingsMapper(
val networkFeeCell = swapFeeBuilder.buildNetworkFeeCell(route, solTokenForFee)
this += networkFeeCell.cellModel

val accountFee = swapFeeBuilder.buildAccountFeeCell(route, tokenB)
val accountFee = swapFeeBuilder.buildAccountFeeCell(route, tokenB, solTokenForFee)
if (accountFee != null) {
this += accountFee.cellModel
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ import org.p2p.uikit.utils.image.ImageViewCellModel
import org.p2p.uikit.utils.text.TextViewCellModel
import org.p2p.wallet.R
import org.p2p.wallet.jupiter.interactor.model.SwapTokenModel
import org.p2p.wallet.jupiter.repository.model.JupiterSwapToken

class SwapEmptySettingsMapper(
private val swapFeeCellsBuilder: SwapFeeCellsBuilder
) {

suspend fun mapEmptyList(
tokenB: SwapTokenModel,
jupiterSolToken: JupiterSwapToken
): List<AnyCellItem> = buildList {
this += swapFeeCellsBuilder.buildNetworkFeeCell(activeRoute = null, solToken = null).cellModel
this += swapFeeCellsBuilder.buildNetworkFeeCell(activeRoute = null, solToken = jupiterSolToken).cellModel
addMinimumReceivedCell(tokenB)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.supervisorScope
import org.p2p.core.common.DrawableContainer
import org.p2p.core.common.TextContainer
import org.p2p.core.utils.asUsdSwap
import org.p2p.core.utils.divideSafe
import org.p2p.core.utils.formatFiat
import org.p2p.core.utils.formatToken
import org.p2p.core.utils.formatTokenWithSymbol
Expand Down Expand Up @@ -43,9 +44,9 @@ class SwapFeeCellsBuilder(

suspend fun buildNetworkFeeCell(
activeRoute: JupiterSwapRouteV6?,
solToken: JupiterSwapToken?,
solToken: JupiterSwapToken,
): SwapSettingsFeeBox {
if (activeRoute != null && solToken != null) {
if (activeRoute != null) {
val networkFee = activeRoute.fees.signatureFee.fromLamports(solToken.decimals)
val solTokenRate: BigDecimal? = loadRateForToken(SwapTokenModel.JupiterToken(solToken))?.rate
val feeUsd: BigDecimal? = solTokenRate?.let { networkFee.multiply(it) }
Expand Down Expand Up @@ -84,15 +85,26 @@ class SwapFeeCellsBuilder(
suspend fun buildAccountFeeCell(
activeRoute: JupiterSwapRouteV6,
tokenB: SwapTokenModel,
solToken: JupiterSwapToken
): SwapSettingsFeeBox? {
val ataFee = activeRoute.fees.totalFees.fromLamports(tokenB.decimals)
if (ataFee.isZero()) {
val ataFeeInSol = activeRoute.fees.ataDepositsInSol.fromLamports(solToken.decimals)
if (ataFeeInSol.isZero()) {
return null
}

val tokenBRate: BigDecimal? = loadRateForToken(tokenB)?.rate
val feeUsd = tokenBRate?.let { ataFee.multiply(it) }
val formattedFeeAmount = ataFee.formatTokenWithSymbol(tokenB.tokenSymbol, tokenB.decimals)
val solRate = loadRateForToken(SwapTokenModel.JupiterToken(solToken))?.rate ?: kotlin.run {
Timber.e(IllegalStateException("Sol rate is null"))
return null
}
val tokenBRate = loadRateForToken(tokenB)?.rate ?: kotlin.run {
Timber.e(IllegalStateException("Token B (${tokenB.mintAddress} rate is null"))
return null
}
val ataFeeInTokenB: BigDecimal = solRate.divideSafe(tokenBRate) * ataFeeInSol

val feeUsd = ataFeeInTokenB.multiply(tokenBRate)

val formattedFeeAmount = ataFeeInTokenB.formatTokenWithSymbol(tokenB.tokenSymbol, tokenB.decimals)

val cellModel = MainCellModel(
leftSideCellModel = LeftSideCellModel.IconWithText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import org.p2p.uikit.utils.skeleton.SkeletonCellModel
import org.p2p.uikit.utils.text.TextViewCellModel
import org.p2p.uikit.utils.toPx
import org.p2p.wallet.R
import org.p2p.wallet.jupiter.repository.model.JupiterSwapToken

class SwapLoadingSettingsMapper(
private val feeCellsBuilder: SwapFeeCellsBuilder
) {

suspend fun mapLoadingList(): List<AnyCellItem> = buildList {
suspend fun mapLoadingList(solToken: JupiterSwapToken): List<AnyCellItem> = buildList {
addRouteCell()
add(feeCellsBuilder.buildNetworkFeeCell(activeRoute = null, solToken = null).cellModel)
add(feeCellsBuilder.buildNetworkFeeCell(activeRoute = null, solToken = solToken).cellModel)
addAccountFeeCell()
addLiquidityFeeCell()
addEstimatedFeeCell()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CalculateToken2022TransferFeeUseCase(
): BigInteger {
if (this == null) return BigInteger.ZERO

if (transferFeeBasisPoints == 0 || preFeeAmount.isZero()) {
if (transferFeeBasisPoints == 0L || preFeeAmount.isZero()) {
return BigInteger.ZERO
}
val transferFeeBasisPoints = transferFeeBasisPoints.toBigInteger()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ import org.p2p.wallet.jupiter.statemanager.token_selector.CommonSwapTokenSelecto
import org.p2p.wallet.jupiter.statemanager.token_selector.PreinstallTokenASelector
import org.p2p.wallet.jupiter.statemanager.token_selector.PreinstallTokensByMintSelector
import org.p2p.wallet.jupiter.statemanager.token_selector.SwapInitialTokenSelector
import org.p2p.wallet.jupiter.statemanager.validator.MinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapMinimumSolAmountValidator
import org.p2p.wallet.jupiter.statemanager.validator.SwapValidator
import org.p2p.wallet.jupiter.ui.main.JupiterSwapTestHelpers.toTokenData
import org.p2p.wallet.jupiter.ui.main.mapper.SwapButtonMapper
Expand Down Expand Up @@ -244,7 +244,7 @@ open class JupiterSwapPresenterBaseTest {
tokenKeyProvider = tokenKeyProvider,
swapRoutesRepository = jupiterSwapRoutesV6Repository,
swapTransactionRepository = jupiterSwapTransactionRepository,
minSolBalanceValidator = MinimumSolAmountValidator(
minSolBalanceValidator = SwapMinimumSolAmountValidator(
rpcAmountRepository = rpcAmountRepository
),
swapValidator = SwapValidator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ object JupiterSwapTestHelpers {
originalRoute = JsonObject(),
fees = SwapKeyAppFees(
signatureFee = BigInteger.ZERO,
ataDeposits = BigInteger.ZERO,
ataDepositsInSol = BigInteger.ZERO,
totalFeeAndDeposits = BigInteger.ZERO,
minimumSolForTransaction = BigInteger.ZERO,
totalFees = BigInteger.ZERO,
Expand Down
3 changes: 0 additions & 3 deletions core/src/main/java/org/p2p/core/network/gson/GsonProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.math.BigInteger
import kotlin.reflect.KClass
import org.p2p.core.crypto.Base58String
import org.p2p.core.crypto.Base64String
Expand All @@ -31,8 +30,6 @@ class GsonProvider {
private fun buildGson(): Gson {
return builder.apply {
setLenient()
registerTypeAdapter(BigInteger::class.java, BigIntegerTypeAdapter())
registerTypeAdapter(object : TypeToken<BigInteger?>() {}.type, BigIntegerTypeAdapter())
registerTypeAdapter(Long::class.java, LongTypeAdapter())
registerTypeAdapter(Int::class.java, IntTypeAdapter())
registerTypeAdapter(ByteArray::class.java, ByteArrayTypeAdapter())
Expand Down
23 changes: 0 additions & 23 deletions core/src/main/java/org/p2p/core/network/gson/GsonTypeAdapters.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,16 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import java.lang.reflect.Type
import java.math.BigInteger
import java.util.Optional
import org.p2p.core.model.DefaultBlockParameter
import org.p2p.core.token.SolAddress
import org.p2p.core.wrapper.HexString
import org.p2p.core.wrapper.eth.EthAddress
import org.p2p.core.wrapper.eth.hexStringToBigIntegerOrNull
import org.p2p.core.wrapper.eth.hexStringToByteArrayOrNull
import org.p2p.core.wrapper.eth.hexStringToIntOrNull
import org.p2p.core.wrapper.eth.hexStringToLongOrNull
import org.p2p.core.wrapper.eth.toHexString

internal class BigIntegerTypeAdapter(private val isHex: Boolean = true) : TypeAdapter<BigInteger?>() {
override fun write(writer: JsonWriter, value: BigInteger?) {
if (value == null) {
writer.nullValue()
} else {
val stringValue = if (isHex) value.toHexString() else value.toString()
writer.value(stringValue)
}
}

override fun read(reader: JsonReader): BigInteger? {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
return null
}
val stringValue = reader.nextString()
return if (isHex) stringValue.hexStringToBigIntegerOrNull() else BigInteger(stringValue)
}
}

internal class LongTypeAdapter(private val isHex: Boolean = false) : TypeAdapter<Long?>() {
override fun write(writer: JsonWriter, value: Long?) {
if (value == null) {
Expand Down Expand Up @@ -190,7 +168,6 @@ internal class OptionalTypeAdapter<T : Any>(
.setLenient()
.registerTypeAdapter(EthAddress::class.java, AddressTypeAdapter())
.registerTypeAdapter(ByteArray::class.java, ByteArrayTypeAdapter())
.registerTypeAdapter(BigInteger::class.java, BigIntegerTypeAdapter())
.registerTypeAdapter(Long::class.java, LongTypeAdapter())
.registerTypeAdapter(object : TypeToken<Long?>() {}.type, LongTypeAdapter())
.registerTypeAdapter(Int::class.java, IntTypeAdapter())
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/p2p/core/utils/AmountExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,4 @@ fun Int?.orZero(): Int = this ?: 0
// value is in (0..0.01)
fun BigDecimal.lessThenMinValue() = !isZero() && isLessThan(AMOUNT_MIN_VALUE.toBigDecimal())
fun BigDecimal.moreThenMinValue() = isMoreThan(AMOUNT_MIN_VALUE.toBigDecimal())
fun BigDecimal.divideByInt(byInt: Int): BigDecimal = divide(byInt.toBigDecimal())
7 changes: 5 additions & 2 deletions core/src/main/java/org/p2p/core/utils/MathUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ fun BigInteger.divideSafe(value: BigInteger): BigInteger =

// scales by default to the decimals
fun BigDecimal.divideSafe(value: BigDecimal, decimals: Int = DEFAULT_DECIMALS_VALUE): BigDecimal =
if (this.isZero() || value.isZero()) BigDecimal.ZERO
else this.divide(value, decimals, RoundingMode.HALF_EVEN).stripTrailingZeros()
if (this.isZero() || value.isZero()) {
BigDecimal.ZERO
} else {
this.divide(value, decimals, RoundingMode.HALF_EVEN).stripTrailingZeros()
}

fun clamp(value: Int, min: Int, max: Int): Int = min(max(min, value), max)
Loading

0 comments on commit bdce0fd

Please sign in to comment.