Expose the rest of `WalletSummary` across the FFI

This commit is contained in:
Jack Grigg 2024-01-26 19:11:00 +00:00 committed by str4d
parent 9eeb5ba04c
commit 1f19360ca3
6 changed files with 111 additions and 12 deletions

View File

@ -6,8 +6,14 @@ import androidx.annotation.Keep
* Serves as cross layer (Kotlin, Rust) communication class.
*
* @param account the account ID
* @param saplingTotalBalance The total account balance in the Sapling pool, including unconfirmed funds.
* @param saplingTotalBalance The total account balance in the Sapling pool,
* including unconfirmed funds.
* @param saplingVerifiedBalance The verified account balance in the Sapling pool.
* @param orchardTotalBalance The total account balance in the Orchard pool,
* including unconfirmed funds.
* @param orchardVerifiedBalance The verified account balance in the Orchard pool.
* @param unshieldedBalance The total account balance in the transparent pool,
* including unconfirmed funds, that must be shielded before use.
* @throws IllegalArgumentException if the values are inconsistent.
*/
@Keep
@ -15,11 +21,18 @@ class JniAccountBalance(
val account: Int,
val saplingTotalBalance: Long,
val saplingVerifiedBalance: Long,
val orchardTotalBalance: Long,
val orchardVerifiedBalance: Long,
val unshieldedBalance: Long,
) {
init {
require(saplingTotalBalance >= saplingVerifiedBalance) {
"Total Sapling balance $saplingTotalBalance must not be " +
"less than verified Sapling balance $saplingVerifiedBalance."
}
require(orchardTotalBalance >= orchardVerifiedBalance) {
"Total Orchard balance $orchardTotalBalance must not be " +
"less than verified Orchard balance $orchardVerifiedBalance."
}
}
}

View File

@ -1,13 +1,19 @@
package cash.z.ecc.android.sdk.internal.model
import androidx.annotation.Keep
import cash.z.ecc.android.sdk.internal.ext.isInUIntRange
/**
* Serves as cross layer (Kotlin, Rust) communication class.
*
* @param accountBalances the balances of the wallet accounts
* @param chainTipHeight the wallet's view of the current chain tip
* @param fullyScannedHeight the height below which all blocks have been scanned
* by the wallet, ignoring blocks below the wallet birthday.
* @param progressNumerator the numerator of the progress ratio
* @param progressDenominator the denominator of the progress ratio
* @param nextSaplingSubtreeIndex the Sapling subtree index that should start
* the next range of subtree roots passed to `Backend.putSaplingSubtreeRoots`.
* @throws IllegalArgumentException unless (progressNumerator is nonnegative,
* progressDenominator is positive, and the represented ratio is in the
* range 0.0 to 1.0 inclusive).
@ -15,10 +21,19 @@ import androidx.annotation.Keep
@Keep
class JniWalletSummary(
val accountBalances: Array<JniAccountBalance>,
val chainTipHeight: Long,
val fullyScannedHeight: Long,
val progressNumerator: Long,
val progressDenominator: Long
val progressDenominator: Long,
val nextSaplingSubtreeIndex: Long,
) {
init {
require(chainTipHeight.isInUIntRange()) {
"Height $chainTipHeight is outside of allowed UInt range"
}
require(fullyScannedHeight.isInUIntRange()) {
"Height $fullyScannedHeight is outside of allowed UInt range"
}
require(progressNumerator >= 0L) {
"Numerator $progressNumerator is outside of allowed range [0, Long.MAX_VALUE]"
}
@ -31,5 +46,8 @@ class JniWalletSummary(
require(progressNumerator.toFloat().div(progressDenominator) <= 1f) {
"Result of ${progressNumerator.toFloat()}/$progressDenominator is outside of allowed range"
}
require(nextSaplingSubtreeIndex >= 0L) {
"Numerator $nextSaplingSubtreeIndex is outside of allowed range [0, Long.MAX_VALUE]"
}
}
}

View File

@ -1117,13 +1117,21 @@ fn encode_account_balance<'a>(
let sapling_total_balance = Amount::from(balance.sapling_balance().total());
let sapling_verified_balance = Amount::from(balance.sapling_balance().spendable_value());
let orchard_total_balance = Amount::from(balance.orchard_balance().total());
let orchard_verified_balance = Amount::from(balance.orchard_balance().spendable_value());
let unshielded = Amount::from(balance.unshielded());
env.new_object(
JNI_ACCOUNT_BALANCE,
"(IJJ)V",
"(IJJJJJ)V",
&[
JValue::Int(u32::from(*account) as i32),
JValue::Long(sapling_total_balance.into()),
JValue::Long(sapling_verified_balance.into()),
JValue::Long(orchard_total_balance.into()),
JValue::Long(orchard_verified_balance.into()),
JValue::Long(unshielded.into()),
],
)
}
@ -1153,11 +1161,14 @@ fn encode_wallet_summary<'a>(
env.new_object(
"cash/z/ecc/android/sdk/internal/model/JniWalletSummary",
&format!("([L{};JJ)V", JNI_ACCOUNT_BALANCE),
&format!("([L{};JJJJJ)V", JNI_ACCOUNT_BALANCE),
&[
(&account_balances).into(),
JValue::Long(i64::from(u32::from(summary.chain_tip_height()))),
JValue::Long(i64::from(u32::from(summary.fully_scanned_height()))),
JValue::Long(progress_numerator as i64),
JValue::Long(progress_denominator as i64),
JValue::Long(summary.next_sapling_subtree_index() as i64),
],
)
}

View File

@ -6,14 +6,24 @@ object JniAccountBalanceFixture {
const val ACCOUNT_ID: Int = 0
const val SAPLING_TOTAL_BALANCE: Long = 0L
const val SAPLING_VERIFIED_BALANCE: Long = 0L
const val ORCHARD_TOTAL_BALANCE: Long = 0L
const val ORCHARD_VERIFIED_BALANCE: Long = 0L
const val UNSHIELDED_BALANCE: Long = 0L
@Suppress("LongParameterList")
fun new(
account: Int = ACCOUNT_ID,
saplingTotalBalance: Long = SAPLING_TOTAL_BALANCE,
saplingVerifiedBalance: Long = SAPLING_VERIFIED_BALANCE,
orchardTotalBalance: Long = ORCHARD_TOTAL_BALANCE,
orchardVerifiedBalance: Long = ORCHARD_VERIFIED_BALANCE,
unshieldedBalance: Long = UNSHIELDED_BALANCE,
) = JniAccountBalance(
account = account,
saplingTotalBalance = saplingTotalBalance,
saplingVerifiedBalance = saplingVerifiedBalance
saplingVerifiedBalance = saplingVerifiedBalance,
orchardTotalBalance = orchardTotalBalance,
orchardVerifiedBalance = orchardVerifiedBalance,
unshieldedBalance = unshieldedBalance
)
}

View File

@ -7,23 +7,43 @@ import kotlin.test.assertIs
class JniWalletSummaryTest {
@Test
fun both_attribute_within_constraints() {
fun all_attribute_within_constraints() {
val instance =
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = 0,
fullyScannedHeight = 0,
progressNumerator = 1L,
progressDenominator = 100L
progressDenominator = 100L,
nextSaplingSubtreeIndex = 0
)
assertIs<JniWalletSummary>(instance)
}
@Test
fun height_not_in_constraints() {
assertFailsWith(IllegalArgumentException::class) {
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = -1,
fullyScannedHeight = 0,
progressNumerator = 1L,
progressDenominator = 100L,
nextSaplingSubtreeIndex = 0
)
}
}
@Test
fun numerator_attribute_not_in_constraints() {
assertFailsWith(IllegalArgumentException::class) {
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = 0,
fullyScannedHeight = 0,
progressNumerator = -1L,
progressDenominator = 100L
progressDenominator = 100L,
nextSaplingSubtreeIndex = 0
)
}
}
@ -33,8 +53,11 @@ class JniWalletSummaryTest {
assertFailsWith(IllegalArgumentException::class) {
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = 0,
fullyScannedHeight = 0,
progressNumerator = 1L,
progressDenominator = 0L
progressDenominator = 0L,
nextSaplingSubtreeIndex = 0
)
}
}
@ -44,8 +67,25 @@ class JniWalletSummaryTest {
assertFailsWith(IllegalArgumentException::class) {
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = 0,
fullyScannedHeight = 0,
progressNumerator = 100L,
progressDenominator = 1L
progressDenominator = 1L,
nextSaplingSubtreeIndex = 0
)
}
}
@Test
fun subtree_index_not_in_constraints() {
assertFailsWith(IllegalArgumentException::class) {
JniWalletSummary(
accountBalances = arrayOf(JniAccountBalanceFixture.new()),
chainTipHeight = 0,
fullyScannedHeight = 0,
progressNumerator = 1L,
progressDenominator = 100L,
nextSaplingSubtreeIndex = -1
)
}
}

View File

@ -1,10 +1,14 @@
package cash.z.ecc.android.sdk.internal.model
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.BlockHeight
internal data class WalletSummary(
val accountBalances: Map<Account, AccountBalance>,
val scanProgress: ScanProgress
val chainTipHeight: BlockHeight,
val fullyScannedHeight: BlockHeight,
val scanProgress: ScanProgress,
val nextSaplingSubtreeIndex: Long
) {
companion object {
fun new(jni: JniWalletSummary): WalletSummary {
@ -13,7 +17,10 @@ internal data class WalletSummary(
jni.accountBalances.associateBy({ Account(it.account) }, {
AccountBalance.new(it)
}),
scanProgress = ScanProgress.new(jni)
chainTipHeight = BlockHeight(jni.chainTipHeight),
fullyScannedHeight = BlockHeight(jni.fullyScannedHeight),
scanProgress = ScanProgress.new(jni),
nextSaplingSubtreeIndex = jni.nextSaplingSubtreeIndex
)
}
}