diff --git a/bitstring/src/ByteBackedBitString.kt b/bitstring/src/ByteBackedBitString.kt index 589828fc..4b7b3ee1 100644 --- a/bitstring/src/ByteBackedBitString.kt +++ b/bitstring/src/ByteBackedBitString.kt @@ -45,7 +45,7 @@ public open class ByteBackedBitString protected constructor( if (augment && (size % 8 != 0)) { appendAugmentTag(bytes, size) } else { - bytes.copyOf() + bytes.copyOf((size + 7) ushr 3) } override fun toBooleanArray(): BooleanArray = toList().toBooleanArray() diff --git a/block-tlb/src/Account.kt b/block-tlb/src/Account.kt index a1cca9dc..9114e5ff 100644 --- a/block-tlb/src/Account.kt +++ b/block-tlb/src/Account.kt @@ -1,20 +1,86 @@ -@file:Suppress("OPT_IN_USAGE") - package org.ton.block -import kotlinx.serialization.json.JsonClassDiscriminator -import org.ton.tlb.TlbCombinator -import org.ton.tlb.TlbObject -import org.ton.tlb.providers.TlbCombinatorProvider +import org.ton.cell.CellBuilder +import org.ton.cell.CellSlice +import org.ton.cell.invoke +import org.ton.tlb.* +import org.ton.tlb.TlbConstructor + +/** + * Existing account data. + */ +public data class Account( + /** + * Account address. + */ + val address: MsgAddressInt, + + /** + * Storage statistics. + */ + val storageStat: StorageInfo, + + /** + * Logical time after the last transaction execution. + */ + val lastTransLt: Long, + + /** + * Account balance for all currencies. + */ + val balance: CurrencyCollection, + + /** + * Account state. + */ + val state: AccountState +) { + public companion object : TlbCodec by AccountInfoTlbConstructor.asNullable() -@JsonClassDiscriminator("@type") + @Deprecated("Use fields lastTransLt, balance, state instead") + val storage: AccountStorage // storage : AccountStorage + get() = AccountStorage(lastTransLt.toULong(), balance, state) -public sealed interface Account : TlbObject { - public companion object : TlbCombinatorProvider by AccountTlbCombinator + @Deprecated("Use address instead", ReplaceWith("address")) + val addr: MsgAddressInt + get() = address + + val isActive: Boolean get() = storage.state is AccountActive + val isFrozen: Boolean get() = storage.state is AccountFrozen + val isUninit: Boolean get() = storage.state is AccountUninit } -private object AccountTlbCombinator : TlbCombinator( - Account::class, - AccountNone::class to AccountNone, - AccountInfo::class to AccountInfo -) +public val Account?.balance: CurrencyCollection + get() = this?.balance ?: CurrencyCollection.ZERO + +public val Account?.accountLastTransLt: Long + get() = this?.lastTransLt ?: 0 + +public val Account?.status: AccountStatus + get() = this?.state?.status ?: AccountStatus.NONEXIST + +private object AccountInfoTlbConstructor : TlbConstructor( + schema = "account\$1 addr:MsgAddressInt storage_stat:StorageInfo storage:AccountStorage = Account;" +) { + override fun storeTlb( + cellBuilder: CellBuilder, + value: Account + ) = cellBuilder { + storeTlb(MsgAddressInt, value.addr) + storeTlb(StorageInfo, value.storageStat) + storeULong(value.lastTransLt.toULong()) + storeTlb(CurrencyCollection, value.balance) + storeTlb(AccountState, value.state) + } + + override fun loadTlb( + cellSlice: CellSlice + ): Account = cellSlice { + val addr = loadTlb(MsgAddressInt) + val storageStat = loadTlb(StorageInfo) + val lastTransLt = loadULong().toLong() + val balance = loadTlb(CurrencyCollection) + val state = loadTlb(AccountState) + Account(addr, storageStat, lastTransLt, balance, state) + } +} diff --git a/block-tlb/src/AccountActive.kt b/block-tlb/src/AccountActive.kt index 8e7e881d..7d6d8429 100644 --- a/block-tlb/src/AccountActive.kt +++ b/block-tlb/src/AccountActive.kt @@ -19,6 +19,8 @@ public value class AccountActive( @get:JvmName("value") public val value: StateInit ) : AccountState { + override val status: AccountStatus get() = AccountStatus.ACTIVE + override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { return printer.type("account_active") { value.print(printer) diff --git a/block-tlb/src/AccountFrozen.kt b/block-tlb/src/AccountFrozen.kt index ed034f77..63dcecc6 100644 --- a/block-tlb/src/AccountFrozen.kt +++ b/block-tlb/src/AccountFrozen.kt @@ -21,6 +21,8 @@ public data class AccountFrozen( require(stateHash.size == 256) { "stateHash must be 256 bits long" } } + override val status: AccountStatus get() = AccountStatus.FROZEN + override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("account_frozen") { printer.field("state_hash", stateHash) } diff --git a/block-tlb/src/AccountInfo.kt b/block-tlb/src/AccountInfo.kt deleted file mode 100644 index cd9cb734..00000000 --- a/block-tlb/src/AccountInfo.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.loadTlb -import org.ton.tlb.providers.TlbConstructorProvider -import org.ton.tlb.storeTlb -import kotlin.jvm.JvmName -import kotlin.jvm.JvmStatic - -@SerialName("account") -public data class AccountInfo( - @SerialName("addr") - @get:JvmName("addr") - val addr: MsgAddressInt, // addr : MsgAddressInt - - @SerialName("storage_stat") - @get:JvmName("storageStat") - val storageStat: StorageInfo, // storage_stat : StorageInfo - - @SerialName("storage") - @get:JvmName("storage") - val storage: AccountStorage // storage : AccountStorage -) : Account { - public companion object : TlbConstructorProvider by AccountInfoTlbConstructor { - @JvmStatic - override fun tlbConstructor(): TlbConstructor = AccountInfoTlbConstructor - } - - val isActive: Boolean get() = storage.state is AccountActive - val isFrozen: Boolean get() = storage.state is AccountFrozen - val isUninit: Boolean get() = storage.state is AccountUninit - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("account") { - field("addr", addr) - field("storage_stat", storageStat) - field("storage", storage) - } - } - - override fun toString(): String = print().toString() -} - -public val AccountInfo?.balance: CurrencyCollection - get() = this?.storage?.balance ?: CurrencyCollection.ZERO - -private object AccountInfoTlbConstructor : TlbConstructor( - schema = "account\$1 addr:MsgAddressInt storage_stat:StorageInfo storage:AccountStorage = Account;" -) { - override fun storeTlb( - cellBuilder: CellBuilder, - value: AccountInfo - ) = cellBuilder { - storeTlb(MsgAddressInt, value.addr) - storeTlb(StorageInfo, value.storageStat) - storeTlb(AccountStorage, value.storage) - } - - override fun loadTlb( - cellSlice: CellSlice - ): AccountInfo = cellSlice { - val addr = loadTlb(MsgAddressInt) - val storageStat = loadTlb(StorageInfo) - val storage = loadTlb(AccountStorage) - AccountInfo(addr, storageStat, storage) - } -} diff --git a/block-tlb/src/AccountNone.kt b/block-tlb/src/AccountNone.kt deleted file mode 100644 index 2652bd2e..00000000 --- a/block-tlb/src/AccountNone.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("account_none") -public object AccountNone : Account, TlbConstructorProvider by AccountNoneTlbConstructor { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("account_none") - - override fun toString(): String = print().toString() -} - -private object AccountNoneTlbConstructor : TlbConstructor( - schema = "account_none\$0 = Account;" -) { - override fun storeTlb(cellBuilder: CellBuilder, value: AccountNone) { - } - - override fun loadTlb(cellSlice: CellSlice): AccountNone { - return AccountNone - } -} diff --git a/block-tlb/src/AccountState.kt b/block-tlb/src/AccountState.kt index 4b24324c..84d65c0c 100644 --- a/block-tlb/src/AccountState.kt +++ b/block-tlb/src/AccountState.kt @@ -10,6 +10,11 @@ import org.ton.tlb.providers.TlbCombinatorProvider @JsonClassDiscriminator("@type") public sealed interface AccountState : TlbObject { + /** + * Account status. + */ + public val status: AccountStatus + public companion object : TlbCombinatorProvider by AccountStateTlbCombinator } diff --git a/block-tlb/src/AccountStorage.kt b/block-tlb/src/AccountStorage.kt index 0541df18..f4c04d8b 100644 --- a/block-tlb/src/AccountStorage.kt +++ b/block-tlb/src/AccountStorage.kt @@ -11,6 +11,7 @@ import kotlin.jvm.JvmName @SerialName("account_storage") +@Deprecated("Use fields from Account instead") public data class AccountStorage( @SerialName("last_trans_lt") @get:JvmName("lastTransLt") diff --git a/block-tlb/src/AccountUninit.kt b/block-tlb/src/AccountUninit.kt index 0077d39e..577ed04a 100644 --- a/block-tlb/src/AccountUninit.kt +++ b/block-tlb/src/AccountUninit.kt @@ -10,6 +10,8 @@ import org.ton.tlb.providers.TlbConstructorProvider @SerialName("account_uninit") public object AccountUninit : AccountState, TlbConstructorProvider by AccountUninitTlbConstructor { + override val status: AccountStatus get() = AccountStatus.UNINIT + override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { return printer.type("account_uninit") } diff --git a/block-tlb/src/TrActionPhase.kt b/block-tlb/src/ActionPhase.kt similarity index 93% rename from block-tlb/src/TrActionPhase.kt rename to block-tlb/src/ActionPhase.kt index c8466ac4..02b114a4 100644 --- a/block-tlb/src/TrActionPhase.kt +++ b/block-tlb/src/ActionPhase.kt @@ -16,7 +16,7 @@ import org.ton.tlb.providers.TlbConstructorProvider @SerialName("tr_phase_action") -public data class TrActionPhase( +public data class ActionPhase( val success: Boolean, val valid: Boolean, @SerialName("no_funds") val noFunds: Boolean, @@ -57,10 +57,10 @@ public data class TrActionPhase( override fun toString(): String = print().toString() - public companion object : TlbConstructorProvider by TrActionPhaseTlbConstructor + public companion object : TlbConstructorProvider by TrActionPhaseTlbConstructor } -private object TrActionPhaseTlbConstructor : TlbConstructor( +private object TrActionPhaseTlbConstructor : TlbConstructor( schema = "tr_phase_action\$_ success:Bool valid:Bool no_funds:Bool " + "status_change:AccStatusChange " + "total_fwd_fees:(Maybe Coins) total_action_fees:(Maybe Coins) " + @@ -74,7 +74,7 @@ private object TrActionPhaseTlbConstructor : TlbConstructor( override fun storeTlb( cellBuilder: CellBuilder, - value: TrActionPhase + value: ActionPhase ) = cellBuilder { storeBit(value.success) storeBit(value.valid) @@ -94,7 +94,7 @@ private object TrActionPhaseTlbConstructor : TlbConstructor( override fun loadTlb( cellSlice: CellSlice - ): TrActionPhase = cellSlice { + ): ActionPhase = cellSlice { val success = loadBit() val valid = loadBit() val noFunds = loadBit() @@ -109,7 +109,7 @@ private object TrActionPhaseTlbConstructor : TlbConstructor( val msgCreated = loadUInt(16).toInt() val actionListHash = loadBits(256) val totMsgSize = loadTlb(StorageUsedShort) - TrActionPhase( + ActionPhase( success, valid, noFunds, diff --git a/block-tlb/src/BouncePhase.kt b/block-tlb/src/BouncePhase.kt new file mode 100644 index 00000000..6e0e3162 --- /dev/null +++ b/block-tlb/src/BouncePhase.kt @@ -0,0 +1,102 @@ +package org.ton.block + +import org.ton.cell.CellBuilder +import org.ton.cell.CellSlice +import org.ton.tlb.TlbCodec +import org.ton.tlb.loadTlb +import org.ton.tlb.storeTlb + +/** + * Bounce phase info. + * + * At this phase, some funds are returned to the sender. + * + * @see [TransactionInfo] + */ +public sealed interface BouncePhase { + public val forwardFees: Coins + + /** + * Skipped bounce phase info. + */ + public data class NoFunds( + /** + * The total number of unique cells (bits / refs) of the bounced message. + */ + val msgSize: StorageUsedShort, + + /** + * Required amount of coins to send the bounced message. + */ + override val forwardFees: Coins + ) : BouncePhase + + /** + * Bounce phase was executed. + */ + public data class Executed( + /** + * The total number of unique cells (bits / refs) of the bounced message. + */ + val msgSize: StorageUsedShort = StorageUsedShort.ZERO, + + /** + * The part of fees for the validators. + */ + val forwardFeesCollected: Coins = Coins.ZERO, + + /** + * Message forwarding fee. + */ + override val forwardFees: Coins = Coins.ZERO, + ) : BouncePhase + + public companion object : TlbCodec by BouncePhaseCodec +} + +private object BouncePhaseCodec : TlbCodec { + private val ZERO = BouncePhase.Executed() + + override fun storeTlb(builder: CellBuilder, value: BouncePhase) { + when (value) { + is BouncePhase.Executed -> { // tr_phase_bounce_ok$1 + builder.storeBoolean(true) + builder.storeTlb(StorageUsedShort, value.msgSize) // msg_size:StorageUsed + builder.storeTlb(Coins, value.forwardFeesCollected) // msg_fees:Coins + builder.storeTlb(Coins, value.forwardFees) // fwd_fees:Coins + } + + is BouncePhase.NoFunds -> { // tr_phase_bounce_nofunds$01 + builder.storeUInt(0b01, 2) + builder.storeTlb(StorageUsedShort, value.msgSize) // msg_size:StorageUsed + builder.storeTlb(Coins, value.forwardFees) // req_fwd_fees:Coins + } + } + } + + override fun loadTlb(slice: CellSlice): BouncePhase { + when (val tag = slice.preloadUInt(2).toInt()) { + 0b00 -> { + slice.skipBits(2) + return ZERO + } + + 0b01 -> { // tr_phase_bounce_nofunds$01 + slice.skipBits(2) + val msgSize = slice.loadTlb(StorageUsedShort) + val forwardFees = slice.loadTlb(Coins) + return BouncePhase.NoFunds(msgSize, forwardFees) + } + + 0b10, 0b11 -> { // tr_phase_bounce_ok$1 + slice.skipBits(1) + val msgSize = slice.loadTlb(StorageUsedShort) + val forwardFees = slice.loadTlb(Coins) + return BouncePhase.Executed(msgSize, forwardFees) + } + + else -> throw IllegalArgumentException("Invalid bounce phase tag: ${tag.toString(2)}") + } + } + +} \ No newline at end of file diff --git a/block-tlb/src/CommonMsgInfo.kt b/block-tlb/src/CommonMsgInfo.kt index 8d5f0334..22ffc10f 100644 --- a/block-tlb/src/CommonMsgInfo.kt +++ b/block-tlb/src/CommonMsgInfo.kt @@ -23,3 +23,5 @@ private object CommonMsgInfoTlbCombinator : TlbCombinator( ExtInMsgInfo::class to ExtInMsgInfo, ExtOutMsgInfo::class to ExtOutMsgInfo ) + +public val CommonMsgInfo?.value: CurrencyCollection get() = (this as? IntMsgInfo)?.value ?: CurrencyCollection.ZERO diff --git a/block-tlb/src/TrComputePhase.kt b/block-tlb/src/ComputePhase.kt similarity index 59% rename from block-tlb/src/TrComputePhase.kt rename to block-tlb/src/ComputePhase.kt index 674aa560..54ae734f 100644 --- a/block-tlb/src/TrComputePhase.kt +++ b/block-tlb/src/ComputePhase.kt @@ -9,12 +9,12 @@ import org.ton.tlb.providers.TlbCombinatorProvider @JsonClassDiscriminator("@type") -public sealed interface TrComputePhase : TlbObject { - public companion object : TlbCombinatorProvider by TrComputePhaseTlbCombinator +public sealed interface ComputePhase : TlbObject { + public companion object : TlbCombinatorProvider by TrComputePhaseTlbCombinator } -private object TrComputePhaseTlbCombinator : TlbCombinator( - TrComputePhase::class, +private object TrComputePhaseTlbCombinator : TlbCombinator( + ComputePhase::class, TrPhaseComputeSkipped::class to TrPhaseComputeSkipped, TrPhaseComputeVm::class to TrPhaseComputeVm, ) diff --git a/block-tlb/src/TrCreditPhase.kt b/block-tlb/src/CreditPhase.kt similarity index 77% rename from block-tlb/src/TrCreditPhase.kt rename to block-tlb/src/CreditPhase.kt index 723e68e1..d70f29e3 100644 --- a/block-tlb/src/TrCreditPhase.kt +++ b/block-tlb/src/CreditPhase.kt @@ -8,9 +8,7 @@ import org.ton.tlb.* import org.ton.tlb.TlbConstructor import org.ton.tlb.providers.TlbConstructorProvider -@SerialName("tr_phase_credit") - -public data class TrCreditPhase( +public data class CreditPhase( @SerialName("due_fees_collected") val dueFeesCollected: Maybe, val credit: CurrencyCollection ) : TlbObject { @@ -23,17 +21,17 @@ public data class TrCreditPhase( override fun toString(): String = print().toString() - public companion object : TlbConstructorProvider by TrCreditPhaseTlbConstructor + public companion object : TlbConstructorProvider by TrCreditPhaseTlbConstructor } -private object TrCreditPhaseTlbConstructor : TlbConstructor( +private object TrCreditPhaseTlbConstructor : TlbConstructor( schema = "tr_phase_credit\$_ due_fees_collected:(Maybe Coins) credit:CurrencyCollection = TrCreditPhase;" ) { val maybeCoins = Maybe.tlbCodec(Coins) override fun storeTlb( cellBuilder: CellBuilder, - value: TrCreditPhase + value: CreditPhase ) = cellBuilder { storeTlb(maybeCoins, value.dueFeesCollected) storeTlb(CurrencyCollection, value.credit) @@ -41,9 +39,9 @@ private object TrCreditPhaseTlbConstructor : TlbConstructor( override fun loadTlb( cellSlice: CellSlice - ): TrCreditPhase = cellSlice { + ): CreditPhase = cellSlice { val dueFeesCollected = loadTlb(maybeCoins) val credit = loadTlb(CurrencyCollection) - TrCreditPhase(dueFeesCollected, credit) + CreditPhase(dueFeesCollected, credit) } } diff --git a/block-tlb/src/CurrencyCollection.kt b/block-tlb/src/CurrencyCollection.kt index f4dd3aab..3727692e 100644 --- a/block-tlb/src/CurrencyCollection.kt +++ b/block-tlb/src/CurrencyCollection.kt @@ -2,11 +2,12 @@ package org.ton.block import org.ton.cell.CellBuilder import org.ton.cell.CellSlice -import org.ton.cell.invoke +import org.ton.kotlin.cell.CellContext import org.ton.kotlin.currency.VarUInt248 import org.ton.kotlin.dict.Dictionary -import org.ton.tlb.* import org.ton.tlb.TlbConstructor +import org.ton.tlb.TlbObject +import org.ton.tlb.TlbPrettyPrinter import org.ton.tlb.providers.TlbConstructorProvider /** @@ -16,12 +17,12 @@ public data class CurrencyCollection( /** * Amount in native currency. */ - val coins: Coins, + val coins: Coins = Coins.ZERO, /** * Amounts in other currencies. */ - val other: ExtraCurrencyCollection + val other: ExtraCurrencyCollection = ExtraCurrencyCollection.EMPTY ) : TlbObject { public constructor() : this(Coins.ZERO, ExtraCurrencyCollection.EMPTY) @@ -31,6 +32,12 @@ public data class CurrencyCollection( public constructor(coins: Coins, other: Dictionary) : this(coins, ExtraCurrencyCollection(other)) + public constructor(other: Map) : this(Coins.ZERO, ExtraCurrencyCollection(other)) + + public constructor(other: Dictionary) : this(Coins.ZERO, ExtraCurrencyCollection(other)) + + public operator fun get(id: Int): VarUInt248? = other[id] + override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("currencies") { field("coins", coins) field("other", other) @@ -60,17 +67,20 @@ private object CurrencyCollectionTlbConstructor : TlbConstructor { return true } - override fun hashCode(): Int = super.hashCode() - override fun toString(): String = this.asSequence().joinToString { (key, value) -> "$key=$value" } public companion object : TlbConstructorProvider by ExtraCurrencyCollectionTlbConstructor { @@ -55,21 +53,18 @@ private object ExtraCurrencyCollectionTlbConstructor : TlbConstructor( info: CommonMsgInfo, init: StateInit?, body: X, + bodyCodec: TlbCodec, layout: MessageLayout ) : this( info = info, init = layout.eitherInit(init).toMaybe(), - body = layout.eitherBody(body), + body = layout.eitherBody(body, bodyCodec), ) override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { diff --git a/block-tlb/src/MessageRelaxed.kt b/block-tlb/src/MessageRelaxed.kt index d34fda67..d1b8f38c 100644 --- a/block-tlb/src/MessageRelaxed.kt +++ b/block-tlb/src/MessageRelaxed.kt @@ -20,11 +20,12 @@ public data class MessageRelaxed( info: CommonMsgInfoRelaxed, init: StateInit?, body: X, + bodyCodec: TlbCodec, layout: MessageLayout ) : this( info = info, init = layout.eitherInit(init).toMaybe(), - body = layout.eitherBody(body), + body = layout.eitherBody(body, bodyCodec), ) override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { diff --git a/block-tlb/src/OldMcBlocksInfo.kt b/block-tlb/src/OldMcBlocksInfo.kt index 834ca18d..bf872ec9 100644 --- a/block-tlb/src/OldMcBlocksInfo.kt +++ b/block-tlb/src/OldMcBlocksInfo.kt @@ -3,6 +3,7 @@ package org.ton.block import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.hashmap.HashmapAugE +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbCodec import org.ton.tlb.TlbObject import org.ton.tlb.TlbPrettyPrinter @@ -25,11 +26,11 @@ public value class OldMcBlocksInfo( private object OldMcBlocksInfoTlbCodec : TlbCodec { private val codec = HashmapAugE.tlbCodec(32, KeyExtBlkRef, KeyMaxLt) - override fun storeTlb(cellBuilder: CellBuilder, value: OldMcBlocksInfo) { - codec.storeTlb(cellBuilder, value.value) + override fun storeTlb(cellBuilder: CellBuilder, value: OldMcBlocksInfo, context: CellContext) { + codec.storeTlb(cellBuilder, value.value, context) } - override fun loadTlb(cellSlice: CellSlice): OldMcBlocksInfo { - return OldMcBlocksInfo(codec.loadTlb(cellSlice)) + override fun loadTlb(cellSlice: CellSlice, context: CellContext): OldMcBlocksInfo { + return OldMcBlocksInfo(codec.loadTlb(cellSlice, context)) } } diff --git a/block-tlb/src/ShardAccount.kt b/block-tlb/src/ShardAccount.kt index d5c9b648..afd28e30 100644 --- a/block-tlb/src/ShardAccount.kt +++ b/block-tlb/src/ShardAccount.kt @@ -1,21 +1,43 @@ package org.ton.block -import kotlinx.serialization.SerialName -import org.ton.bitstring.BitString +import kotlinx.io.bytestring.ByteString import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.invoke -import org.ton.tlb.* +import org.ton.kotlin.cell.CellContext +import org.ton.tlb.CellRef import org.ton.tlb.TlbConstructor +import org.ton.tlb.TlbObject +import org.ton.tlb.TlbPrettyPrinter import org.ton.tlb.providers.TlbConstructorProvider - -@SerialName("account_descr") +/** + * Shard accounts entry. + */ public data class ShardAccount( - val account: CellRef, - @SerialName("last_trans_hash") val lastTransHash: BitString, - @SerialName("last_trans_lt") val lastTransLt: ULong + /** + * Optional reference to account state. + */ + val account: CellRef, + + /** + * The exact hash of the last transaction. + */ + val lastTransHash: ByteString, + + /** + * The exact logical time of the last transaction. + */ + val lastTransLt: Long ) : TlbObject { + + /** + * Load account data from cell. + */ + public fun loadAccount(context: CellContext = CellContext.EMPTY): Account? { + return account.load(context) + } + override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("account_descr") { field("account", account) field("last_trans_hash", lastTransHash) @@ -24,6 +46,23 @@ public data class ShardAccount( override fun toString(): String = print().toString() + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + other as ShardAccount + if (lastTransLt != other.lastTransLt) return false + if (account != other.account) return false + if (lastTransHash != other.lastTransHash) return false + return true + } + + override fun hashCode(): Int { + var result = lastTransLt.hashCode() + result = 31 * result + account.hashCode() + result = 31 * result + lastTransHash.hashCode() + return result + } + public companion object : TlbConstructorProvider by ShardAccountTlbConstructor } @@ -34,17 +73,17 @@ private object ShardAccountTlbConstructor : TlbConstructor( cellBuilder: CellBuilder, value: ShardAccount ) = cellBuilder { - storeRef(Account, value.account) - storeBits(value.lastTransHash) - storeUInt64(value.lastTransLt) + storeRef(value.account.cell) + storeBytes(value.lastTransHash.toByteArray()) + storeULong(value.lastTransLt.toULong()) } override fun loadTlb( cellSlice: CellSlice ): ShardAccount = cellSlice { - val account = loadRef(Account) - val lastTransHash = loadBits(256) - val lastTransLt = loadUInt64() + val account = CellRef(loadRef(), Account) + val lastTransHash = loadByteString(32) + val lastTransLt = loadULong().toLong() ShardAccount(account, lastTransHash, lastTransLt) } } diff --git a/block-tlb/src/ShardAccounts.kt b/block-tlb/src/ShardAccounts.kt index 361a96b9..e6a14513 100644 --- a/block-tlb/src/ShardAccounts.kt +++ b/block-tlb/src/ShardAccounts.kt @@ -3,6 +3,7 @@ package org.ton.block import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.hashmap.HashmapAugE +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbCodec import org.ton.tlb.TlbObject import org.ton.tlb.TlbPrettyPrinter @@ -25,11 +26,11 @@ public value class ShardAccounts( private object ShardAccountsTlbCodec : TlbCodec { private val codec = HashmapAugE.tlbCodec(256, ShardAccount, DepthBalanceInfo) - override fun storeTlb(cellBuilder: CellBuilder, value: ShardAccounts) { - codec.storeTlb(cellBuilder, value.x) + override fun storeTlb(cellBuilder: CellBuilder, value: ShardAccounts, context: CellContext) { + codec.storeTlb(cellBuilder, value.x, context) } - override fun loadTlb(cellSlice: CellSlice): ShardAccounts { - return ShardAccounts(codec.loadTlb(cellSlice)) + override fun loadTlb(cellSlice: CellSlice, context: CellContext): ShardAccounts { + return ShardAccounts(codec.loadTlb(cellSlice, context)) } } diff --git a/block-tlb/src/ShardHashes.kt b/block-tlb/src/ShardHashes.kt index a0921161..af371a48 100644 --- a/block-tlb/src/ShardHashes.kt +++ b/block-tlb/src/ShardHashes.kt @@ -3,6 +3,7 @@ package org.ton.block import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.hashmap.HashMapE +import org.ton.kotlin.cell.CellContext import org.ton.tlb.CellRef import org.ton.tlb.TlbCodec import org.ton.tlb.TlbObject @@ -25,11 +26,11 @@ public value class ShardHashes( private object ShardHashesTlbCodec : TlbCodec { private val codec = HashMapE.tlbCodec(32, CellRef.tlbCodec(BinTree.tlbCodec(ShardDescr))) - override fun storeTlb(cellBuilder: CellBuilder, value: ShardHashes) { - codec.storeTlb(cellBuilder, value.value) + override fun storeTlb(cellBuilder: CellBuilder, value: ShardHashes, context: CellContext) { + codec.storeTlb(cellBuilder, value.value, context) } - override fun loadTlb(cellSlice: CellSlice): ShardHashes { - return ShardHashes(codec.loadTlb(cellSlice)) + override fun loadTlb(cellSlice: CellSlice, context: CellContext): ShardHashes { + return ShardHashes(codec.loadTlb(cellSlice, context)) } } diff --git a/block-tlb/src/TrStoragePhase.kt b/block-tlb/src/StoragePhase.kt similarity index 81% rename from block-tlb/src/TrStoragePhase.kt rename to block-tlb/src/StoragePhase.kt index 117bc763..cf4f8df3 100644 --- a/block-tlb/src/TrStoragePhase.kt +++ b/block-tlb/src/StoragePhase.kt @@ -10,7 +10,7 @@ import org.ton.tlb.providers.TlbConstructorProvider @SerialName("tr_phase_storage") -public data class TrStoragePhase( +public data class StoragePhase( @SerialName("storage_fees_collected") val storageFeesCollected: Coins, @SerialName("storage_fees_due") val storageFeesDue: Maybe, @SerialName("status_change") val statusChange: AccStatusChange @@ -25,10 +25,10 @@ public data class TrStoragePhase( } } - public companion object : TlbConstructorProvider by TrStoragePhaseTlbConstructor + public companion object : TlbConstructorProvider by TrStoragePhaseTlbConstructor } -private object TrStoragePhaseTlbConstructor : TlbConstructor( +private object TrStoragePhaseTlbConstructor : TlbConstructor( schema = "tr_phase_storage\$_ storage_fees_collected:Coins " + "storage_fees_due:(Maybe Coins) " + "status_change:AccStatusChange " + @@ -38,7 +38,7 @@ private object TrStoragePhaseTlbConstructor : TlbConstructor( override fun storeTlb( cellBuilder: CellBuilder, - value: TrStoragePhase + value: StoragePhase ) = cellBuilder { storeTlb(Coins, value.storageFeesCollected) storeTlb(maybeCoins, value.storageFeesDue) @@ -47,10 +47,10 @@ private object TrStoragePhaseTlbConstructor : TlbConstructor( override fun loadTlb( cellSlice: CellSlice - ): TrStoragePhase = cellSlice { + ): StoragePhase = cellSlice { val storageFeesCollected = loadTlb(Coins) val storageFeesDue = loadTlb(maybeCoins) val statusChange = loadTlb(AccStatusChange) - TrStoragePhase(storageFeesCollected, storageFeesDue, statusChange) + StoragePhase(storageFeesCollected, storageFeesDue, statusChange) } } diff --git a/block-tlb/src/StorageUsedShort.kt b/block-tlb/src/StorageUsedShort.kt index 8252a5e3..2ad09ce7 100644 --- a/block-tlb/src/StorageUsedShort.kt +++ b/block-tlb/src/StorageUsedShort.kt @@ -1,17 +1,15 @@ package org.ton.block -import kotlinx.serialization.SerialName import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.invoke import org.ton.tlb.* +import org.ton.tlb.TlbConstructor import org.ton.tlb.providers.TlbConstructorProvider -@SerialName("storage_used_short") - public data class StorageUsedShort( - val cells: VarUInteger, - val bits: VarUInteger + val cells: Long, + val bits: Long ) : TlbObject { override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { return printer { @@ -24,7 +22,9 @@ public data class StorageUsedShort( override fun toString(): String = print().toString() - public companion object : TlbConstructorProvider by StorageUsedShortTlbConstructor + public companion object : TlbConstructorProvider by StorageUsedShortTlbConstructor { + public val ZERO: StorageUsedShort = StorageUsedShort(0, 0) + } } private object StorageUsedShortTlbConstructor : TlbConstructor( @@ -32,18 +32,19 @@ private object StorageUsedShortTlbConstructor : TlbConstructor ) { private val varUInteger7Codec = VarUInteger.tlbCodec(7) + @Suppress("DEPRECATION") override fun storeTlb( cellBuilder: CellBuilder, value: StorageUsedShort ) = cellBuilder { - storeTlb(varUInteger7Codec, value.cells) - storeTlb(varUInteger7Codec, value.bits) + storeTlb(varUInteger7Codec, VarUInteger(value.cells)) + storeTlb(varUInteger7Codec, VarUInteger(value.bits)) } override fun loadTlb( cellSlice: CellSlice ): StorageUsedShort = cellSlice { - val cells = loadTlb(varUInteger7Codec) - val bits = loadTlb(varUInteger7Codec) + val cells = loadTlb(varUInteger7Codec).value.toLong() + val bits = loadTlb(varUInteger7Codec).value.toLong() StorageUsedShort(cells, bits) } } diff --git a/block-tlb/src/TrBouncePhase.kt b/block-tlb/src/TrBouncePhase.kt deleted file mode 100644 index 203d2806..00000000 --- a/block-tlb/src/TrBouncePhase.kt +++ /dev/null @@ -1,21 +0,0 @@ -@file:Suppress("OPT_IN_USAGE") - -package org.ton.block - -import kotlinx.serialization.json.JsonClassDiscriminator -import org.ton.tlb.TlbCombinator -import org.ton.tlb.TlbObject -import org.ton.tlb.providers.TlbCombinatorProvider - -@JsonClassDiscriminator("@type") - -public sealed interface TrBouncePhase : TlbObject { - public companion object : TlbCombinatorProvider by TrBouncePhaseTlbCombinator -} - -private object TrBouncePhaseTlbCombinator : TlbCombinator( - TrBouncePhase::class, - TrPhaseBounceNegFunds::class to TrPhaseBounceNegFunds, - TrPhaseBounceNoFunds::class to TrPhaseBounceNoFunds, - TrPhaseBounceOk::class to TrPhaseBounceOk, -) diff --git a/block-tlb/src/TrPhaseBounceNegFunds.kt b/block-tlb/src/TrPhaseBounceNegFunds.kt deleted file mode 100644 index 753529a0..00000000 --- a/block-tlb/src/TrPhaseBounceNegFunds.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("tr_phase_bounce_negfunds") -public object TrPhaseBounceNegFunds : TrBouncePhase, - TlbConstructorProvider by TrPhaseBounceNegFundsTlbConstructor { - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("tr_phase_bounce_negfunds") - } - - override fun toString(): String = print().toString() -} - -private object TrPhaseBounceNegFundsTlbConstructor : TlbConstructor( - schema = "tr_phase_bounce_negfunds\$00 = TrBouncePhase;" -) { - override fun storeTlb(cellBuilder: CellBuilder, value: TrPhaseBounceNegFunds) = Unit - override fun loadTlb(cellSlice: CellSlice): TrPhaseBounceNegFunds = TrPhaseBounceNegFunds -} diff --git a/block-tlb/src/TrPhaseBounceNoFunds.kt b/block-tlb/src/TrPhaseBounceNoFunds.kt deleted file mode 100644 index a7657ce3..00000000 --- a/block-tlb/src/TrPhaseBounceNoFunds.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.loadTlb -import org.ton.tlb.providers.TlbConstructorProvider -import org.ton.tlb.storeTlb - - -@SerialName("tr_phase_bounce_nofunds") -public data class TrPhaseBounceNoFunds( - val msgSize: StorageUsedShort, - val reqFwdFees: Coins -) : TrBouncePhase { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("tr_phase_bounce_nofunds") { - field("msg_size", msgSize) - field("reqFwdFees", reqFwdFees) - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TrPhaseBounceNoFundsTlbConstructor -} - -private object TrPhaseBounceNoFundsTlbConstructor : TlbConstructor( - schema = "tr_phase_bounce_nofunds\$01 msg_size:StorageUsedShort req_fwd_fees:Coins = TrBouncePhase;" -) { - override fun storeTlb( - cellBuilder: CellBuilder, - value: TrPhaseBounceNoFunds - ) = cellBuilder { - storeTlb(StorageUsedShort, value.msgSize) - storeTlb(Coins, value.reqFwdFees) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TrPhaseBounceNoFunds = cellSlice { - val msgSize = loadTlb(StorageUsedShort) - val coins = loadTlb(Coins) - TrPhaseBounceNoFunds(msgSize, coins) - } -} diff --git a/block-tlb/src/TrPhaseBounceOk.kt b/block-tlb/src/TrPhaseBounceOk.kt deleted file mode 100644 index 4d028898..00000000 --- a/block-tlb/src/TrPhaseBounceOk.kt +++ /dev/null @@ -1,54 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.loadTlb -import org.ton.tlb.providers.TlbConstructorProvider -import org.ton.tlb.storeTlb - - -@SerialName("tr_phase_bounce_ok") -public data class TrPhaseBounceOk( - val msgSize: StorageUsedShort, - val msgFees: Coins, - val fwdFees: Coins -) : TrBouncePhase { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("tr_phase_bounce_ok") { - field("msg_size", msgSize) - field("msg_fees", msgFees) - field("fwd_fees", fwdFees) - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TrPhaseBounceOkTlbConstructor -} - -private object TrPhaseBounceOkTlbConstructor : TlbConstructor( - schema = "tr_phase_bounce_ok\$1 msg_size:StorageUsedShort msg_fees:Coins fwd_fees:Coins = TrBouncePhase;" -) { - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TrPhaseBounceOk - ) = cellBuilder { - storeTlb(StorageUsedShort, value.msgSize) - storeTlb(Coins, value.msgFees) - storeTlb(Coins, value.fwdFees) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TrPhaseBounceOk = cellSlice { - val msgSize = loadTlb(StorageUsedShort) - val msgFees = loadTlb(Coins) - val fwdFees = loadTlb(Coins) - TrPhaseBounceOk(msgSize, msgFees, fwdFees) - } -} diff --git a/block-tlb/src/TrPhaseComputeSkipped.kt b/block-tlb/src/TrPhaseComputeSkipped.kt index 32c58cbc..15914d19 100644 --- a/block-tlb/src/TrPhaseComputeSkipped.kt +++ b/block-tlb/src/TrPhaseComputeSkipped.kt @@ -14,7 +14,7 @@ import org.ton.tlb.storeTlb @SerialName("tr_phase_compute_skipped") public data class TrPhaseComputeSkipped( val reason: ComputeSkipReason -) : TrComputePhase { +) : ComputePhase { override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { type("tr_phase_compute_skipped") { field("reason", reason) diff --git a/block-tlb/src/TrPhaseComputeVm.kt b/block-tlb/src/TrPhaseComputeVm.kt index 6c94d1c3..30944a7a 100644 --- a/block-tlb/src/TrPhaseComputeVm.kt +++ b/block-tlb/src/TrPhaseComputeVm.kt @@ -6,6 +6,7 @@ import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.invoke import org.ton.tlb.* +import org.ton.tlb.TlbConstructor import org.ton.tlb.constructor.IntTlbConstructor import org.ton.tlb.providers.TlbConstructorProvider @@ -17,7 +18,7 @@ public data class TrPhaseComputeVm( @SerialName("account_activated") val accountActivated: Boolean, @SerialName("gas_fees") val gasFees: Coins, val r1: CellRef -) : TrComputePhase { +) : ComputePhase { override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { type("tr_phase_compute_vm") { field("success", success) diff --git a/block-tlb/src/TransMergeInstall.kt b/block-tlb/src/TransMergeInstall.kt deleted file mode 100644 index da642c6d..00000000 --- a/block-tlb/src/TransMergeInstall.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("trans_merge_install") -public data class TransMergeInstall( - @SerialName("split_info") val splitInfo: SplitMergeInfo, - @SerialName("prepare_transaction") val prepareTransaction: CellRef, - @SerialName("storage_ph") val storagePh: Maybe, - @SerialName("credit_ph") val creditPh: Maybe, - @SerialName("compute_ph") val computePh: TrComputePhase, - val action: Maybe>, - val aborted: Boolean, - val destroyed: Boolean -) : TransactionDescr { - public companion object : TlbConstructorProvider by TransMergeInstallTlbConstructor - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("trans_merge_install") { - field("split_info", splitInfo) - field("prepare_transaction", prepareTransaction) - field("storage_ph", storagePh) - field("credit_ph", creditPh) - field("compute_ph", computePh) - field("action", action) - field("aborted", aborted) - field("destroyed", destroyed) - } - } - - override fun toString(): String = print().toString() -} - -private object TransMergeInstallTlbConstructor : TlbConstructor( - schema = "trans_merge_install\$0111 split_info:SplitMergeInfo\n" + - " prepare_transaction:^Transaction\n" + - " storage_ph:(Maybe TrStoragePhase)\n" + - " credit_ph:(Maybe TrCreditPhase)\n" + - " compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)\n" + - " aborted:Bool destroyed:Bool\n" + - " = TransactionDescr;" -) { - val maybeTrStoragePhase = Maybe.tlbCodec(TrStoragePhase) - val maybeTrCreditPhase = Maybe.tlbCodec(TrCreditPhase) - val maybeTrActionPhase = Maybe.tlbCodec(CellRef(TrActionPhase)) - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransMergeInstall - ) = cellBuilder { - storeTlb(SplitMergeInfo, value.splitInfo) - storeRef(Transaction, value.prepareTransaction) - storeTlb(maybeTrStoragePhase, value.storagePh) - storeTlb(maybeTrCreditPhase, value.creditPh) - storeTlb(TrComputePhase, value.computePh) - storeTlb(maybeTrActionPhase, value.action) - storeBit(value.aborted) - storeBit(value.destroyed) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransMergeInstall = cellSlice { - val splitInfo = loadTlb(SplitMergeInfo) - val prepareTransaction = loadRef(Transaction) - val storagePh = loadTlb(maybeTrStoragePhase) - val creditPh = loadTlb(maybeTrCreditPhase) - val computePh = loadTlb(TrComputePhase) - val action = loadTlb(maybeTrActionPhase) - val aborted = loadBit() - val destroyed = loadBit() - TransMergeInstall(splitInfo, prepareTransaction, storagePh, creditPh, computePh, action, aborted, destroyed) - } -} diff --git a/block-tlb/src/TransMergePrepare.kt b/block-tlb/src/TransMergePrepare.kt deleted file mode 100644 index 9076090f..00000000 --- a/block-tlb/src/TransMergePrepare.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.loadTlb -import org.ton.tlb.providers.TlbConstructorProvider -import org.ton.tlb.storeTlb - - -@SerialName("trans_merge_prepare") -public data class TransMergePrepare( - @SerialName("split_info") val splitInfo: SplitMergeInfo, - @SerialName("storage_ph") val storagePh: TrStoragePhase, - val aborted: Boolean -) : TransactionDescr { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("trans_merge_prepare") { - field("split_info", splitInfo) - field("storage_ph", storagePh) - field("aborted", aborted) - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TransMergePrepareTlbConstructor -} - -private object TransMergePrepareTlbConstructor : TlbConstructor( - schema = "trans_merge_prepare\$0110 split_info:SplitMergeInfo\n" + - " storage_ph:TrStoragePhase aborted:Bool\n" + - " = TransactionDescr;" -) { - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransMergePrepare - ) = cellBuilder { - storeTlb(SplitMergeInfo, value.splitInfo) - storeTlb(TrStoragePhase, value.storagePh) - storeBit(value.aborted) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransMergePrepare = cellSlice { - val splitInfo = loadTlb(SplitMergeInfo) - val storagePh = loadTlb(TrStoragePhase) - val aborted = loadBit() - TransMergePrepare(splitInfo, storagePh, aborted) - } -} diff --git a/block-tlb/src/TransOrd.kt b/block-tlb/src/TransOrd.kt deleted file mode 100644 index 5013b79a..00000000 --- a/block-tlb/src/TransOrd.kt +++ /dev/null @@ -1,88 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbConstructorProvider - -@SerialName("trans_ord") - -public data class TransOrd( - @SerialName("credit_first") val creditFirst: Boolean, - @SerialName("storage_ph") val storagePh: Maybe, - @SerialName("credit_ph") val creditPh: Maybe, - @SerialName("compute_ph") val computePh: TrComputePhase, - val action: Maybe>, - val aborted: Boolean, - val bounce: Maybe, - val destroyed: Boolean -) : TransactionDescr { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { - return printer { - type("trans_ord") { - field("credit_first", creditFirst) - field("storage_ph", storagePh) - field("credit_ph", creditPh) - field("compute_ph", computePh) - field("action", action) - field("aborted", aborted) - field("bounce", bounce) - field("destroyed", destroyed) - } - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TransOrdTlbConstructor -} - -private object TransOrdTlbConstructor : TlbConstructor( - schema = "trans_ord\$0000 credit_first:Bool " + - "storage_ph:(Maybe TrStoragePhase) " + - "credit_ph:(Maybe TrCreditPhase) " + - "compute_ph:TrComputePhase " + - "action:(Maybe ^TrActionPhase) " + - "aborted:Bool " + - "bounce:(Maybe TrBouncePhase) " + - "destroyed:Bool " + - "= TransactionDescr;" -) { - val maybeTrStoragePhase = Maybe.tlbCodec(TrStoragePhase) - val maybeTrCreditPhase = Maybe.tlbCodec(TrCreditPhase) - val maybeTrActionPhase = Maybe.tlbCodec(CellRef.tlbCodec(TrActionPhase)) - val maybeTrBouncePhase = Maybe.tlbCodec(TrBouncePhase) - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransOrd - ) = cellBuilder { - storeBit(value.creditFirst) - storeTlb(maybeTrStoragePhase, value.storagePh) - storeTlb(maybeTrCreditPhase, value.creditPh) - storeTlb(TrComputePhase, value.computePh) - storeTlb(maybeTrActionPhase, value.action) - storeBit(value.aborted) - storeTlb(maybeTrBouncePhase, value.bounce) - storeBit(value.destroyed) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransOrd = cellSlice { - val creditFirst = loadBit() - val storagePh = loadTlb(maybeTrStoragePhase) - val creditPh = loadTlb(maybeTrCreditPhase) - val computePh = loadTlb(TrComputePhase) - val action = loadTlb(maybeTrActionPhase) - val aborted = loadBit() - val bounce = loadTlb(maybeTrBouncePhase) - val destroyed = loadBit() - TransOrd( - creditFirst, storagePh, creditPh, computePh, action, aborted, bounce, destroyed - ) - } -} diff --git a/block-tlb/src/TransSplitInstall.kt b/block-tlb/src/TransSplitInstall.kt deleted file mode 100644 index 74583032..00000000 --- a/block-tlb/src/TransSplitInstall.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("trans_split_install") -public data class TransSplitInstall( - @SerialName("split_info") val splitInfo: SplitMergeInfo, - @SerialName("prepare_transaction") val prepareTransaction: CellRef, - val installed: Boolean -) : TransactionDescr { - public companion object : TlbConstructorProvider by TransSplitInstallTlbConstructor - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { - return printer { - type("trans_split_install") { - field("split_info", splitInfo) - field("prepare_transaction", prepareTransaction) - field("installed", installed) - } - } - } - - override fun toString(): String = print().toString() -} - -private object TransSplitInstallTlbConstructor : TlbConstructor( - schema = "trans_split_install\$0101 split_info:SplitMergeInfo\n" + - " prepare_transaction:^Transaction\n" + - " installed:Bool = TransactionDescr;" -) { - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransSplitInstall - ) = cellBuilder { - storeTlb(SplitMergeInfo, value.splitInfo) - storeRef(Transaction, value.prepareTransaction) - storeBit(value.installed) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransSplitInstall = cellSlice { - val splitInfo = loadTlb(SplitMergeInfo) - val prepareTransaction = loadRef(Transaction) - val installed = loadBit() - TransSplitInstall(splitInfo, prepareTransaction, installed) - } -} diff --git a/block-tlb/src/TransSplitPrepare.kt b/block-tlb/src/TransSplitPrepare.kt deleted file mode 100644 index 73fd14ad..00000000 --- a/block-tlb/src/TransSplitPrepare.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("trans_split_prepare") -public data class TransSplitPrepare( - val splitInfo: SplitMergeInfo, - val storagePh: Maybe, - val computePh: TrComputePhase, - val action: Maybe>, - val aborted: Boolean, - val destroyed: Boolean -) : TransactionDescr { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("trans_split_prepare") { - field("split_info", splitInfo) - field("storage_ph", storagePh) - field("compute_ph", computePh) - field("action", action) - field("aborted", aborted) - field("destroyed", destroyed) - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TransSplitPrepareTlbConstructor -} - -private object TransSplitPrepareTlbConstructor : TlbConstructor( - schema = "trans_split_prepare\$0100 " + - " split_info:SplitMergeInfo\n" + - " storage_ph:(Maybe TrStoragePhase)\n" + - " compute_ph:TrComputePhase " + - " action:(Maybe ^TrActionPhase)\n" + - " aborted:Bool destroyed:Bool\n" + - " = TransactionDescr;" -) { - val maybeTrStoragePhase = Maybe(TrStoragePhase) - val maybeTrActionPhase = Maybe(CellRef(TrActionPhase)) - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransSplitPrepare - ) = cellBuilder { - storeTlb(SplitMergeInfo, value.splitInfo) - storeTlb(maybeTrStoragePhase, value.storagePh) - storeTlb(TrComputePhase, value.computePh) - storeTlb(maybeTrActionPhase, value.action) - storeBit(value.aborted) - storeBit(value.destroyed) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransSplitPrepare = cellSlice { - val splitInfo = loadTlb(SplitMergeInfo) - val storagePh = loadTlb(maybeTrStoragePhase) - val computePh = loadTlb(TrComputePhase) - val action = loadTlb(maybeTrActionPhase) - val aborted = loadBit() - val destroyed = loadBit() - TransSplitPrepare(splitInfo, storagePh, computePh, action, aborted, destroyed) - } -} diff --git a/block-tlb/src/TransStorage.kt b/block-tlb/src/TransStorage.kt deleted file mode 100644 index 5da7a678..00000000 --- a/block-tlb/src/TransStorage.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.TlbConstructor -import org.ton.tlb.TlbPrettyPrinter -import org.ton.tlb.loadTlb -import org.ton.tlb.providers.TlbConstructorProvider -import org.ton.tlb.storeTlb - - -@SerialName("trans_storage") -public data class TransStorage( - @SerialName("storage_ph") val storagePh: TrStoragePhase -) : TransactionDescr { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("storage_ph") { - field("storage_ph", storagePh) - } - } - - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TransStorageTlbConstructor -} - -private object TransStorageTlbConstructor : TlbConstructor( - schema = "trans_storage\$0001 storage_ph:TrStoragePhase = TransactionDescr;" -) { - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransStorage - ) = cellBuilder { - storeTlb(TrStoragePhase, value.storagePh) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransStorage = cellSlice { - val storagePh = loadTlb(TrStoragePhase) - TransStorage(storagePh) - } -} diff --git a/block-tlb/src/TransTickTock.kt b/block-tlb/src/TransTickTock.kt deleted file mode 100644 index 4b86629e..00000000 --- a/block-tlb/src/TransTickTock.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.ton.block - -import kotlinx.serialization.SerialName -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbConstructorProvider - - -@SerialName("trans_tick_tock") -public data class TransTickTock( - @SerialName("is_tock") val isTock: Boolean, - @SerialName("storage_ph") val storagePh: TrStoragePhase, - @SerialName("compute_ph") val computePh: TrComputePhase, - val action: Maybe>, - val aborted: Boolean, - val destroyed: Boolean -) : TransactionDescr { - override fun toString(): String = print().toString() - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("trans_tick_tock") { - field("is_tock", isTock) - field("storage_ph", storagePh) - field("compute_ph", computePh) - field("action", action) - field("aborted", aborted) - field("destroyed", destroyed) - } - } - - public companion object : TlbConstructorProvider by TransTickTockTlbConstructor -} - -private object TransTickTockTlbConstructor : TlbConstructor( - schema = "trans_tick_tock\$001 is_tock:Bool storage_ph:TrStoragePhase\n" + - " compute_ph:TrComputePhase action:(Maybe ^TrActionPhase)\n" + - " aborted:Bool destroyed:Bool = TransactionDescr;" -) { - val maybeTrActionPhase = Maybe.tlbCodec(CellRef.tlbCodec(TrActionPhase)) - - override fun storeTlb( - cellBuilder: CellBuilder, - value: TransTickTock - ) = cellBuilder { - storeBit(value.isTock) - storeTlb(TrStoragePhase, value.storagePh) - storeTlb(TrComputePhase, value.computePh) - storeTlb(maybeTrActionPhase, value.action) - storeBit(value.aborted) - storeBit(value.destroyed) - } - - override fun loadTlb( - cellSlice: CellSlice - ): TransTickTock = cellSlice { - val isTock = loadBit() - val storagePh = loadTlb(TrStoragePhase) - val computePh = loadTlb(TrComputePhase) - val action = loadTlb(maybeTrActionPhase) - val aborted = loadBit() - val destroyed = loadBit() - TransTickTock(isTock, storagePh, computePh, action, aborted, destroyed) - } -} diff --git a/block-tlb/src/Transaction.kt b/block-tlb/src/Transaction.kt index b5af3965..0427cfc6 100644 --- a/block-tlb/src/Transaction.kt +++ b/block-tlb/src/Transaction.kt @@ -1,197 +1,162 @@ package org.ton.block -import kotlinx.serialization.SerialName -import org.ton.bitstring.BitString -import org.ton.cell.Cell +import kotlinx.io.bytestring.ByteString import org.ton.cell.CellBuilder import org.ton.cell.CellSlice -import org.ton.cell.invoke -import org.ton.hashmap.HashMapE -import org.ton.tlb.* -import org.ton.tlb.TlbConstructor -import org.ton.tlb.providers.TlbCombinatorProvider -import org.ton.tlb.providers.TlbConstructorProvider -import kotlin.jvm.JvmName - - -@SerialName("transaction") +import org.ton.cell.loadRef +import org.ton.cell.storeRef +import org.ton.kotlin.cell.CellContext +import org.ton.kotlin.dict.Dictionary +import org.ton.kotlin.dict.DictionaryKeyCodec +import org.ton.tlb.CellRef +import org.ton.tlb.TlbCodec +import org.ton.tlb.constructor.RemainingTlbCodec + +/** + * Blockchain transaction. + */ public data class Transaction( - @SerialName("account_addr") - @get:JvmName("accountAddr") - val accountAddr: BitString, // account_addr : bits256 - - @SerialName("lt") - @get:JvmName("lt") - val lt: ULong, // lt : uint64 - - @SerialName("prev_trans_hash") - @get:JvmName("prevTransHash") - val prevTransHash: BitString, // prev_trans_hash : bits256 - - @SerialName("prev_trans_lt") - @get:JvmName("prevTransLt") - val prevTransLt: ULong, // prev_trans_lt : uint64 - - @SerialName("now") - @get:JvmName("now") - val now: UInt, // now : uint32 - - @SerialName("outmsg_cnt") - @get:JvmName("outMsgCnt") - val outMsgCnt: Int, // outmsg_cnt : uint15 - - @SerialName("orig_status") - @get:JvmName("origStatus") - val origStatus: AccountStatus, // orig_status : AccountStatus - - @SerialName("end_status") - @get:JvmName("endStatus") - val endStatus: AccountStatus, // end_status : AccountStatus - - @get:JvmName("r1") - val r1: CellRef, // r1 : Aux - - @SerialName("total_fees") - @get:JvmName("totalFees") - val totalFees: CurrencyCollection, // total_fees : CurrencyCollection - - @SerialName("state_update") - @get:JvmName("stateUpdate") - val stateUpdate: CellRef, // state_update : ^HashUpdate - - @SerialName("description") - @get:JvmName("description") - val description: CellRef // description : ^TransactionDescr -) : TlbObject { + /** + * Account on which this transaction was produced. + */ + val account: ByteString, + + /** + * Logical time when the transaction was created. + */ + val lt: Long, + + /** + * The hash of the previous transaction on the same account. + */ + val prevTransactionHash: ByteString, + + /** + * The logical time of the previous transaction on the same account. + */ + val prevTransactionLt: Long, + + /** + * Unix timestamp in seconds when the transaction was created. + */ + val now: Long, + + /** + * The number of outgoing messages. + */ + val outMsgCount: Int, + + /** + * Account status before this transaction. + */ + val originalStatus: AccountStatus, + + /** + * Account status after this transaction. + */ + val endStatus: AccountStatus, + + /** + * Optional incoming message. + */ + val inMsg: CellRef>?, + + /** + * Outgoing messages. + */ + val outMsg: Dictionary>>, + + /** + * Total transaction fees (including extra fwd fees). + */ + val totalFees: CurrencyCollection, + + /** + * Account state hashes. + */ + val hashUpdate: CellRef, + + /** + * Detailed transaction info. + */ + val info: CellRef +) { init { - require(accountAddr.size == 256) { "expected accountAddr.size == 256, actual: ${accountAddr.size}" } - require(prevTransHash.size == 256) { "expected prevTransHash.size == 256, actual: ${accountAddr.size}" } + require(account.size == 32) { "Account size should be 32 bytes" } + require(prevTransactionHash.size == 32) { "Prev transaction hash should be 32 bytes" } } - public fun toCell(): Cell = CellBuilder.createCell { - storeTlb(Transaction, this@Transaction) + public fun loadInMessage(context: CellContext = CellContext.EMPTY): Message? { + return inMsg?.load(context) } - public fun hash(): BitString = toCell().hash() - - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { - type("transaction") { - field("account_addr", accountAddr) - field("lt", lt) - field("prev_trans_hash", prevTransHash) - field("prev_trans_lt", prevTransLt) - field("now", now) - field("outmsg_cnt", outMsgCnt) - field("orig_status", origStatus) - field("end_status", endStatus) - field(r1) - field("total_fees", totalFees) - field("state_update", stateUpdate) - field("description", description) - } + public fun loadInfo(context: CellContext = CellContext.EMPTY): TransactionInfo { + return info.load(context) } - override fun toString(): String = print().toString() - - public companion object : TlbCombinatorProvider by TransactionTlConstructor.asTlbCombinator() + public companion object : TlbCodec by TransactionCodec } - -public data class TransactionAux( - @SerialName("in_msg") - @get:JvmName("inMsg") - val inMsg: Maybe>>, - - @SerialName("out_msgs") - @get:JvmName("outMsgs") - val outMsgs: HashMapE>>, -) : TlbObject { - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { - return printer.type { - field("in_msg", inMsg) - field("out_msgs", outMsgs) +private object TransactionCodec : TlbCodec { + private val int15keyCodec = DictionaryKeyCodec.int(15) + private val refMessageCodec = CellRef.tlbCodec(Message.tlbCodec(RemainingTlbCodec)) + + override fun storeTlb(builder: CellBuilder, value: Transaction, context: CellContext) { + builder.storeUInt(0b0111, 4) + builder.storeByteString(value.account) + builder.storeLong(value.lt) + builder.storeByteString(value.prevTransactionHash) + builder.storeLong(value.prevTransactionLt) + builder.storeLong(value.now, 32) + builder.storeUInt(value.outMsgCount, 15) + AccountStatus.storeTlb(builder, value.originalStatus, context) + AccountStatus.storeTlb(builder, value.endStatus, context) + builder.storeRef(context) { + storeNullableRef(value.inMsg?.cell) + storeNullableRef(value.outMsg.cell) } + CurrencyCollection.storeTlb(builder, value.totalFees, context) + builder.storeRef(value.hashUpdate.cell) + builder.storeRef(value.info.cell) } - override fun toString(): String = print().toString() - - public companion object : TlbConstructorProvider by TransactionAuxTlbConstructor -} - -private object TransactionTlConstructor : TlbConstructor( - schema = "transaction\$0111 " + - " account_addr:bits256 " + - " lt:uint64 " + - " prev_trans_hash:bits256 " + - " prev_trans_lt:uint64 " + - " now:uint32 " + - " outmsg_cnt:uint15 " + - " orig_status:AccountStatus " + - " end_status:AccountStatus " + - " ^[ in_msg:(Maybe ^(Message Any)) out_msgs:(HashmapE 15 ^(Message Any)) ] " + - " total_fees:CurrencyCollection state_update:^(HASH_UPDATE Account) " + - " description:^TransactionDescr = Transaction;" -) { - override fun loadTlb(cellSlice: CellSlice): Transaction = cellSlice { - val accountAddr = loadBits(256) - val lt = loadUInt64() - val prevTransHash = loadBits(256) - val prevTransLt = loadUInt64() - val now = loadUInt32() - val outmsgCnt = loadUInt(15).toInt() - val origStatus = loadTlb(AccountStatus) - val endStatus = loadTlb(AccountStatus) - val r1 = loadRef(TransactionAux) - val totalFees = loadTlb(CurrencyCollection) - val stateUpdate = loadRef(HashUpdate) - val description = loadRef(TransactionDescr) - Transaction( - accountAddr, + override fun loadTlb(slice: CellSlice, context: CellContext): Transaction { + val tag = slice.loadUInt(4).toInt() + require(tag == 0b0111) { + "Invalid transaction tag: $tag" + } + val account = slice.loadByteString(32) + val lt = slice.loadLong() + val prevTransactionHash = slice.loadByteString(32) + val prevTransactionLt = slice.loadLong() + val now = slice.loadLong(32) + val outMsgCount = slice.loadUInt(15).toInt() + val originalStatus = AccountStatus.loadTlb(slice, context) + val endStatus = AccountStatus.loadTlb(slice, context) + val inMsg: CellRef>? + val outMsg: Dictionary>> + slice.loadRef(context) { + inMsg = loadNullableRef()?.let { ref -> CellRef(ref, Message.tlbCodec(RemainingTlbCodec)) } + outMsg = Dictionary(loadNullableRef(), int15keyCodec, refMessageCodec, context) + } + val totalFees = CurrencyCollection.loadTlb(slice, context) + val hashUpdate = CellRef(slice.loadRef(), HashUpdate) + val info = CellRef(slice.loadRef(), TransactionInfo) + return Transaction( + account, lt, - prevTransHash, - prevTransLt, + prevTransactionHash, + prevTransactionLt, now, - outmsgCnt, - origStatus, + outMsgCount, + originalStatus, endStatus, - r1, + inMsg, + outMsg, totalFees, - stateUpdate, - description + hashUpdate, + info ) } - - override fun storeTlb(cellBuilder: CellBuilder, value: Transaction) = cellBuilder { - storeBits(value.accountAddr) - storeUInt64(value.lt) - storeBits(value.prevTransHash) - storeUInt64(value.prevTransLt) - storeUInt32(value.now) - storeUInt(value.outMsgCnt, 15) - storeTlb(AccountStatus, value.origStatus) - storeTlb(AccountStatus, value.endStatus) - storeRef(TransactionAux, value.r1) - storeTlb(CurrencyCollection, value.totalFees) - storeRef(HashUpdate, value.stateUpdate) - storeRef(TransactionDescr, value.description) - } } -private object TransactionAuxTlbConstructor : TlbConstructor( - schema = "\$_ in_msg:(Maybe ^(Message Any)) out_msgs:(HashmapE 15 ^(Message Any)) " -) { - val maybeMessage = Maybe.tlbCodec(CellRef.tlbCodec(Message.Any)) - val outMsgs = HashMapE.tlbCodec(15, CellRef.tlbCodec(Message.Any)) - - override fun storeTlb(cellBuilder: CellBuilder, value: TransactionAux) = cellBuilder { - storeTlb(maybeMessage, value.inMsg) - storeTlb(outMsgs, value.outMsgs) - } - - override fun loadTlb(cellSlice: CellSlice): TransactionAux = cellSlice { - TransactionAux( - inMsg = loadTlb(maybeMessage), - outMsgs = loadTlb(outMsgs) - ) - } -} diff --git a/block-tlb/src/TransactionDescr.kt b/block-tlb/src/TransactionDescr.kt deleted file mode 100644 index 7aab9bfb..00000000 --- a/block-tlb/src/TransactionDescr.kt +++ /dev/null @@ -1,25 +0,0 @@ -@file:Suppress("OPT_IN_USAGE") - -package org.ton.block - -import kotlinx.serialization.json.JsonClassDiscriminator -import org.ton.tlb.TlbCombinator -import org.ton.tlb.TlbObject -import org.ton.tlb.providers.TlbCombinatorProvider - -@JsonClassDiscriminator("@type") - -public sealed interface TransactionDescr : TlbObject { - public companion object : TlbCombinatorProvider by TransactionDescrTlbCombinator -} - -private object TransactionDescrTlbCombinator : TlbCombinator( - TransactionDescr::class, - TransOrd::class to TransOrd.tlbConstructor(), - TransStorage::class to TransStorage.tlbConstructor(), - TransTickTock::class to TransTickTock.tlbConstructor(), - TransMergeInstall::class to TransMergeInstall.tlbConstructor(), - TransMergePrepare::class to TransMergePrepare.tlbConstructor(), - TransSplitInstall::class to TransSplitInstall.tlbConstructor(), - TransSplitPrepare::class to TransSplitPrepare.tlbConstructor(), -) diff --git a/block-tlb/src/TransactionInfo.kt b/block-tlb/src/TransactionInfo.kt new file mode 100644 index 00000000..c506797f --- /dev/null +++ b/block-tlb/src/TransactionInfo.kt @@ -0,0 +1,378 @@ +@file:Suppress("OPT_IN_USAGE") + +package org.ton.block + +import org.ton.cell.CellBuilder +import org.ton.cell.CellSlice +import org.ton.cell.storeRef +import org.ton.tlb.* + +/** + * Detailed transaction info. + * + * @see [Transaction] + */ +public sealed interface TransactionInfo { + /** + * Storage phase info. + */ + public val storagePhase: StoragePhase? + + /** + * Credit phase info. + */ + public val creditPhase: CreditPhase? + + /** + * Compute phase info. + */ + public val computePhase: ComputePhase? + + /** + * Action phase info. + */ + public val actionPhase: ActionPhase? + + /** + * Bounce phase info. + * + * Only present in [Ordinary] transaction and if the incoming [Message] had `bounce: true` and + * the compute phase failed. + */ + public val bouncePhase: BouncePhase? + + /** + * Ordinary transaction info. + */ + public data class Ordinary( + /** + * Whether the credit phase was executed first + * + * (usually set when incoming message has `bounce: false`). + */ + val isCreditFirst: Boolean, + + /** + * Storage phase info. + * + * Skipped if the account did not exist prior to execution. + */ + override val storagePhase: StoragePhase?, + + /** + * Credit phase info. + * + * Skipped if the incoming message is external. + */ + override val creditPhase: CreditPhase?, + + /** + * Compute phase info. + */ + override val computePhase: ComputePhase, + + /** + * Action phase info. + * + * Skipped if the transaction was aborted at the compute phase. + */ + override val actionPhase: ActionPhase?, + + /** + * Whether the transaction was reverted. + */ + val isAborted: Boolean, + + /** + * Bounce phase info. + * + * Only present if the incoming message had `bounce: true` and + * the compute phase failed. + */ + override val bouncePhase: BouncePhase?, + + /** + * Whether the account was destroyed during this transaction. + */ + val isDestroyed: Boolean + ) : TransactionInfo + + /** + * Tick-Tock transaction info + */ + public data class TickTock( + val isTock: Boolean, + + /** + * Storage phase info. + */ + override val storagePhase: StoragePhase, + + /** + * Compute phase info. + */ + override val computePhase: ComputePhase, + + /** + * Action phase info. + * + * Skipped if the transaction was aborted at the compute phase. + */ + override val actionPhase: ActionPhase?, + + /** + * Whether the transaction was reverted. + */ + val isAborted: Boolean, + + /** + * Whether the account was destroyed during this transaction. + */ + val isDestroyed: Boolean + ) : TransactionInfo { + val isTick: Boolean get() = !isTock + override val creditPhase: CreditPhase? get() = null + override val bouncePhase: BouncePhase? get() = null + } + + /** + * Storage transaction info + * + * **Currently, not implemented in TON Blockchain** + */ + @Deprecated("Not implemented in TON Blockchain") + public data class Storage( + /** + * Storage phase info. + */ + override val storagePhase: StoragePhase, + ) : TransactionInfo { + override val creditPhase: CreditPhase? get() = null + override val computePhase: ComputePhase? get() = null + override val actionPhase: ActionPhase? get() = null + override val bouncePhase: BouncePhase? get() = null + } + + /** + * Split prepare transaction info. + * + * **Currently, not implemented in TON Blockchain** + */ + @Deprecated("Not implemented in TON Blockchain") + public data class SplitPrepare( + val splitInfo: SplitMergeInfo, + override val storagePhase: StoragePhase?, + override val computePhase: ComputePhase, + override val actionPhase: ActionPhase?, + val isAborted: Boolean, + val isDestroyed: Boolean + ) : TransactionInfo { + override val creditPhase: CreditPhase? get() = null + override val bouncePhase: BouncePhase? get() = null + } + + /** + * Split install transaction info. + * + * **Currently, not implemented in TON Blockchain** + */ + @Deprecated("Not implemented in TON Blockchain") + public data class SplitInstall( + val splitInfo: SplitMergeInfo, + val prepareTransaction: CellRef, + val isInstalled: Boolean, + ) : TransactionInfo { + override val storagePhase: StoragePhase? get() = null + override val creditPhase: CreditPhase? get() = null + override val computePhase: ComputePhase? get() = null + override val actionPhase: ActionPhase? get() = null + override val bouncePhase: BouncePhase? get() = null + } + + /** + * Merge-prepare transaction info. + * + * **Currently, not implemented in TON Blockchain** + */ + @Deprecated("Not implemented in TON Blockchain") + public data class MergePrepare( + val splitInfo: SplitMergeInfo, + override val storagePhase: StoragePhase, + val isAborted: Boolean, + ) : TransactionInfo { + override val creditPhase: CreditPhase? get() = null + override val computePhase: ComputePhase? get() = null + override val actionPhase: ActionPhase? get() = null + override val bouncePhase: BouncePhase? get() = null + } + + /** + * Merge-install transaction info. + * + * **Currently, not implemented in TON Blockchain** + */ + @Deprecated("Not implemented in TON Blockchain") + public data class MergeInstall( + val splitInfo: SplitMergeInfo, + val prepareTransaction: CellRef, + override val storagePhase: StoragePhase?, + override val creditPhase: CreditPhase?, + override val computePhase: ComputePhase, + override val actionPhase: ActionPhase?, + val isAborted: Boolean, + val isDestroyed: Boolean + ) : TransactionInfo { + override val bouncePhase: BouncePhase? get() = null + } + + public companion object : TlbCodec by TransactionInfoCodec +} + +private object TransactionInfoCodec : TlbCodec { + private val maybeStoragePhase = StoragePhase.asNullable() + private val maybeCreditPhase = CreditPhase.asNullable() + private val maybeRefActionPhase = CellRef.tlbCodec(ActionPhase).asNullable() + private val maybeBouncePhase = BouncePhase.asNullable() + + @Suppress("DEPRECATION") + override fun storeTlb(builder: CellBuilder, value: TransactionInfo) { + when (value) { + is TransactionInfo.Ordinary -> { // trans_ord$0000 + builder.storeUInt(0b0000, 4) + builder.storeBoolean(value.isCreditFirst) + builder.storeTlb(maybeStoragePhase, value.storagePhase) + builder.storeTlb(maybeCreditPhase, value.creditPhase) + builder.storeTlb(ComputePhase, value.computePhase) + val actionPhase = value.actionPhase + if (actionPhase != null) { + builder.storeBoolean(true) + builder.storeRef { + storeTlb(ActionPhase, actionPhase) + } + } else { + builder.storeBoolean(false) + } + builder.storeBoolean(value.isAborted) + builder.storeTlb(maybeBouncePhase, value.bouncePhase) + builder.storeBoolean(value.isDestroyed) + } + + is TransactionInfo.TickTock -> TODO() + is TransactionInfo.MergeInstall -> TODO() + is TransactionInfo.MergePrepare -> TODO() + is TransactionInfo.SplitInstall -> TODO() + is TransactionInfo.SplitPrepare -> TODO() + is TransactionInfo.Storage -> TODO() + } + } + + @Suppress("DEPRECATION") + override fun loadTlb(slice: CellSlice): TransactionInfo { + when (val tag = getTag(slice)) { + 0b0000 -> { // trans_ord$0000 + slice.bitsPosition += 4 + val isCreditFirst = slice.loadBoolean() + val storagePhase = slice.loadTlb(maybeStoragePhase) + val creditPhase = slice.loadTlb(maybeCreditPhase) + val computePhase = slice.loadTlb(ComputePhase) + val actionPhase = slice.loadTlb(maybeRefActionPhase)?.load() + val isAborted = slice.loadBoolean() + val bouncePhase = slice.loadTlb(maybeBouncePhase) + println("bounce: $bouncePhase") + val isDestroyed = slice.loadBoolean() + return TransactionInfo.Ordinary( + isCreditFirst, + storagePhase, + creditPhase, + computePhase, + actionPhase, + isAborted, + bouncePhase, + isDestroyed + ) + } + + 0b0001 -> { // trans_storage$0001 + slice.bitsPosition += 4 + val storagePhase = slice.loadTlb(StoragePhase) + return TransactionInfo.Storage(storagePhase) + } + + 0b0010 -> { // trans_tick_tock$001 + slice.bitsPosition += 3 + val isTock = slice.loadBoolean() + val storagePhase = slice.loadTlb(StoragePhase) + val computePhase = slice.loadTlb(ComputePhase) + val actionPhase = slice.loadTlb(maybeRefActionPhase)?.load() + val isAborted = slice.loadBoolean() + val isDestroyed = slice.loadBoolean() + return TransactionInfo.TickTock(isTock, storagePhase, computePhase, actionPhase, isAborted, isDestroyed) + } + + 0b0100 -> { // trans_split_prepare$0100 + slice.bitsPosition += 4 + val splitInfo = slice.loadTlb(SplitMergeInfo) + val storagePhase = slice.loadTlb(maybeStoragePhase) + val computePhase = slice.loadTlb(ComputePhase) + val actionPhase = slice.loadTlb(maybeRefActionPhase)?.load() + val isAborted = slice.loadBoolean() + val isDestroyed = slice.loadBoolean() + return TransactionInfo.SplitPrepare( + splitInfo, + storagePhase, + computePhase, + actionPhase, + isAborted, + isDestroyed + ) + } + + 0b0101 -> { // trans_split_install$0101 + slice.bitsPosition += 4 + val splitInfo = slice.loadTlb(SplitMergeInfo) + val prepareTransaction = CellRef(slice.loadRef(), Transaction) + val isInstalled = slice.loadBoolean() + return TransactionInfo.SplitInstall(splitInfo, prepareTransaction, isInstalled) + } + + 0b0110 -> { // trans_merge_prepare$0110 + slice.bitsPosition += 4 + val splitInfo = slice.loadTlb(SplitMergeInfo) + val storagePhase = slice.loadTlb(StoragePhase) + val isAborted = slice.loadBoolean() + return TransactionInfo.MergePrepare(splitInfo, storagePhase, isAborted) + } + + 0b0111 -> { // trans_merge_install$0111 + slice.bitsPosition += 4 + val splitInfo = slice.loadTlb(SplitMergeInfo) + val prepareTransaction = CellRef(slice.loadRef(), Transaction) + val storagePhase = slice.loadTlb(maybeStoragePhase) + val creditPhase = slice.loadTlb(maybeCreditPhase) + val computePhase = slice.loadTlb(ComputePhase) + val actionPhase = slice.loadTlb(maybeRefActionPhase)?.load() + val isAborted = slice.loadBoolean() + val isDestroyed = slice.loadBoolean() + return TransactionInfo.MergeInstall( + splitInfo, + prepareTransaction, + storagePhase, + creditPhase, + computePhase, + actionPhase, + isAborted, + isDestroyed + ) + } + + else -> throw IllegalArgumentException("Invalid transaction info tag: $tag") + } + } + + private fun getTag(slice: CellSlice): Int = slice.bitSelect(4, 0xF7) + + private fun CellSlice.bitSelect(bitCount: Int, mask: Int): Int { + val n = preloadUInt(bitCount).toInt() + return (mask and ((0b10 shl n) - 1)).countOneBits() - 1 + } +} \ No newline at end of file diff --git a/block-tlb/src/message/MessageLayout.kt b/block-tlb/src/message/MessageLayout.kt index e889754d..10ab9550 100644 --- a/block-tlb/src/message/MessageLayout.kt +++ b/block-tlb/src/message/MessageLayout.kt @@ -7,6 +7,7 @@ import org.ton.block.Either.Left import org.ton.block.Either.Right import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.CellRef import org.ton.tlb.TlbCodec import org.ton.tlb.TlbStorer @@ -29,14 +30,14 @@ public data class MessageLayout( public fun eitherInit(init: StateInit?): Either>? { if (init == null) return null return if (initToCell) { - Right(CellRef(init)) + Right(CellRef(init, StateInit)) } else { Left(init) } } - public fun eitherBody(body: T): Either> { - return if (bodyToCell) Right(CellRef(body)) else Left(body) + public fun eitherBody(body: T, bodyCodec: TlbCodec): Either> { + return if (bodyToCell) Right(CellRef(body, bodyCodec)) else Left(body) } public companion object { @@ -58,15 +59,17 @@ public data class MessageLayout( bodyStorer: TlbStorer ): MessageLayout { val bodyCodec = object : TlbCodec { - override fun storeTlb(cellBuilder: CellBuilder, value: T) = bodyStorer.storeTlb(cellBuilder, value) - override fun loadTlb(cellSlice: CellSlice): T = throw UnsupportedOperationException() + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext) = + bodyStorer.storeTlb(builder, value, context) + + override fun loadTlb(slice: CellSlice, context: CellContext): T = throw UnsupportedOperationException() } val messageCodec = MessageRelaxed.tlbCodec(bodyCodec) val builder = CellBuilder() for (layout in LAYOUTS) { builder.reset() val result = runCatching { - builder.storeTlb(messageCodec, MessageRelaxed(info, init, body, layout)) + builder.storeTlb(messageCodec, MessageRelaxed(info, init, body, bodyCodec, layout)) check(builder.remainingBits >= 0 && builder.refs.size <= 4) } if (result.isSuccess) { @@ -83,15 +86,17 @@ public data class MessageLayout( bodyStorer: TlbStorer ): MessageLayout { val bodyCodec = object : TlbCodec { - override fun storeTlb(cellBuilder: CellBuilder, value: T) = bodyStorer.storeTlb(cellBuilder, value) - override fun loadTlb(cellSlice: CellSlice): T = throw UnsupportedOperationException() + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext) = + bodyStorer.storeTlb(builder, value, context) + + override fun loadTlb(slice: CellSlice): T = throw UnsupportedOperationException() } val messageCodec = Message.tlbCodec(bodyCodec) val builder = CellBuilder() for (layout in LAYOUTS) { builder.reset() val result = runCatching { - builder.storeTlb(messageCodec, Message(info, init, body, layout)) + builder.storeTlb(messageCodec, Message(info, init, body, bodyCodec, layout)) check(builder.remainingBits >= 0 && builder.refs.size <= 4) } if (result.isSuccess) { diff --git a/build.gradle.kts b/build.gradle.kts index 09e1e2cf..7a81ee91 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,8 +9,8 @@ plugins { } allprojects { - group = "org.ton.kotlin" - version = "0.4.0" + group = "org.ton" + version = "0.4.1" repositories { mavenCentral() diff --git a/contract/src/CellString.kt b/contract/src/CellString.kt index 526ce3d8..328db33d 100644 --- a/contract/src/CellString.kt +++ b/contract/src/CellString.kt @@ -6,6 +6,7 @@ import org.ton.cell.Cell import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.storeRef +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbConstructor import kotlin.math.min @@ -23,7 +24,7 @@ public object CellStringTlbConstructor : TlbConstructor( return ByteString(*result.toByteArray()) } - override fun storeTlb(cellBuilder: CellBuilder, value: ByteString) { + override fun storeTlb(cellBuilder: CellBuilder, value: ByteString, context: CellContext) { require(value.size <= MAX_BYTES) { "String is too long" } @@ -40,8 +41,8 @@ public object CellStringTlbConstructor : TlbConstructor( cellBuilder.storeBytes(value.toByteArray()) } else { cellBuilder.storeBytes(value.substring(0, head / 8).toByteArray()) - cellBuilder.storeRef { - storeTlb(this, value.substring(head / 8, value.size)) + cellBuilder.storeRef(context) { + storeTlb(this, value.substring(head / 8, value.size), context) } } } diff --git a/contract/src/wallet/WalletMessage.kt b/contract/src/wallet/WalletMessage.kt index 7b36c51c..5dcb2701 100644 --- a/contract/src/wallet/WalletMessage.kt +++ b/contract/src/wallet/WalletMessage.kt @@ -17,10 +17,6 @@ public interface WalletMessage { public fun of(mode: Int, msg: CellRef>): WalletMessage = WalletMessageImpl(mode, msg) - @JvmStatic - public fun of(mode: Int, msg: MessageRelaxed): WalletMessage = - of(mode, CellRef(msg)) - @JvmStatic public fun tlbCodec(x: TlbCodec): TlbCodec> = WalletMessageTlbConstructor(x) @@ -30,9 +26,6 @@ public interface WalletMessage { public inline fun WalletMessage(mode: Int, msg: CellRef>): WalletMessage = WalletMessage.of(mode, msg) -public inline fun WalletMessage(mode: Int, msg: MessageRelaxed): WalletMessage = - WalletMessage.of(mode, msg) - private data class WalletMessageImpl( override val mode: Int, override val msg: CellRef> diff --git a/contract/src/wallet/WalletTransfer.kt b/contract/src/wallet/WalletTransfer.kt index 8e5b67e6..de29e49a 100644 --- a/contract/src/wallet/WalletTransfer.kt +++ b/contract/src/wallet/WalletTransfer.kt @@ -50,6 +50,7 @@ public data class WalletTransfer internal constructor( info = msgInfo, init = init, body = body, + bodyCodec = AnyTlbConstructor, layout = layout ?: MessageLayout.compute(msgInfo, init, body, AnyTlbConstructor) ) } @@ -89,11 +90,11 @@ public sealed interface MessageData { public data class Text( public val text: CellRef, - override val layout: MessageLayout? + override val layout: MessageLayout? = null ) : MessageData { - public constructor(text: MessageText) : this(CellRef(text, MessageText), null) + public constructor(text: MessageText) : this(CellRef(text, MessageText)) - override val body: Cell get() = text.toCell(MessageText) + override val body: Cell get() = text.cell override val stateInit: CellRef? get() = null } diff --git a/contract/src/wallet/WalletV3Contract.kt b/contract/src/wallet/WalletV3Contract.kt index 58ce6e88..d495609f 100644 --- a/contract/src/wallet/WalletV3Contract.kt +++ b/contract/src/wallet/WalletV3Contract.kt @@ -15,7 +15,6 @@ import org.ton.lite.client.LiteClient import org.ton.tlb.CellRef import org.ton.tlb.TlbConstructor import org.ton.tlb.constructor.AnyTlbConstructor -import org.ton.tlb.storeRef import org.ton.tlb.storeTlb public class WalletV3R2Contract( @@ -24,7 +23,8 @@ public class WalletV3R2Contract( ) : WalletContract { public suspend fun getWalletData(): WalletV3R2Data { val data = - ((liteClient.getAccountState(address).account.value as? AccountInfo)?.storage?.state as? AccountActive)?.value?.data?.value?.value?.beginParse() + ((liteClient.getAccountState(address).account.load())?.state as? AccountActive)?.value?.data?.value?.load() + ?.beginParse() require(data != null) { throw AccountNotInitializedException(address) } return WalletV3R2Data.loadTlb(data) } @@ -48,7 +48,7 @@ public class WalletV3R2Contract( walletId, privateKey.publicKey() ) - ).value else null + ).load() else null val message = transferMessage( address = address, stateInit = stateInit, @@ -101,8 +101,10 @@ public class WalletV3R2Contract( val dataCell = buildCell { storeTlb(WalletV3R2Data, data) } + val stateCell = StateInit(CODE, dataCell).toCell() + return CellRef( - StateInit(CODE, dataCell), + stateCell, StateInit ) } @@ -122,7 +124,12 @@ public class WalletV3R2Contract( importFee = Coins() ) val maybeStateInit = - Maybe.of(stateInit?.let { Either.of>(null, CellRef(it)) }) + Maybe.of(stateInit?.let { + Either.of>( + null, + CellRef(value = it, StateInit) + ) + }) val transferBody = createTransferMessageBody( privateKey, walletId, @@ -130,7 +137,7 @@ public class WalletV3R2Contract( seqno, *transfers ) - val body = Either.of>(null, CellRef(transferBody)) + val body = Either.of>(null, CellRef(value = transferBody, AnyTlbConstructor)) return Message( info = info, init = maybeStateInit, @@ -154,10 +161,10 @@ public class WalletV3R2Contract( if (gift.sendMode > -1) { sendMode = gift.sendMode } - val intMsg = CellRef(gift.toMessageRelaxed()) + val intMsg = CellRef(gift.toMessageRelaxed(), MessageRelaxed.tlbCodec(AnyTlbConstructor)) storeUInt(sendMode, 8) - storeRef(MessageRelaxed.tlbCodec(AnyTlbConstructor), intMsg) + storeRef(intMsg.cell) } } val signature = BitString(privateKey.sign(unsignedBody.hash().toByteArray())) diff --git a/contract/src/wallet/WalletV4R2Contract.kt b/contract/src/wallet/WalletV4R2Contract.kt index 99d5fedf..8ba35273 100644 --- a/contract/src/wallet/WalletV4R2Contract.kt +++ b/contract/src/wallet/WalletV4R2Contract.kt @@ -14,9 +14,11 @@ import org.ton.contract.exception.AccountNotInitializedException import org.ton.contract.wallet.WalletContract.Companion.DEFAULT_WALLET_ID import org.ton.hashmap.HashMapE import org.ton.lite.client.LiteClient -import org.ton.tlb.* +import org.ton.tlb.CellRef import org.ton.tlb.TlbConstructor import org.ton.tlb.constructor.AnyTlbConstructor +import org.ton.tlb.loadTlb +import org.ton.tlb.storeTlb import kotlin.io.encoding.Base64 public class WalletV4R2Contract( @@ -25,7 +27,7 @@ public class WalletV4R2Contract( ) : WalletContract { public suspend fun getWalletData(): Data { val data = - ((liteClient.getAccountState(address).account.value as? AccountInfo)?.storage?.state as? AccountActive)?.value?.data?.value?.value?.beginParse() + ((liteClient.getAccountState(address).account.value as? Account)?.storage?.state as? AccountActive)?.value?.data?.value?.value?.beginParse() require(data != null) { throw AccountNotInitializedException(address) } return Data.loadTlb(data) } @@ -144,7 +146,12 @@ public class WalletV4R2Contract( importFee = Coins() ) val maybeStateInit = - Maybe.of(stateInit?.let { Either.of>(null, CellRef(it)) }) + Maybe.of(stateInit?.let { + Either.of>( + null, + CellRef(value = it, StateInit) + ) + }) val transferBody = createTransferMessageBody( privateKey, walletId, @@ -152,7 +159,7 @@ public class WalletV4R2Contract( seqno, *transfers ) - val body = Either.of>(null, CellRef(transferBody)) + val body = Either.of>(null, CellRef(value = transferBody, AnyTlbConstructor)) return Message( info = info, init = maybeStateInit, @@ -177,10 +184,10 @@ public class WalletV4R2Contract( if (gift.sendMode > -1) { sendMode = gift.sendMode } - val intMsg = CellRef(gift.toMessageRelaxed()) + val intMsg = CellRef(gift.toMessageRelaxed(), MessageRelaxed.tlbCodec(AnyTlbConstructor)) storeUInt(sendMode, 8) - storeRef(MessageRelaxed.tlbCodec(AnyTlbConstructor), intMsg) + storeRef(intMsg.cell) } } val signature = BitString(privateKey.sign(unsignedBody.hash().toByteArray())) diff --git a/contract/test/wallet/WalletV4Example.kt b/contract/test/wallet/WalletV4Example.kt index 850be77b..60947038 100644 --- a/contract/test/wallet/WalletV4Example.kt +++ b/contract/test/wallet/WalletV4Example.kt @@ -4,7 +4,7 @@ import io.github.andreypfau.kotlinx.crypto.sha2.sha256 import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.ton.api.pk.PrivateKeyEd25519 -import org.ton.block.AccountInfo +import org.ton.block.Account import org.ton.block.AddrStd import org.ton.block.Coins import kotlin.test.Test @@ -24,7 +24,7 @@ class WalletV4Example { println("Wallet Address: $testnetNonBounceAddr") var accountState = liteClient.getAccountState(contract.address) - val account = accountState.account.value as? AccountInfo + val account = accountState.account.value as? Account if (account == null) { println("Account $testnetNonBounceAddr not initialized") return@runBlocking diff --git a/examples/src/ExtraCurrencyExample.kt b/examples/src/ExtraCurrencyExample.kt index fd685380..5b742cc4 100644 --- a/examples/src/ExtraCurrencyExample.kt +++ b/examples/src/ExtraCurrencyExample.kt @@ -4,36 +4,41 @@ import org.ton.api.pk.PrivateKeyEd25519 import org.ton.block.AddrStd import org.ton.block.Coins import org.ton.block.CurrencyCollection -import org.ton.block.balance import org.ton.contract.wallet.WalletTransfer import org.ton.kotlin.currency.VarUInt248 import org.ton.kotlin.examples.contract.WalletV1R3Contract import org.ton.kotlin.examples.faucet.TestnetFaucet import org.ton.kotlin.examples.provider.LiteClientProvider import org.ton.kotlin.examples.provider.liteClientTestnet +import kotlin.random.Random -val swapAddress = AddrStd("kQC_rkxBuZDwS81yvMSLzeXBNLCGFNofm0avwlMfNXCwoOgr") +private val swapAddress = AddrStd("kQC_rkxBuZDwS81yvMSLzeXBNLCGFNofm0avwlMfNXCwoOgr") +@OptIn(ExperimentalStdlibApi::class) suspend fun main() { val provider = LiteClientProvider(liteClientTestnet()) - val key = PrivateKeyEd25519() + val seed = Random.nextBytes(32) + println("seed=${seed.toHexString()}") + val key = PrivateKeyEd25519(seed) val wallet = WalletV1R3Contract(0, key.publicKey(), provider) - var state = wallet.getState() + println("raw address: ${wallet.address.toString(userFriendly = false)}") + println("pub-key : ${wallet.publicKey.key}") + val faucet = TestnetFaucet(provider) - faucet.topUpContract(wallet.address, 4000000000) - state = requireNotNull(wallet.waitForStateChange(state)) + faucet.topUpContract(wallet.address, 55000000000) + var state = requireNotNull(wallet.waitForAccountChange(wallet.getState()?.loadAccount())) println("new balance: ${state.balance}") println("requesting from $swapAddress ECHIDNA...") wallet.transfer(key, wallet.getSeqno(state), listOf(WalletTransfer { destination = swapAddress - coins = Coins(3700000000) + coins = Coins(5000000000) })) while (true) { - state = requireNotNull(wallet.waitForStateChange(state)) + state = requireNotNull(wallet.waitForAccountChange(state)) println("new balance: ${state.balance}") if (!state.balance.other.isEmpty()) { break @@ -48,7 +53,6 @@ suspend fun main() { currencyCollection = CurrencyCollection(Coins(100000000), mapOf(100 to VarUInt248(600000000))) })) - state = requireNotNull(wallet.waitForStateChange(state)) + state = requireNotNull(wallet.waitForAccountChange(state)) println("new balance: ${state.balance}") } - diff --git a/examples/src/ExtraCurrencyMonitor.kt b/examples/src/ExtraCurrencyMonitor.kt new file mode 100644 index 00000000..776dbc4d --- /dev/null +++ b/examples/src/ExtraCurrencyMonitor.kt @@ -0,0 +1,91 @@ +package org.ton.kotlin.examples + +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.io.bytestring.ByteString +import org.ton.api.pk.PrivateKeyEd25519 +import org.ton.block.* +import org.ton.contract.wallet.MessageData +import org.ton.contract.wallet.WalletTransfer +import org.ton.kotlin.currency.VarUInt248 +import org.ton.kotlin.examples.contract.WalletV1R3Contract +import org.ton.kotlin.examples.faucet.TestnetFaucet +import org.ton.kotlin.examples.provider.LiteClientProvider +import org.ton.kotlin.examples.provider.liteClientTestnet + +private val provider = LiteClientProvider(liteClientTestnet()) +private val swapAddress = AddrStd("kQC_rkxBuZDwS81yvMSLzeXBNLCGFNofm0avwlMfNXCwoOgr") +private val faucet = TestnetFaucet(provider) + +suspend fun main(): Unit = coroutineScope { + val key1 = PrivateKeyEd25519() + val key2 = PrivateKeyEd25519() + var (wallet1, state1) = deployWallet(key1) + val (wallet2, _) = deployWallet(key2) + println("requesting from $swapAddress ECHIDNA...") + wallet1.transfer( + key1, + wallet1.getSeqno(state1), + WalletTransfer { + destination = swapAddress + coins = Coins(3700000000) + } + ) + println("sent transfer message, waiting for extra currency...") + var tx = wallet1.waitForExtraCurrency(100) + println("wallet1 ${wallet1.address.toString(userFriendly = false)} received ${tx.inMsg?.load()?.info.value}") + + println("sending EC wallet1->wallet2...") + state1 = requireNotNull(wallet1.getState()?.loadAccount()) + wallet1.transfer(key1, wallet1.getSeqno(state1), WalletTransfer { + destination = wallet2.address + currencyCollection = CurrencyCollection(mapOf(100 to VarUInt248(300000000))) + messageData = MessageData.text("send EC wallet1->wallet2") + }) + println("transfer sent, waiting for extra currency on wallet2...") + tx = wallet2.waitForExtraCurrency(100) + println("wallet2 received ${tx.inMsg?.load()?.info.value}") +} + + +suspend fun WalletV1R3Contract.waitForExtraCurrency( + id: Int, + fromTxLt: Long? = null, + fromTxHash: ByteString? = null +): Transaction { + while (true) { + for (cellTx in provider.getTransactions(address, fromTxLt, fromTxHash)) { + val tx = cellTx.load() + (tx.inMsg?.load()?.info as? IntMsgInfo)?.value?.get(id) ?: continue + val info = tx.loadInfo() + if (info.bouncePhase !is BouncePhase.Executed) { + return tx + } + throw IllegalStateException( + "Extra currency transaction ${tx.lt}:${cellTx.hash().toHexString()} bounced: ${info.bouncePhase}" + ) + } + delay(2000) + } +} + +suspend fun deployWallet(key: PrivateKeyEd25519): Pair { + val wallet = WalletV1R3Contract(0, key.publicKey(), provider) + println("Deploying wallet...") + println(" raw address: ${wallet.address.toString(userFriendly = false)}") + println("nonBounceableAddress: ${wallet.address.toString(userFriendly = true, bounceable = false)}") + println(" bounceableAddress: ${wallet.address.toString(userFriendly = true, bounceable = true)}") + var account = wallet.getState()?.loadAccount() + println("top up from faucet...") + val faucetRequest = Coins.of(4) + faucet.topUpContract(wallet.address, faucetRequest) + account = wallet.waitForAccountChange(account) { it.balance.coins == faucetRequest } + println("topped up: ${account.balance}") + println("sending state-init to self...") + wallet.transfer(key, wallet.getSeqno(account), WalletTransfer { + destination = wallet.address + }) + account = requireNotNull(wallet.waitForAccountChange(account) { it?.isActive == true }) + println("wallet ${wallet.address.toString(userFriendly = false)} deployed!") + return wallet to account +} \ No newline at end of file diff --git a/examples/src/contract/BaseWalletContract.kt b/examples/src/contract/BaseWalletContract.kt index 687d0820..e2fe01f8 100644 --- a/examples/src/contract/BaseWalletContract.kt +++ b/examples/src/contract/BaseWalletContract.kt @@ -3,8 +3,9 @@ package contract import kotlinx.coroutines.delay import org.ton.api.pub.PublicKeyEd25519 import org.ton.api.tonnode.TonNodeBlockIdExt -import org.ton.block.AccountInfo +import org.ton.block.Account import org.ton.block.AddrStd +import org.ton.block.ShardAccount import org.ton.block.StateInit import org.ton.cell.Cell import org.ton.kotlin.examples.provider.Provider @@ -33,15 +34,25 @@ abstract class BaseWalletContract( abstract fun initCodeCell(): Cell - suspend fun getState(blockId: TonNodeBlockIdExt? = null): AccountInfo? = provider.getAccountState(address, blockId) + suspend fun getState(blockId: TonNodeBlockIdExt? = null): ShardAccount? = provider.getAccount(address, blockId) - abstract suspend fun getSeqno(accountInfo: AccountInfo): Int + abstract fun getSeqno(accountInfo: Account?): Int - suspend fun waitForStateChange(currentState: AccountInfo?): AccountInfo? { + suspend fun waitForShardAccountChange(shardAccount: ShardAccount?): ShardAccount? { while (true) { - val state = provider.getAccountState(address) - if (state != currentState) { - return state + val newShardAccount = provider.getAccount(address) + if (newShardAccount != shardAccount) { + return newShardAccount + } + delay(2000) + } + } + + suspend fun waitForAccountChange(account: Account?, condition: (Account?) -> Boolean = { true }): Account? { + while (true) { + val newAccount = provider.getAccount(address)?.loadAccount() + if (newAccount != account && condition(newAccount)) { + return newAccount } delay(2000) } diff --git a/examples/src/contract/WalletV1R3Contract.kt b/examples/src/contract/WalletV1R3Contract.kt index 2c0a5646..06b034ef 100644 --- a/examples/src/contract/WalletV1R3Contract.kt +++ b/examples/src/contract/WalletV1R3Contract.kt @@ -6,18 +6,14 @@ import org.ton.api.pub.PublicKeyEd25519 import org.ton.bitstring.BitString import org.ton.block.* import org.ton.boc.BagOfCells -import org.ton.cell.Cell -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.storeUInt +import org.ton.cell.* import org.ton.contract.wallet.WalletTransfer +import org.ton.kotlin.cell.CellContext import org.ton.kotlin.examples.provider.Provider import org.ton.kotlin.message.MessageLayout -import org.ton.tlb.CellRef import org.ton.tlb.TlbCodec import org.ton.tlb.TlbStorer import org.ton.tlb.constructor.AnyTlbConstructor -import org.ton.tlb.storeRef open class WalletV1R3Contract( workchain: Int = DEFAULT_WORKCHAIN, @@ -35,14 +31,27 @@ open class WalletV1R3Contract( override fun initCodeCell(): Cell = CODE - override suspend fun getSeqno(accountInfo: AccountInfo): Int { - val state = (accountInfo.storage.state as AccountActive).value - val data = requireNotNull(state.data.value).value.beginParse() + override fun getSeqno(accountInfo: Account?): Int { + val accountState = accountInfo?.state ?: return 0 + val stateInit = when (accountState) { + is AccountActive -> accountState.value + is AccountFrozen -> throw IllegalStateException("Account ${accountInfo.address} frozen") + AccountUninit -> return 0 + } + val data = requireNotNull(stateInit.data.value).load().beginParse() return data.loadUInt(32).toInt() } + suspend fun transfer(privateKey: PrivateKey, seqno: Int, vararg transfers: WalletTransfer) = + transfer(privateKey, seqno, transfers.toList()) + suspend fun transfer(privateKey: PrivateKey, seqno: Int, transfers: List) { - val message = WalletV1R3Message(seqno, transfers).sign(privateKey).toMessage(address) + val message = WalletV1R3Message(seqno, transfers) + .sign(privateKey) + .toMessage( + address = address, + init = if (seqno == 0) stateInit else null + ) provider.sendMessage(SignedWalletV1R3Message, message) } @@ -57,24 +66,29 @@ open class WalletV1R3Contract( } override fun sign(privateKey: PrivateKey): SignedWalletV1R3Message { - val cell = CellBuilder().apply { - storeTlb(this, this@WalletV1R3Message) - }.build() + val cell = buildCell { + storeTlb(this, this@WalletV1R3Message, CellContext.EMPTY) + } val signature = BitString(privateKey.sign(cell.hash().toByteArray())) return SignedWalletV1R3Message(signature, seqno, transfers) } companion object : TlbStorer { + val messageRelaxed = MessageRelaxed.Companion.tlbCodec(AnyTlbConstructor) + override fun storeTlb( builder: CellBuilder, - value: WalletV1R3Message + value: WalletV1R3Message, + context: CellContext ) { builder.storeUInt(value.seqno.toUInt()) for (gift in value.transfers) { - val intMsg = CellRef(gift.toMessageRelaxed()) + val intMsg = gift.toMessageRelaxed() builder.storeUInt(gift.sendMode, 8) - builder.storeRef(MessageRelaxed.Companion.tlbCodec(AnyTlbConstructor), intMsg) + builder.storeRef(context) { + messageRelaxed.storeTlb(this, intMsg) + } } } } @@ -100,10 +114,11 @@ open class WalletV1R3Contract( layout: MessageLayout? = null ): Message { val info = ExtInMsgInfo(source, address) - return Message( + return Message( info = info, init = init, body = this, + bodyCodec = Companion, layout = layout ?: MessageLayout.compute( info = info, init = init, @@ -116,13 +131,14 @@ open class WalletV1R3Contract( companion object : TlbCodec { override fun storeTlb( builder: CellBuilder, - value: SignedWalletV1R3Message + value: SignedWalletV1R3Message, + context: CellContext ) { builder.storeBitString(value.signature) - WalletV1R3Message.storeTlb(builder, value) + WalletV1R3Message.storeTlb(builder, value, context) } - override fun loadTlb(cellSlice: CellSlice): SignedWalletV1R3Message { + override fun loadTlb(cellSlice: CellSlice, context: CellContext): SignedWalletV1R3Message { TODO("Not yet implemented") } } diff --git a/examples/src/faucet/TestnetFaucet.kt b/examples/src/faucet/TestnetFaucet.kt index c9b8faf7..a43de885 100644 --- a/examples/src/faucet/TestnetFaucet.kt +++ b/examples/src/faucet/TestnetFaucet.kt @@ -4,10 +4,11 @@ import org.ton.api.pk.PrivateKeyEd25519 import org.ton.bigint.BigInt import org.ton.block.Coins import org.ton.block.MsgAddressInt +import org.ton.block.balance +import org.ton.contract.wallet.MessageData import org.ton.contract.wallet.WalletTransfer import org.ton.kotlin.examples.contract.WalletV1R3Contract import org.ton.kotlin.examples.provider.Provider -import org.ton.kotlin.message.MessageLayout class TestnetFaucet( val provider: Provider @@ -24,24 +25,17 @@ class TestnetFaucet( topUpContract(destination, Coins(amount)) suspend fun topUpContract(destination: MsgAddressInt, amount: Coins) { -// val account = provider.getAccountState(wallet.address) -// val seqno = account?.let { wallet.getSeqno(it) } ?: 0 - val seqno = 0 -// println("faucet balance: ${account.balance}") - println(destination.toAddrStd().toString(userFriendly = true)) - val message = WalletV1R3Contract.WalletV1R3Message(seqno, WalletTransfer { + var account = provider.getAccount(wallet.address)?.loadAccount() + val seqno = account?.let { wallet.getSeqno(it) } ?: 0 + println("faucet balance: ${account?.balance}") + wallet.transfer(SECRET, seqno, WalletTransfer { this.destination = destination this.coins = amount this.bounceable = false -// this.messageData = MessageData.text("github.com/ton-community/ton-kotlin") - }).sign(SECRET).toMessage(wallet.address, layout = MessageLayout.PLAIN) - provider.sendMessage(WalletV1R3Contract.SignedWalletV1R3Message, message) + this.messageData = MessageData.text("github.com/ton-community/ton-kotlin") + }) + println("Waiting for faucet new balance...") + account = wallet.waitForAccountChange(account) + println("New faucet balance: ${account.balance}") } } -// faucet: -// 1011010100101010000101101011101000110111001101010101000000011101111100011001100110010111010101010000111001111110110101001100010000010111010101001110111001010000000111011110110110001010100001000001000010001000110011100100001001111000101101100110110111100100 100100 000000 - -// norm -// 1000100000000001011010100101010000101101011101000110111001101010101000000011101111100011001100110010111010101010000111001111110110101001100010000010111010101001110111001010000000111011110110110001010100001000001000010001000110011100100001001111000101101100110110111100100 000000111011100001111111111010011011110101011010110100100011010111101110000101001100110100011111001001110000010000110010111100100010001111001111010001101110101010011111000010101100110110101011111111011010010100011111011010100001000001101110000011101110110011100001010001111110111010101010010000110101110011100001100100110001100001100111000111110101101001001001000010000111100000101111001111001100000100100011010111100110011101000101011110101000110001100101010100110000110111001001010001000111010110100000010010000001010000000000000000000000000000000000000011 - -// 1000100000000001011010100101010000101101011101000110111001101010101000000011101111100011001100110010111010101010000111001111110110101001100010000010111010101001110111001010000000111011110110110001010100001000001000010001000110011100100001001111000101101100110110111100100 000000001111010010110000011101100010011010100111101100100010000011100100110000101011011010011100110010010100101100100001001101000111111110111000101110100000110110001101101110101110011100101001110111000010110101011101000001100111000111001100010001011110011011011010010010100111110011111110111010010101110001010110011110010000100000001011000110100010010101010101001001000111101100011000110101111011100111110001010000010010110001001001110010110110111010000001000011110011101111010101110100110111100001000101001101000001110000000000000000000000000000000000000011 \ No newline at end of file diff --git a/examples/src/provider/Provider.kt b/examples/src/provider/Provider.kt index b652f49f..2e5a6857 100644 --- a/examples/src/provider/Provider.kt +++ b/examples/src/provider/Provider.kt @@ -1,52 +1,94 @@ package org.ton.kotlin.examples.provider -import io.ktor.util.* +import kotlinx.io.bytestring.ByteString import org.ton.api.tonnode.TonNodeBlockIdExt -import org.ton.block.AccountInfo import org.ton.block.Message import org.ton.block.MsgAddressInt +import org.ton.block.ShardAccount +import org.ton.block.Transaction import org.ton.boc.BagOfCells import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.buildCell +import org.ton.kotlin.cell.CellContext import org.ton.lite.client.LiteClient +import org.ton.lite.client.internal.TransactionId +import org.ton.tlb.CellRef import org.ton.tlb.TlbCodec import org.ton.tlb.TlbStorer import org.ton.tlb.storeTlb interface Provider { - suspend fun getAccountState(address: MsgAddressInt, blockId: TonNodeBlockIdExt? = null): AccountInfo? + suspend fun getLastBlockId(): TonNodeBlockIdExt + + suspend fun getAccount(address: MsgAddressInt, blockId: TonNodeBlockIdExt? = null): ShardAccount? suspend fun sendMessage(serializer: TlbStorer, message: Message) + suspend fun getTransactions( + address: MsgAddressInt, + fromTxLt: Long? = null, + fromTxHash: ByteString? = null + ): List> } class LiteClientProvider( val liteClient: LiteClient ) : Provider { - override suspend fun getAccountState( + override suspend fun getLastBlockId(): TonNodeBlockIdExt { + return liteClient.getLastBlockId() + } + + override suspend fun getAccount( address: MsgAddressInt, blockId: TonNodeBlockIdExt? - ): AccountInfo? { + ): ShardAccount? { val state = if (blockId == null) { liteClient.getAccountState(address) } else { liteClient.getAccountState(address, blockId) } - return state.account.value as? AccountInfo + val lastTransactionId = state.lastTransactionId + if (lastTransactionId != null) { + return ShardAccount( + state.account, + ByteString(*lastTransactionId.hash.toByteArray()), + lastTransactionId.lt + ) + } else { + return ShardAccount( + state.account, + ByteString(*ByteArray(32)), + 0 + ) + } } override suspend fun sendMessage(serializer: TlbStorer, message: Message) { val cell = buildCell { val codec = Message.tlbCodec(object : TlbCodec { - override fun storeTlb(cellBuilder: CellBuilder, value: T) = serializer.storeTlb(cellBuilder, value) - override fun loadTlb(cellSlice: CellSlice): T = throw UnsupportedOperationException() + override fun storeTlb(cellBuilder: CellBuilder, value: T, context: CellContext) = + serializer.storeTlb(cellBuilder, value, context) + + override fun loadTlb(cellSlice: CellSlice, context: CellContext): T = + throw UnsupportedOperationException() }) storeTlb(codec, message) } - val byteArray = BagOfCells(cell).toByteArray() - println(hex(byteArray)) - val bagOfCells = BagOfCells(byteArray).first() - println(bagOfCells) - println(cell) + val boc = BagOfCells(cell) + liteClient.sendMessage(boc) + } + + override suspend fun getTransactions( + address: MsgAddressInt, + fromTxLt: Long?, + fromTxHash: ByteString? + ): List> { + if (fromTxLt == null || fromTxHash == null) { + val account = getAccount(address) ?: return emptyList() + return getTransactions(address, account.lastTransLt, account.lastTransHash) + } + return liteClient.getTransactions(address, TransactionId(fromTxHash.toByteArray(), fromTxLt), 10).map { + it.transaction + } } } \ No newline at end of file diff --git a/hashmap-tlb/src/Dictionary.kt b/hashmap-tlb/src/Dictionary.kt index 0deeff29..370f89ec 100644 --- a/hashmap-tlb/src/Dictionary.kt +++ b/hashmap-tlb/src/Dictionary.kt @@ -38,7 +38,7 @@ public open class Dictionary( val builder = CellBuilder() map.forEach { (key, value) -> builder.reset() - valueCodec.storeTlb(builder, value) + valueCodec.storeTlb(builder, value, context) val slice = builder.endCell().beginParse() val bitString = keyCodec.encodeKey(key) set(bitString, slice, context) @@ -55,6 +55,8 @@ public open class Dictionary( context ) + public val cell: Cell? get() = dict.root + override val size: Int get() = dict.iterator(context).asSequence().count() @@ -69,8 +71,7 @@ public open class Dictionary( public fun loadEntries(context: CellContext = this.context): Sequence> = dict.iterator(context).asSequence().map { (key, value) -> -// val value = valueSerializer.load(value, context) TODO: use context - val value = valueCodec.loadTlb(value) + val value = valueCodec.loadTlb(value, context) val key = keyCodec.decodeKey(key) DictEntry(key, value) } @@ -82,8 +83,7 @@ public open class Dictionary( public fun loadValues(context: CellContext = this.context): Sequence = dict.iterator(context).asSequence().map { (_, value) -> -// valueSerializer.load(value, context) TODO: use context - valueCodec.loadTlb(value) + valueCodec.loadTlb(value, context) } override fun isEmpty(): Boolean { @@ -101,9 +101,7 @@ public open class Dictionary( override fun get(key: K): V? = get(key, context) public fun get(key: K, context: CellContext = this.context): V? { - // TODO: use context -// return valueSerializer.loadTlb(dict.get(keySerializer(key), context) ?: return null, context) - return valueCodec.loadTlb(dict.get(keyCodec.encodeKey(key), context) ?: return null) + return valueCodec.loadTlb(dict.get(keyCodec.encodeKey(key), context) ?: return null, context) } public fun toMap(context: CellContext = this.context): Map { @@ -114,7 +112,7 @@ public open class Dictionary( public fun > toMap(destination: M, context: CellContext = this.context): M { if (dict.isEmpty()) return destination dict.iterator(context).forEach { (key, value) -> - val value = valueCodec.loadTlb(value) + val value = valueCodec.loadTlb(value, context) val key = keyCodec.decodeKey(key) destination[key] = value } diff --git a/hashmap-tlb/src/DictionaryKeyCodec.kt b/hashmap-tlb/src/DictionaryKeyCodec.kt index 843132a0..a537bb37 100644 --- a/hashmap-tlb/src/DictionaryKeyCodec.kt +++ b/hashmap-tlb/src/DictionaryKeyCodec.kt @@ -3,21 +3,12 @@ package org.ton.kotlin.dict import org.ton.bitstring.BitString +import org.ton.cell.CellSlice +import org.ton.cell.buildCell public interface DictionaryKeyCodec : DictionaryKeyLoader, DictionaryKeyStorer { public companion object { - public val INT32: DictionaryKeyCodec = object : DictionaryKeyCodec { - override val keySize: Int get() = Int.SIZE_BITS - - override fun decodeKey(value: BitString): Int { - require(value.size == Int.SIZE_BITS) - return value.toHexString().toUInt(16).toInt() - } - - override fun encodeKey(value: Int): BitString { - return BitString.parse(value.toHexString()) - } - } + public val INT32: DictionaryKeyCodec = int() public val BITS256: DictionaryKeyCodec = object : DictionaryKeyCodec { override val keySize: Int get() = 256 @@ -32,6 +23,34 @@ public interface DictionaryKeyCodec : DictionaryKeyLoader, DictionaryKeySt return value } } + + public fun long(keySize: Int = Long.SIZE_BITS): DictionaryKeyCodec = object : DictionaryKeyCodec { + override val keySize: Int = keySize + + override fun decodeKey(value: BitString): Long { + return CellSlice(value).loadLong(keySize) + } + + override fun encodeKey(value: Long): BitString { + return buildCell { + storeLong(value, keySize) + }.bits + } + } + + public fun int(keySize: Int = Int.SIZE_BITS): DictionaryKeyCodec = object : DictionaryKeyCodec { + override val keySize: Int = keySize + + override fun decodeKey(value: BitString): Int { + return CellSlice(value).loadInt(keySize).toInt() + } + + override fun encodeKey(value: Int): BitString { + return buildCell { + storeInt(value, keySize) + }.bits + } + } } } diff --git a/hashmap-tlb/src/HashMapE.kt b/hashmap-tlb/src/HashMapE.kt index e230654a..c7334202 100644 --- a/hashmap-tlb/src/HashMapE.kt +++ b/hashmap-tlb/src/HashMapE.kt @@ -16,8 +16,6 @@ public sealed interface HashMapE : Iterable>, TlbObject { override fun iterator(): Iterator> - public fun set(key: BitString, value: T): HmeRoot - public companion object { @Suppress("UNCHECKED_CAST") @JvmStatic @@ -29,19 +27,6 @@ public sealed interface HashMapE : Iterable>, TlbObject { @JvmStatic public fun root(root: CellRef>): HashMapE = HmeRoot(root) - public fun fromMap(map: Map?): HashMapE { - var hashMap = empty() - if (map == null) return hashMap - var i = -1 - map.forEach { (key, value) -> - require(!key.isEmpty()) { "Empty key" } - if (i == -1) i = key.size - else require(i == key.size) { "Variable length key, expected: $i, actual: (${key.size}) $key" } - hashMap = hashMap.set(key, value) - } - return hashMap - } - @Suppress("UNCHECKED_CAST") @JvmStatic public fun tlbCodec(n: Int, x: TlbCodec): TlbCodec> { diff --git a/hashmap-tlb/src/HashMapNode.kt b/hashmap-tlb/src/HashMapNode.kt index c326350b..c2f85239 100644 --- a/hashmap-tlb/src/HashMapNode.kt +++ b/hashmap-tlb/src/HashMapNode.kt @@ -21,11 +21,6 @@ public sealed interface HashMapNode : TlbObject { HmnFork.tlbCodec(n, x) } as TlbCodec> - public fun fork( - left: HmEdge, - right: HmEdge - ): HmnFork = HmnFork(left, right) - public fun leaf(value: T): HmnLeaf = HmnLeaf(value) } } diff --git a/hashmap-tlb/src/HashmapAugE.kt b/hashmap-tlb/src/HashmapAugE.kt index 62d20285..625b00ae 100644 --- a/hashmap-tlb/src/HashmapAugE.kt +++ b/hashmap-tlb/src/HashmapAugE.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.json.JsonClassDiscriminator import org.ton.bitstring.BitString import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.* import kotlin.jvm.JvmStatic @@ -122,14 +123,14 @@ private class AhmeEmptyTlbConstructor( ) : TlbConstructor>( schema = "ahme_empty\$0 {n:#} {X:Type} {Y:Type} extra:Y = HashmapAugE n X Y" ) { - override fun loadTlb(cellSlice: CellSlice): HashmapAugE.AhmeEmpty { - val extra = y.loadTlb(cellSlice) + override fun loadTlb(cellSlice: CellSlice, context: CellContext): HashmapAugE.AhmeEmpty { + val extra = y.loadTlb(cellSlice, context) return AhmeEmptyImpl(n, extra) } - override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugE.AhmeEmpty) { + override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugE.AhmeEmpty, context: CellContext) { require(value.n == n) { "n mismatch, expected: $n, actual: ${value.n}" } - y.storeTlb(cellBuilder, value.extra) + y.storeTlb(cellBuilder, value.extra, context) } } @@ -142,15 +143,15 @@ private class AhmeRootTlbConstructor( ) { private val hashmapAug = HashmapAug.tlbCodec(n, x, y) - override fun loadTlb(cellSlice: CellSlice): HashmapAugE.AhmeRoot { + override fun loadTlb(cellSlice: CellSlice, context: CellContext): HashmapAugE.AhmeRoot { val root = cellSlice.loadRef(hashmapAug) - val extra = y.loadTlb(cellSlice) + val extra = y.loadTlb(cellSlice, context) return AhmeRootImpl(n, root, extra) } - override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugE.AhmeRoot) { + override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugE.AhmeRoot, context: CellContext) { require(value.n == n) { "n mismatch, expected: $n, actual: ${value.n}" } cellBuilder.storeRef(hashmapAug, value.root) - y.storeTlb(cellBuilder, value.extra) + y.storeTlb(cellBuilder, value.extra, context) } } diff --git a/hashmap-tlb/src/HashmapAugNode.kt b/hashmap-tlb/src/HashmapAugNode.kt index 54a50fdb..2f415cb3 100644 --- a/hashmap-tlb/src/HashmapAugNode.kt +++ b/hashmap-tlb/src/HashmapAugNode.kt @@ -2,6 +2,7 @@ package org.ton.hashmap import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.* import kotlin.jvm.JvmStatic @@ -112,15 +113,15 @@ private class AhmnLeafTlbConstructor( ) : TlbConstructor>( schema = "ahmn_leaf#_ {X:Type} {Y:Type} extra:Y value:X = HashmapAugNode 0 X Y" ) { - override fun loadTlb(cellSlice: CellSlice): HashmapAugNode.AhmnLeaf { - val extra = y.loadTlb(cellSlice) - val value = x.loadTlb(cellSlice) + override fun loadTlb(cellSlice: CellSlice, context: CellContext): HashmapAugNode.AhmnLeaf { + val extra = y.loadTlb(cellSlice, context) + val value = x.loadTlb(cellSlice, context) return AhmnLeafImpl(extra, value) } - override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugNode.AhmnLeaf) { - y.storeTlb(cellBuilder, value.extra) - x.storeTlb(cellBuilder, value.value) + override fun storeTlb(cellBuilder: CellBuilder, value: HashmapAugNode.AhmnLeaf, context: CellContext) { + y.storeTlb(cellBuilder, value.extra, context) + x.storeTlb(cellBuilder, value.value, context) } } diff --git a/hashmap-tlb/src/HmEdge.kt b/hashmap-tlb/src/HmEdge.kt index 69495555..c2cce67a 100644 --- a/hashmap-tlb/src/HmEdge.kt +++ b/hashmap-tlb/src/HmEdge.kt @@ -16,43 +16,6 @@ public data class HmEdge( ) : Iterable>, TlbObject { override fun iterator(): Iterator> = HmEdgeIterator(this) - public fun set(key: BitString, value: T): HmEdge { - check(!key.isEmpty()) - val label = label.toBitString() - if (label == key) { - // replace existing leaf - return HmEdge(this.label, HmnLeaf(value)) - } else if (label.isEmpty()) { - // 1-bit edge - node as HmnFork - return HmEdge(this.label, node.set(key, value)) - } else { - val labelPrefix = label.commonPrefixWith(key) - val labelReminder = label.slice(labelPrefix.size) - val keyReminder = key.slice(labelPrefix.size) - - if (keyReminder.isEmpty()) { - throw IllegalArgumentException("variable length key: $key") - } else if (!labelReminder.isEmpty() && !keyReminder.isEmpty()) { - // forking - val (left, right) = if (keyReminder[0]) { - HmEdge(HmLabel(labelReminder.slice(1)), node) to - HmEdge(HmLabel(keyReminder.slice(1)), HmnLeaf(value)) - } else { - HmEdge(HmLabel(keyReminder.slice(1)), HmnLeaf(value)) to - HmEdge(HmLabel(labelReminder.slice(1)), node) - } - return HmEdge(HmLabel(labelPrefix), HmnFork(left, right)) - } else if (!labelPrefix.isEmpty() && labelReminder.isEmpty() && !keyReminder.isEmpty()) { - // next iteration - node as HmnFork - val newNode = node.set(keyReminder, value) - return HmEdge(HmLabel(labelPrefix), newNode) - } - throw IllegalStateException() - } - } - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { type("hm_edge") { field("label", label) diff --git a/hashmap-tlb/src/HmeEmpty.kt b/hashmap-tlb/src/HmeEmpty.kt index d6e2c432..36eebe3d 100644 --- a/hashmap-tlb/src/HmeEmpty.kt +++ b/hashmap-tlb/src/HmeEmpty.kt @@ -13,11 +13,6 @@ public class HmeEmpty : HashMapE { override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("hme_empty") - override fun set(key: BitString, value: T): HmeRoot { - val root = HmEdge(HmLabel(key), HmnLeaf(value)) - return HmeRoot(root) - } - override fun toString(): String = print().toString() } diff --git a/hashmap-tlb/src/HmeRoot.kt b/hashmap-tlb/src/HmeRoot.kt index 93a59603..cf86cfcc 100644 --- a/hashmap-tlb/src/HmeRoot.kt +++ b/hashmap-tlb/src/HmeRoot.kt @@ -15,15 +15,9 @@ public data class HmeRoot( val root: CellRef> ) : HashMapE { public constructor(root: Cell, tlbCodec: TlbCodec>) : this(CellRef(root, tlbCodec)) - public constructor(root: HmEdge) : this(CellRef(root)) override fun iterator(): Iterator> = root.value.iterator() - override fun set(key: BitString, value: T): HmeRoot { - val root = root.value.set(key, value) - return HmeRoot(root) - } - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer { type("hme_root") { field("root", root) diff --git a/hashmap-tlb/src/HmnFork.kt b/hashmap-tlb/src/HmnFork.kt index 5200b391..e827c6e0 100644 --- a/hashmap-tlb/src/HmnFork.kt +++ b/hashmap-tlb/src/HmnFork.kt @@ -14,33 +14,11 @@ public data class HmnFork( val left: CellRef>, val right: CellRef> ) : HashMapNode { - public constructor( - left: HmEdge, - right: HmEdge, - ) : this( - left = CellRef(left), - right = CellRef(right) - ) - override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter = printer.type("hmn_fork") { field("left", left) field("right", right) } - public fun set(key: BitString, value: T): HmnFork { - return if (key[0]) { - copy( - left = left, - right = CellRef(right.value.set(key.slice(1), value)) - ) - } else { - copy( - left = CellRef(left.value.set(key.slice(1), value)), - right = right, - ) - } - } - override fun toString(): String = print().toString() public companion object { diff --git a/liteapi-tl/src/LiteApiClient.kt b/liteapi-tl/src/LiteApiClient.kt index baf3b83e..1f3f4cc9 100644 --- a/liteapi-tl/src/LiteApiClient.kt +++ b/liteapi-tl/src/LiteApiClient.kt @@ -1,11 +1,13 @@ package org.ton.lite.api +import kotlinx.coroutines.coroutineScope import kotlinx.io.Buffer import kotlinx.io.readByteString import org.ton.lite.api.exception.LiteServerException import org.ton.lite.api.liteserver.* import org.ton.lite.api.liteserver.functions.* import org.ton.tl.TlCodec +import kotlin.coroutines.cancellation.CancellationException public interface LiteApiClient : LiteApi { public suspend fun sendQuery( @@ -13,7 +15,7 @@ public interface LiteApiClient : LiteApi { answerCodec: TlCodec, query: Q, waitMasterchainSeqno: Int = -1 - ): A { + ): A = coroutineScope { val rawQuery = Buffer().apply { if (waitMasterchainSeqno > 0) { val wait = LiteServerWaitMasterchainSeqno(waitMasterchainSeqno, 30000) @@ -29,10 +31,10 @@ public interface LiteApiClient : LiteApi { null } if (liteServerError != null) { - throw LiteServerException.create(liteServerError.code, liteServerError.message) + throw CancellationException(LiteServerException.create(liteServerError.code, liteServerError.message)) } val answer = answerCodec.decodeBoxed(result) - return answer + answer } public suspend fun sendRawQuery(query: ByteArray): ByteArray diff --git a/liteapi-tl/src/liteserver/functions/LiteServerRunSmcMethod.kt b/liteapi-tl/src/liteserver/functions/LiteServerRunSmcMethod.kt index 5c0518b5..c4e4a2d5 100644 --- a/liteapi-tl/src/liteserver/functions/LiteServerRunSmcMethod.kt +++ b/liteapi-tl/src/liteserver/functions/LiteServerRunSmcMethod.kt @@ -8,7 +8,9 @@ import org.ton.block.VmStack import org.ton.block.VmStackList import org.ton.block.VmStackValue import org.ton.boc.BagOfCells +import org.ton.cell.buildCell import org.ton.crypto.crc16 +import org.ton.kotlin.cell.CellContext import org.ton.lite.api.liteserver.LiteServerAccountId import org.ton.lite.api.liteserver.LiteServerRunMethodResult import org.ton.tl.* @@ -44,7 +46,9 @@ public data class LiteServerRunSmcMethod( @JvmStatic public fun params(vmStack: VmStack): ByteArray = - BagOfCells(VmStack.createCell(vmStack)).toByteArray() + BagOfCells(buildCell(CellContext.EMPTY) { + VmStack.storeTlb(this, vmStack, CellContext.EMPTY) + }).toByteArray() @JvmStatic public fun params(vmStackList: VmStackList?): ByteArray = diff --git a/liteclient/src/CheckProofUtils.kt b/liteclient/src/CheckProofUtils.kt index e90d12a9..068ead99 100644 --- a/liteclient/src/CheckProofUtils.kt +++ b/liteclient/src/CheckProofUtils.kt @@ -59,14 +59,14 @@ internal object CheckProofUtils { val shardAccount = checkNotNull(shardState.accounts.value.x[address.address]?.value) { "Shard account ${address.address} not found in shard state" } - check(shardAccount.account.hash() == root.hash()) { + check(shardAccount.account.cell.virtualize().hash() == root.hash()) { "Account state hash mismatch, expected: ${shardAccount.account.hash()}, actual: ${root.hash()}" } return FullAccountState( shardBlock, address, - TransactionId(shardAccount.lastTransHash, shardAccount.lastTransLt.toLong()), + TransactionId(shardAccount.lastTransHash.toByteArray(), shardAccount.lastTransLt.toLong()), account ) } diff --git a/liteclient/src/LiteClient.kt b/liteclient/src/LiteClient.kt index 709460d0..853ebadb 100644 --- a/liteclient/src/LiteClient.kt +++ b/liteclient/src/LiteClient.kt @@ -318,7 +318,7 @@ public class LiteClient( } catch (e: Exception) { throw RuntimeException("Can't deserialize block data", e) } - val actualRootHash = root.hash().toBitString() + root.hash().toBitString() // FIXME: https://github.com/andreypfau/ton-kotlin/issues/82 // check(blockId.rootHash.toBitString() == actualRootHash) { // "block root hash mismatch, expected: ${blockId.rootHash} , actual: $actualRootHash" @@ -344,7 +344,7 @@ public class LiteClient( throw IllegalStateException("Can't deserialize account state", e) } if (root.isEmpty()) { - return FullAccountState(rawAccountState.shardBlock, accountAddress, null, CellRef(AccountNone, Account)) + return FullAccountState(rawAccountState.shardBlock, accountAddress, null, CellRef(null, Account)) } check(rawAccountState.id == blockId || rawAccountState.id.seqno == 0) { @@ -454,9 +454,11 @@ public class LiteClient( } - public suspend fun sendMessage(body: Message): LiteServerSendMsgStatus = sendMessage(CellRef(body)) + public suspend fun sendMessage(body: Message): LiteServerSendMsgStatus = + sendMessage(CellRef(body, Message.tlbCodec(AnyTlbConstructor))) + public suspend fun sendMessage(body: CellRef>): LiteServerSendMsgStatus = - sendMessage(body.toCell(Message.tlbCodec(AnyTlbConstructor))) + sendMessage(body.cell) public suspend fun sendMessage(cell: Cell): LiteServerSendMsgStatus = sendMessage(BagOfCells(cell)) public suspend fun sendMessage(boc: BagOfCells): LiteServerSendMsgStatus { diff --git a/liteclient/src/intetnal/BlockHeaderResult.kt b/liteclient/src/intetnal/BlockHeaderResult.kt index 131bba76..e1687a1d 100644 --- a/liteclient/src/intetnal/BlockHeaderResult.kt +++ b/liteclient/src/intetnal/BlockHeaderResult.kt @@ -17,7 +17,6 @@ internal data class BlockHeaderResult( val stateHash: BitString? ) -@Serializable public data class FullAccountState( @SerialName("block_id") @get:JvmName("blockId") @@ -31,7 +30,7 @@ public data class FullAccountState( public val lastTransactionId: TransactionId?, @get:JvmName("account") - public val account: CellRef + public val account: CellRef ) @Serializable diff --git a/tlb/src/CellRef.kt b/tlb/src/CellRef.kt index f00fc85b..56a33fc4 100644 --- a/tlb/src/CellRef.kt +++ b/tlb/src/CellRef.kt @@ -1,22 +1,27 @@ package org.ton.tlb import org.ton.bitstring.BitString -import org.ton.cell.Cell -import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice -import org.ton.cell.CellType +import org.ton.cell.* +import org.ton.kotlin.cell.CellContext import kotlin.jvm.JvmStatic public inline fun CellRef(cell: Cell, codec: TlbCodec): CellRef = CellRef.valueOf(cell, codec) -public inline fun CellRef(value: T, codec: TlbCodec? = null): CellRef = CellRef.valueOf(value, codec) + +public inline fun CellRef(value: T, codec: TlbCodec): CellRef = CellRef.valueOf(value, codec) public inline fun CellRef(codec: TlbCodec): TlbCodec> = CellRef.tlbCodec(codec) public inline fun Cell.asRef(codec: TlbCodec): CellRef = CellRef.valueOf(this, codec) public interface CellRef : TlbObject { - public val value: T + @Deprecated("use load() instead.", ReplaceWith("load()")) + public val value: T get() = load() + public val cell: Cell + + public fun load(): T = load(CellContext.EMPTY) + public fun load(context: CellContext): T - public fun toCell(codec: TlbCodec<@UnsafeVariance T>? = null): Cell + @Deprecated("use cell instead.", ReplaceWith("cell")) + public fun toCell(codec: TlbCodec<@UnsafeVariance T>? = null): Cell = cell public fun hash(): BitString = hash(null) public fun hash(codec: TlbCodec<@UnsafeVariance T>?): BitString = toCell().hash() @@ -38,11 +43,15 @@ public interface CellRef : TlbObject { @JvmStatic public fun valueOf(cell: Cell, codec: TlbCodec): CellRef = CellRefImpl(cell, codec) + @Suppress("DEPRECATION") @JvmStatic + @Deprecated("Scheduled to be removed") public fun valueOf(value: T): CellRef = CellRefValue(value, null) @JvmStatic - public fun valueOf(value: T, codec: TlbCodec?): CellRef = CellRefValue(value, codec) + public fun valueOf(value: T, codec: TlbCodec): CellRef = CellRefImpl(buildCell { + codec.storeTlb(this, value, CellContext.EMPTY) + }, codec) @JvmStatic public fun tlbCodec(codec: TlbCodec): TlbCodec> = CellRefTlbConstructor(codec) @@ -50,16 +59,11 @@ public interface CellRef : TlbObject { } private class CellRefImpl( - val cell: Cell, + override val cell: Cell, val codec: TlbCodec ) : CellRef { - override val value: T by lazy(LazyThreadSafetyMode.PUBLICATION) { - check(cell.type != CellType.PRUNED_BRANCH) { "Can't load reference value: $cell" } - codec.loadTlb(cell) - } - - override fun toCell(codec: TlbCodec?): Cell { - return cell + override fun load(context: CellContext): T { + return codec.loadTlb(context.loadCell(cell).beginParse(), context) } override fun print(printer: TlbPrettyPrinter): TlbPrettyPrinter { @@ -75,16 +79,22 @@ private class CellRefImpl( override fun toString(): String = "CellRef($cell)" } +@Deprecated("Deprecated") private class CellRefValue( override val value: T, val codec: TlbCodec? = null ) : CellRef { - override fun toCell(codec: TlbCodec?): Cell { - val currentCodec = codec ?: this.codec - require(currentCodec != null) { "Codec is not specified" } - return CellBuilder.createCell { - currentCodec.storeTlb(this, value) + override val cell: Cell + get() { + val currentCodec = codec ?: this.codec + require(currentCodec != null) { "Codec is not specified" } + return CellBuilder.createCell { + currentCodec.storeTlb(this, value, CellContext.EMPTY) + } } + + override fun load(context: CellContext): T { + return value } override fun toString(): String = "CellRef($value)" @@ -103,7 +113,7 @@ private class CellRefTlbConstructor( } public inline fun CellBuilder.storeRef(codec: TlbCodec, value: CellRef) { - storeRef(value.toCell(codec)) + storeRef(value.cell) } public inline fun CellSlice.loadRef(codec: TlbCodec): CellRef { diff --git a/tlb/src/TlbCodec.kt b/tlb/src/TlbCodec.kt index 5f687d11..e4ada182 100644 --- a/tlb/src/TlbCodec.kt +++ b/tlb/src/TlbCodec.kt @@ -5,19 +5,22 @@ package org.ton.tlb import org.ton.cell.Cell import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext public interface TlbStorer { - public fun storeTlb(cellBuilder: CellBuilder, value: T) + public fun storeTlb(builder: CellBuilder, value: T): Unit = storeTlb(builder, value, CellContext.EMPTY) + public fun storeTlb(builder: CellBuilder, value: T, context: CellContext): Unit = storeTlb(builder, value) public fun createCell(value: T): Cell = CellBuilder.createCell { - storeTlb(this, value) + storeTlb(this, value, CellContext.EMPTY) } } +@Deprecated("Scheduled to remove") public interface TlbNegatedStorer : TlbStorer { - public fun storeNegatedTlb(cellBuilder: CellBuilder, value: T): Int + public fun storeNegatedTlb(builder: CellBuilder, value: T): Int - override fun storeTlb(cellBuilder: CellBuilder, value: T) { - storeNegatedTlb(cellBuilder, value) + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext) { + storeNegatedTlb(builder, value) } } @@ -27,17 +30,20 @@ public interface TlbLoader { return loadTlb(cellSlice) } - public fun loadTlb(cellSlice: CellSlice): T + public fun loadTlb(slice: CellSlice): T = loadTlb(slice, CellContext.EMPTY) + + public fun loadTlb(slice: CellSlice, context: CellContext): T = loadTlb(slice) } +@Deprecated("Scheduled to remove") public interface TlbNegatedLoader : TlbLoader { public fun loadNegatedTlb(cell: Cell): TlbNegatedResult = cell.parse { loadNegatedTlb(this) } - public fun loadNegatedTlb(cellSlice: CellSlice): TlbNegatedResult + public fun loadNegatedTlb(slice: CellSlice): TlbNegatedResult - override fun loadTlb(cellSlice: CellSlice): T = loadNegatedTlb(cellSlice).value + override fun loadTlb(slice: CellSlice): T = loadNegatedTlb(slice).value } public data class TlbNegatedResult( @@ -46,8 +52,34 @@ public data class TlbNegatedResult( ) public interface TlbCodec : TlbStorer, TlbLoader + +@Suppress("DEPRECATION") +@Deprecated("Scheduled to remove") public interface TlbNegatedCodec : TlbCodec, TlbNegatedStorer, TlbNegatedLoader +public fun TlbCodec.asNullable(): TlbCodec = NullableTlbCodec(this) + +public class NullableTlbCodec( + private val codec: TlbCodec +) : TlbCodec { + override fun storeTlb(builder: CellBuilder, value: T?, context: CellContext) { + if (value == null) { + builder.storeBoolean(false) + } else { + builder.storeBoolean(true) + codec.storeTlb(builder, value, context) + } + } + + override fun loadTlb(slice: CellSlice, context: CellContext): T? { + return if (slice.loadBoolean()) { + codec.loadTlb(slice, context) + } else { + null + } + } +} + public inline fun CellSlice.loadTlb(codec: TlbLoader): T { return codec.loadTlb(this) } @@ -56,8 +88,12 @@ public inline fun CellSlice.loadNegatedTlb(codec: TlbNegatedLoader): TlbN return codec.loadNegatedTlb(this) } -public inline fun CellBuilder.storeTlb(codec: TlbStorer, value: T): CellBuilder = apply { - codec.storeTlb(this, value) +public inline fun CellBuilder.storeTlb( + codec: TlbStorer, + value: T, + context: CellContext = CellContext.EMPTY +): CellBuilder = apply { + codec.storeTlb(this, value, context) } public inline fun CellBuilder.storeNegatedTlb(codec: TlbNegatedStorer, value: T): Int = diff --git a/tlb/src/TlbCombinator.kt b/tlb/src/TlbCombinator.kt index 3473be95..2084321a 100644 --- a/tlb/src/TlbCombinator.kt +++ b/tlb/src/TlbCombinator.kt @@ -3,6 +3,7 @@ package org.ton.tlb import org.ton.bitstring.BitString import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.exception.UnknownTlbConstructorException import org.ton.tlb.providers.TlbCombinatorProvider import org.ton.tlb.providers.TlbConstructorProvider @@ -44,24 +45,24 @@ public abstract class TlbCombinator( override fun tlbCombinator(): TlbCombinator = this - override fun loadTlb(cellSlice: CellSlice): T { - val constructor = findTlbLoaderOrNull(cellSlice) ?: throw UnknownTlbConstructorException( - cellSlice.preloadBits(32) + override fun loadTlb(slice: CellSlice, context: CellContext): T { + val constructor = findTlbLoaderOrNull(slice) ?: throw UnknownTlbConstructorException( + slice.preloadBitString(32) ) if (constructor is TlbConstructor<*>) { - cellSlice.skipBits(constructor.id.size) + slice.skipBits(constructor.id.size) } - return constructor.loadTlb(cellSlice) + return constructor.loadTlb(slice, context) } - override fun storeTlb(cellBuilder: CellBuilder, value: T) { + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext) { val storer = findTlbStorerOrNull(value) ?: throw UnknownTlbConstructorException() if (storer is TlbConstructorProvider<*>) { - cellBuilder.storeBits(storer.tlbConstructor().id) + builder.storeBitString(storer.tlbConstructor().id) } else if (storer is TlbConstructor<*>) { - cellBuilder.storeBits(storer.id) + builder.storeBitString(storer.id) } - return storer.storeTlb(cellBuilder, value) + return storer.storeTlb(builder, value, context) } protected open fun findTlbLoaderOrNull(cellSlice: CellSlice): TlbLoader? { diff --git a/tlb/src/TlbConstructor.kt b/tlb/src/TlbConstructor.kt index 43af2581..e6522aa4 100644 --- a/tlb/src/TlbConstructor.kt +++ b/tlb/src/TlbConstructor.kt @@ -3,6 +3,7 @@ package org.ton.tlb import org.ton.bitstring.BitString import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.exception.ParseTlbException import org.ton.tlb.providers.TlbConstructorProvider import kotlin.jvm.JvmStatic @@ -72,11 +73,20 @@ public abstract class TlbNegatedConstructor( schema: String, id: BitString? = null ) : TlbConstructor(schema, id), TlbNegatedCodec { - override fun storeTlb(cellBuilder: CellBuilder, value: T) { + + override fun storeTlb(builder: CellBuilder, value: T) { + storeNegatedTlb(builder, value) + } + + override fun storeTlb(cellBuilder: CellBuilder, value: T, context: CellContext) { storeNegatedTlb(cellBuilder, value) } - override fun loadTlb(cellSlice: CellSlice): T = loadNegatedTlb(cellSlice).value + override fun loadTlb(slice: CellSlice, context: CellContext): T = loadNegatedTlb(slice).value + + override fun loadTlb(slice: CellSlice): T { + return loadNegatedTlb(slice).value + } } public inline fun TlbConstructor.asTlbCombinator(): TlbCombinator = asTlbCombinator(T::class) diff --git a/tlb/src/TlbNegatedCombinator.kt b/tlb/src/TlbNegatedCombinator.kt index bcaf0520..793ebdb7 100644 --- a/tlb/src/TlbNegatedCombinator.kt +++ b/tlb/src/TlbNegatedCombinator.kt @@ -2,6 +2,7 @@ package org.ton.tlb import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.exception.UnknownTlbConstructorException import kotlin.reflect.KClass @@ -12,11 +13,11 @@ public abstract class TlbNegatedCombinator( baseClass, *subClasses ), TlbNegatedCodec { - override fun storeTlb(cellBuilder: CellBuilder, value: T) { + override fun storeTlb(cellBuilder: CellBuilder, value: T, context: CellContext) { storeNegatedTlb(cellBuilder, value) } - override fun loadTlb(cellSlice: CellSlice): T = loadNegatedTlb(cellSlice).value + override fun loadTlb(cellSlice: CellSlice, context: CellContext): T = loadNegatedTlb(cellSlice).value override fun storeNegatedTlb(cellBuilder: CellBuilder, value: T): Int { val constructor = findTlbStorerOrNull(value) as? TlbNegatedConstructor diff --git a/tlb/src/constructor/AnyTlbConstructor.kt b/tlb/src/constructor/AnyTlbConstructor.kt index 65f477b3..7c899bf2 100644 --- a/tlb/src/constructor/AnyTlbConstructor.kt +++ b/tlb/src/constructor/AnyTlbConstructor.kt @@ -4,6 +4,7 @@ import org.ton.cell.Cell import org.ton.cell.CellBuilder import org.ton.cell.CellSlice import org.ton.cell.buildCell +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbCodec public object AnyTlbConstructor : TlbCodec { @@ -19,3 +20,17 @@ public object AnyTlbConstructor : TlbCodec { } } } + +public object RemainingTlbCodec : TlbCodec { + override fun storeTlb(builder: CellBuilder, value: CellSlice, context: CellContext) { + builder.storeBitString(value.bits) + builder.storeRefs(value.refs) + } + + override fun loadTlb(cellSlice: CellSlice): CellSlice { + return buildCell { + storeBitString(cellSlice.loadBits(cellSlice.bits.size - cellSlice.bitsPosition)) + storeRefs(cellSlice.loadRefs(cellSlice.refs.size - cellSlice.refsPosition)) + }.beginParse() + } +} \ No newline at end of file diff --git a/tlb/src/providers/TlbCombinatorProvider.kt b/tlb/src/providers/TlbCombinatorProvider.kt index 11273c40..849ea351 100644 --- a/tlb/src/providers/TlbCombinatorProvider.kt +++ b/tlb/src/providers/TlbCombinatorProvider.kt @@ -2,14 +2,16 @@ package org.ton.tlb.providers import org.ton.cell.CellBuilder import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbCodec import org.ton.tlb.TlbCombinator public interface TlbCombinatorProvider : TlbProvider, TlbCodec { public fun tlbCombinator(): TlbCombinator - override fun loadTlb(cellSlice: CellSlice): T = tlbCombinator().loadTlb(cellSlice) - override fun storeTlb(cellBuilder: CellBuilder, value: T) { - tlbCombinator().storeTlb(cellBuilder, value) - } + override fun loadTlb(slice: CellSlice, context: CellContext): T = + tlbCombinator().loadTlb(slice, context) + + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext): Unit = + tlbCombinator().storeTlb(builder, value, context) } diff --git a/tlb/src/providers/TlbConstructorProvider.kt b/tlb/src/providers/TlbConstructorProvider.kt index e0b0c6e9..17f60a4e 100644 --- a/tlb/src/providers/TlbConstructorProvider.kt +++ b/tlb/src/providers/TlbConstructorProvider.kt @@ -1,15 +1,18 @@ package org.ton.tlb.providers import org.ton.cell.CellBuilder -import org.ton.cell.CellSlice +import org.ton.kotlin.cell.CellContext import org.ton.tlb.TlbCodec import org.ton.tlb.TlbConstructor -public interface TlbConstructorProvider : TlbProvider, TlbCodec { +public interface TlbConstructorProvider : TlbCodec { public fun tlbConstructor(): TlbConstructor - override fun loadTlb(cellSlice: CellSlice): T = tlbConstructor().loadTlb(cellSlice) - override fun storeTlb(cellBuilder: CellBuilder, value: T) { - tlbConstructor().storeTlb(cellBuilder, value) + override fun storeTlb(builder: CellBuilder, value: T, context: CellContext) { + tlbConstructor().storeTlb(builder, value) + } + + override fun storeTlb(builder: CellBuilder, value: T) { + tlbConstructor().storeTlb(builder, value, CellContext.EMPTY) } } diff --git a/tvm/build.gradle.kts b/tvm/build.gradle.kts index c714df43..5a7f43bd 100644 --- a/tvm/build.gradle.kts +++ b/tvm/build.gradle.kts @@ -11,6 +11,7 @@ kotlin { api(projects.tonKotlinBigint) api(projects.tonKotlinCrypto) implementation(libs.serialization.json) + api(libs.kotlinx.io) } } } diff --git a/tvm/src/boc/BagOfCellsUtils.kt b/tvm/src/boc/BagOfCellsUtils.kt index 11b667b6..c9d7dcd4 100644 --- a/tvm/src/boc/BagOfCellsUtils.kt +++ b/tvm/src/boc/BagOfCellsUtils.kt @@ -6,7 +6,6 @@ import org.ton.bitstring.BitString import org.ton.cell.Cell import org.ton.cell.CellDescriptor import org.ton.cell.buildCell -import kotlin.experimental.and internal fun Input.readBagOfCell(): BagOfCells { val prefix = readInt() @@ -50,8 +49,8 @@ internal fun Input.readBagOfCell(): BagOfCells { val offsetSize = readByte().toInt() val cellCount = readInt(refSize) val rootsCount = readInt(refSize) - val absentCount = readInt(refSize) - val totalCellsSize = readInt(offsetSize) + readInt(refSize) + readInt(offsetSize) // Roots val rootIndexes = IntArray(rootsCount) { @@ -164,7 +163,7 @@ private suspend fun createCell( cells[refIndex].await() } val descriptor = descriptors[index] - val hashes = cellHashes[index] + cellHashes[index] // val cell = if (!descriptors[index].isExotic && hashes != null) { // val new = buildCell { // isExotic = descriptor.isExotic @@ -255,26 +254,25 @@ private fun serializeBagOfCells( } val serializedCells = cells.mapIndexed { index: Int, cell: Cell -> - buildPacket { - val (d1, d2) = cell.descriptor - writeByte(d1) - writeByte(d2) - val cellData = cell.bits.toByteArray( - augment = (d2 and 1) != 0.toByte() - ) + val serializedCell = buildPacket { + val descriptor = cell.descriptor + writeByte(descriptor.d1) + writeByte(descriptor.d2) + val cellData = cell.bits.toByteArray(true) writeFully(cellData) cell.refs.forEach { reference -> val refIndex = cells.indexOf(reference) writeInt(refIndex, sizeBytes) } - } + }.readBytes() + serializedCell } var fullSize = 0 val sizeIndex = ArrayList() serializedCells.forEach { serializedCell -> sizeIndex.add(fullSize) - fullSize += serializedCell.remaining.toInt() + fullSize += serializedCell.size } var offsetBytes = 0 while (fullSize >= (1L shl (offsetBytes shl 3))) { @@ -312,8 +310,7 @@ private fun serializeBagOfCells( } } serializedCells.forEach { serializedCell -> - val bytes = serializedCell.readBytes() - writeFully(bytes) + writeFully(serializedCell) } }.readBytes() diff --git a/tvm/src/cell/CellBuilder.kt b/tvm/src/cell/CellBuilder.kt index 1e8521c0..84a75e8b 100644 --- a/tvm/src/cell/CellBuilder.kt +++ b/tvm/src/cell/CellBuilder.kt @@ -1,12 +1,14 @@ package org.ton.cell import io.github.andreypfau.kotlinx.crypto.sha2.SHA256 +import kotlinx.io.bytestring.ByteString import org.ton.bigint.* import org.ton.bitstring.BitString import org.ton.bitstring.ByteBackedMutableBitString import org.ton.bitstring.MutableBitString import org.ton.bitstring.toBitString import org.ton.cell.exception.CellOverflowException +import org.ton.kotlin.cell.CellContext import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -48,6 +50,7 @@ public interface CellBuilder { public fun storeBitString(value: BitString, startIndex: Int = 0, endIndex: Int = value.size): CellBuilder + public fun storeByteString(value: ByteString): CellBuilder public fun storeBytes(byteArray: ByteArray): CellBuilder public fun storeByte(byte: Byte): CellBuilder @@ -55,6 +58,7 @@ public interface CellBuilder { * Stores a reference to cell into builder. */ public fun storeRef(ref: Cell): CellBuilder + public fun storeNullableRef(ref: Cell?): CellBuilder public fun storeRefs(vararg refs: Cell): CellBuilder public fun storeRefs(refs: Iterable): CellBuilder @@ -69,10 +73,14 @@ public interface CellBuilder { public fun storeUInt(value: Int, length: Int): CellBuilder = storeUInt(value.toBigInt(), length) public fun storeUInt(value: Long, length: Int): CellBuilder = storeUInt(value.toBigInt(), length) + public fun storeULong(value: ULong, bitCount: Int = ULong.SIZE_BITS): CellBuilder = storeLong(value.toLong(), 64) + public fun storeUInt8(value: UByte): CellBuilder = storeInt(value.toByte(), 8) public fun storeUInt16(value: UShort): CellBuilder = storeInt(value.toShort(), 16) public fun storeUInt32(value: UInt): CellBuilder = storeInt(value.toInt(), 32) - public fun storeUInt64(value: ULong): CellBuilder = storeInt(value.toLong(), 64) + + @Deprecated("Use storeULong(value) instead.", ReplaceWith("storeULong(value)")) + public fun storeUInt64(value: ULong): CellBuilder = storeULong(value) public fun storeUIntLeq(value: BigInt, max: BigInt): CellBuilder = storeUInt(value, max.bitLength) public fun storeUIntLeq(value: Byte, max: Byte): CellBuilder = storeUIntLeq(value.toInt(), max.toInt()) @@ -154,6 +162,7 @@ public interface CellBuilder { } public fun storeBytes(byteArray: ByteArray, length: Int): CellBuilder + } public inline operator fun CellBuilder.invoke(builder: CellBuilder.() -> Unit) { @@ -161,16 +170,22 @@ public inline operator fun CellBuilder.invoke(builder: CellBuilder.() -> Unit) { } @OptIn(ExperimentalContracts::class) -public inline fun buildCell(builderAction: CellBuilder.() -> Unit): Cell { +public inline fun buildCell(context: CellContext = CellContext.EMPTY, builderAction: CellBuilder.() -> Unit): Cell { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } - return CellBuilder.beginCell().apply(builderAction).endCell() + return context.finalizeCell(CellBuilder.beginCell().apply(builderAction)) } -public inline fun CellBuilder.storeRef(refBuilder: CellBuilder.() -> Unit): CellBuilder = apply { - val cellBuilder = CellBuilder.beginCell() - cellBuilder.apply(refBuilder) - val cell = cellBuilder.endCell() +@OptIn(ExperimentalContracts::class) +public inline fun CellBuilder.storeRef( + context: CellContext = CellContext.EMPTY, + block: CellBuilder.() -> Unit +): CellBuilder { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + val cell = context.finalizeCell(CellBuilder.beginCell().apply(block)) storeRef(cell) + return this } public inline fun CellBuilder(cell: Cell): CellBuilder = CellBuilder.of(cell) @@ -213,6 +228,12 @@ private class CellBuilderImpl( override fun storeBits(bits: Iterable): CellBuilder = storeBits(bits.toList()) + override fun storeByteString(value: ByteString): CellBuilder { + checkBitsOverflow(value.size * Byte.SIZE_BITS) + this.bits.plus(value.toByteArray()) + return this + } + override fun storeBytes(byteArray: ByteArray): CellBuilder = apply { checkBitsOverflow(byteArray.size * Byte.SIZE_BITS) this.bits.plus(byteArray) @@ -233,6 +254,17 @@ private class CellBuilderImpl( refs.add(ref) } + override fun storeNullableRef(ref: Cell?): CellBuilder { + if (ref != null) { + checkRefsOverflow(1) + storeBoolean(true) + refs.add(ref) + } else { + storeBoolean(false) + } + return this + } + override fun storeRefs(vararg refs: Cell): CellBuilder = apply { checkRefsOverflow(refs.size) this.refs.addAll(refs) diff --git a/tvm/src/cell/CellContext.kt b/tvm/src/cell/CellContext.kt index d715da85..17aedd6a 100644 --- a/tvm/src/cell/CellContext.kt +++ b/tvm/src/cell/CellContext.kt @@ -5,6 +5,7 @@ package org.ton.kotlin.cell import org.ton.cell.Cell import org.ton.cell.CellBuilder import org.ton.cell.DataCell +import org.ton.cell.VirtualCell public interface CellContext { public fun loadCell(cell: Cell): DataCell @@ -15,7 +16,8 @@ public interface CellContext { public val EMPTY: CellContext = object : CellContext { override fun loadCell(cell: Cell): DataCell { if (cell is DataCell) return cell - else throw IllegalArgumentException("Can't load $cell") + if (cell is VirtualCell && cell.cell is DataCell) return cell.cell + else throw IllegalArgumentException("Can't load ${cell::class} $cell") } override fun finalizeCell(builder: CellBuilder): Cell { diff --git a/tvm/src/cell/CellSlice.kt b/tvm/src/cell/CellSlice.kt index de70cc25..555e5b1f 100644 --- a/tvm/src/cell/CellSlice.kt +++ b/tvm/src/cell/CellSlice.kt @@ -2,12 +2,16 @@ package org.ton.cell +import kotlinx.io.bytestring.ByteString import org.ton.bigint.* import org.ton.bitstring.BitString import org.ton.bitstring.ByteBackedBitString import org.ton.bitstring.ByteBackedMutableBitString import org.ton.bitstring.exception.BitStringUnderflowException import org.ton.cell.exception.CellUnderflowException +import org.ton.kotlin.cell.CellContext +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.jvm.JvmStatic public inline fun CellSlice(bits: BitString, refs: List = emptyList()): CellSlice = CellSlice.of(bits, refs) @@ -18,7 +22,7 @@ public interface CellSlice { public var bitsPosition: Int public var refsPosition: Int public val remainingBits: Int get() = bits.size - bitsPosition - public val remainingRefs: Int get() = refs.size - bitsPosition + public val remainingRefs: Int get() = refs.size - refsPosition /** * Checks if slice is empty. If not, throws an exception. @@ -31,6 +35,8 @@ public interface CellSlice { public fun loadRef(): Cell public fun loadRefs(count: Int): List + public fun loadNullableRef(): Cell? + public fun preloadRef(): Cell public fun preloadRefs(count: Int): List public fun preloadRef(cellSlice: CellSlice.() -> T): T @@ -56,6 +62,12 @@ public interface CellSlice { public fun loadBitString(bitCount: Int): BitString public fun preloadBitString(bitCount: Int): BitString + public fun loadByteString(byteCount: Int): ByteString = ByteString(*loadByteArray(byteCount)) + public fun preloadByteString(byteCount: Int): ByteString = ByteString(*preloadByteArray(byteCount)) + + public fun loadByteArray(byteCount: Int): ByteArray + public fun preloadByteArray(byteCount: Int): ByteArray + public fun loadInt(length: Int): BigInt public fun preloadInt(length: Int): BigInt { val uint = preloadUInt(length) @@ -63,6 +75,9 @@ public interface CellSlice { return if (uint >= int) uint - (int * 2.toBigInt()) else uint } + public fun loadLong(bitCount: Int = Long.SIZE_BITS): Long = loadInt(bitCount).toLong() + public fun preloadLong(bitCount: Int = Long.SIZE_BITS): Long = preloadInt(bitCount).toLong() + public fun loadUInt(length: Int): BigInt public fun preloadUInt(length: Int): BigInt { if (length == 0) return 0.toBigInt() @@ -82,7 +97,11 @@ public interface CellSlice { public fun loadUInt8(): UByte = loadTinyInt(8).toUByte() public fun loadUInt16(): UShort = loadTinyInt(16).toUShort() public fun loadUInt32(): UInt = loadTinyInt(32).toUInt() - public fun loadUInt64(): ULong = loadTinyInt(64).toULong() + + @Deprecated("use loadULong() instead", ReplaceWith("loadULong()")) + public fun loadUInt64(): ULong = loadULong() + + public fun loadULong(bitCount: Int = ULong.SIZE_BITS): ULong = loadTinyInt(64).toULong() public fun loadTinyInt(length: Int): Long = loadInt(length).toLong() public fun preloadTinyInt(length: Int): Long = preloadInt(length).toLong() @@ -125,8 +144,12 @@ public interface CellSlice { public inline operator fun CellSlice.invoke(cellSlice: CellSlice.() -> T): T = let(cellSlice) -public inline fun CellSlice.loadRef(cellSlice: CellSlice.() -> T): T = - cellSlice(loadRef().beginParse()) +public inline fun CellSlice.loadRef(context: CellContext = CellContext.EMPTY, block: CellSlice.() -> T): T { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return block(context.loadCell(loadRef()).beginParse()) +} private class CellSliceByteBackedBitString( override val bits: ByteBackedBitString, @@ -176,23 +199,22 @@ private class CellSliceByteBackedBitString( } override fun preloadBitString(bitCount: Int): BitString { - val bytes = bitCount / 8 - val remainder = bitCount % 8 - val arraySize = bytes + if (remainder != 0) 1 else 0 - val array = ByteArray(arraySize) - if (bitsPosition % 8 == 0) { - val startIndex = bitsPosition / 8 - data.copyInto(array, startIndex = startIndex, endIndex = startIndex + bytes) - } else { - repeat(bytes) { i -> - array[i] = getByte(i * 8) - } - } - if (remainder != 0) { - val v = getBits(bytes * 8, remainder).toInt() shl (8 - remainder) - array[array.lastIndex] = v.toByte() - } - return BitString(array, bitCount) + val bits = ByteBackedMutableBitString(bitCount) + this.bits.copyInto(bits, 0, bitsPosition, bitsPosition + bitCount) + return bits + } + + override fun loadByteArray(byteCount: Int): ByteArray { + val result = preloadByteArray(byteCount) + bitsPosition += byteCount * 8 + return result + } + + override fun preloadByteArray(byteCount: Int): ByteArray { + val array = ByteArray(byteCount) + val bits = ByteBackedMutableBitString(array, byteCount * 8) + this.bits.copyInto(bits, 0, bitsPosition, bitsPosition + byteCount * 8) + return array } override fun preloadUInt(length: Int): BigInt { @@ -258,6 +280,14 @@ private class CellSliceByteBackedBitString( return cell } + override fun loadNullableRef(): Cell? { + return if (loadBoolean()) { + loadRef() + } else { + null + } + } + override fun loadRefs(count: Int): List = List(count) { loadRef() } override fun preloadRef(): Cell = refs[refsPosition]