Вот возможное решение.
В приведенном ниже коде TokenState
определяет стандартный токен с дополнительным полем issuingHash
.TokenContract
указывает, что в этом поле указан хэш транзакции, которая первоначально выдала токен после того, как токен был выпущен, но до его передачи.После установки issuingHash
невозможно изменить.
Например, предположим, что существует некоторая транзакция выдачи с хешем 7925679A6414AEBF69ED1A250E3E1E4452A4384529E3B690A4B47DD6A9918B93
, которая генерирует 1 000 000 токенов.TokenContract
принудительно устанавливает, что в следующей транзакции для этого токена установлено значение issuingHash
.
Теперь, если исходная транзакция выдачи является общедоступной, каждый может быть уверен, что когда-либо может быть только 1 000 000 токенов сэто issuingHash
в существовании.Нотариальный пул отклонит любые будущие попытки установить issuingHash
выходных данных транзакции выдачи в один и тот же хэш (это будет попытка двойного расходования), и никакие токены не могут быть переданы, пока не будет установлено issuingHash
.
Затем вы можете сказать, что готовы платить только токенами с issuingHash
7925679A6414AEBF69ED1A250E3E1E4452A4384529E3B690A4B47DD6A9918B93
, зная, что существует только 1 000 000.
data class TokenState(val owner: Party, val amount: Int, val issuingHash: SecureHash?) : ContractState {
override val participants: List<AbstractParty> = listOf()
}
interface TokenCommands : CommandData {
class Issue : TokenCommands
class SetIssuingHash : TokenCommands
class Transfer : TokenCommands
}
class TokenContract : Contract {
override fun verify(tx: LedgerTransaction) {
val tokenCommand = tx.commandsOfType<TokenCommands>().singleOrNull() ?: throw IllegalArgumentException()
val tokenInputs = tx.inputsOfType<TokenState>()
val tokenOutputs = tx.outputsOfType<TokenState>()
when (tokenCommand.value) {
is TokenCommands.Issue -> {
if (tokenInputs.isNotEmpty()) throw IllegalArgumentException()
val tokenOutput = tokenOutputs.singleOrNull() ?: throw IllegalArgumentException()
if (tokenOutput.issuingHash != null) throw IllegalArgumentException()
}
is TokenCommands.SetIssuingHash -> {
val tokenInput = tokenInputs.singleOrNull() ?: throw IllegalArgumentException()
val tokenOutput = tokenOutputs.singleOrNull() ?: throw IllegalArgumentException()
if (tokenInput.issuingHash != null) throw IllegalArgumentException()
if (tokenOutput.issuingHash != tx.inputs[0].ref.txhash) throw IllegalArgumentException()
if (tokenOutput.copy(issuingHash = null) != tokenInput) throw IllegalArgumentException()
}
// Extend this logic to allow tokens with different `issuingHash`s to be used in the same transaction.
is TokenCommands.Transfer -> {
if ((tokenInputs + tokenOutputs).any { it.issuingHash == null }) throw IllegalArgumentException()
if ((tokenInputs + tokenOutputs).map { it.issuingHash }.toSet().size != 1) throw IllegalArgumentException()
if (tokenInputs.sumBy { it.amount } != tokenOutputs.sumBy { it.amount }) throw IllegalArgumentException()
}
}
}
}