diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index ce7944681a..83af11ec34 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -214,92 +214,101 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], outputCandidates: Seq[ErgoBoxCandidate], stateContext: ErgoStateContext): Try[Unit] = { Try { - lazy val reemissionSettings = stateContext.ergoSettings.chainSettings.reemission - lazy val reemissionRules = reemissionSettings.reemissionRules - - lazy val reemissionTokenId = ModifierId @@ reemissionSettings.reemissionTokenId - lazy val reemissionTokenIdBytes = reemissionSettings.reemissionTokenIdBytes - - lazy val emissionNftId = ModifierId @@ reemissionSettings.emissionNftId - lazy val emissionNftIdBytes = reemissionSettings.emissionNftIdBytes - - lazy val chainSettings = stateContext.ergoSettings.chainSettings - lazy val emissionRules = chainSettings.emissionRules - - lazy val height = stateContext.currentHeight - lazy val eip27Supported = stateContext.eip27Supported - lazy val activationHeight = if (eip27Supported) { - reemissionSettings.activationHeight - } else { - Int.MaxValue - } + // we check that we're in utxo mode, as eip27Supported flag available only in this mode + // if we're in digest mode, skip validation + // todo: this check could be removed after EIP-27 activation + if (stateContext.ergoSettings.nodeSettings.stateType.holdsUtxoSet) { + lazy val reemissionSettings = stateContext.ergoSettings.chainSettings.reemission + lazy val reemissionRules = reemissionSettings.reemissionRules + + lazy val reemissionTokenId = ModifierId @@ reemissionSettings.reemissionTokenId + lazy val reemissionTokenIdBytes = reemissionSettings.reemissionTokenIdBytes + + lazy val emissionNftId = ModifierId @@ reemissionSettings.emissionNftId + lazy val emissionNftIdBytes = reemissionSettings.emissionNftIdBytes + + lazy val chainSettings = stateContext.ergoSettings.chainSettings + lazy val emissionRules = chainSettings.emissionRules + + lazy val height = stateContext.currentHeight + lazy val eip27Supported = stateContext.eip27Supported + + // considering voting for eip27 done, via eip27Supported flag + val activationHeight = if (eip27Supported) { + reemissionSettings.activationHeight + } else { + Int.MaxValue + } - if (stateContext.currentHeight >= activationHeight) { - // reemission check logic below - var reemissionSpending = false - boxesToSpend.foreach { box => - // checking EIP-27 rules for emission box - if (box.value > 100000 * EmissionRules.CoinsInOneErgo) { // for efficiency, skip boxes with less than 100,000 ERG - // on activation height, emissionNft is not in emission box yet, but in injection box - if (box.tokens.contains(emissionNftId) || - (height == activationHeight && boxesToSpend(1).tokens.contains(emissionNftId))) { - - // if emission contract NFT is in the input, remission tokens should be there also - val reemissionTokensIn = if (height == activationHeight) { - boxesToSpend(1).tokens.getOrElse(reemissionTokenId, 0L) + if (stateContext.currentHeight >= activationHeight) { + // reemission check logic below + var reemissionSpending = false + boxesToSpend.foreach { box => + // checking EIP-27 rules for emission box + if (box.value > 100000 * EmissionRules.CoinsInOneErgo) { // for efficiency, skip boxes with less than 100,000 ERG + // on activation height, emissionNft is not in emission box yet, but in injection box + if (box.tokens.contains(emissionNftId) || + (height == activationHeight && boxesToSpend(1).tokens.contains(emissionNftId))) { + + // if emission contract NFT is in the input, remission tokens should be there also + val reemissionTokensIn = if (height == activationHeight) { + boxesToSpend(1).tokens.getOrElse(reemissionTokenId, 0L) + } else { + box.tokens.getOrElse(reemissionTokenId, 0L) + } + require(reemissionTokensIn > 0) + + // output positions guaranteed by emission contract + val emissionOut = outputCandidates(0) + val rewardsOut = outputCandidates(1) + + // check positions of emission NFT and reemission token + val firstEmissionBoxTokenId = emissionOut.additionalTokens.apply(0)._1 + val secondEmissionBoxTokenId = emissionOut.additionalTokens.apply(1)._1 + require(firstEmissionBoxTokenId.sameElements(emissionNftIdBytes)) + require(secondEmissionBoxTokenId.sameElements(reemissionTokenIdBytes)) + + //we're checking how emission box is paying reemission tokens below + val emissionTokensOut = emissionOut.tokens.getOrElse(reemissionTokenId, 0L) + val rewardsTokensOut = rewardsOut.tokens.getOrElse(reemissionTokenId, 0L) + require(reemissionTokensIn == emissionTokensOut + rewardsTokensOut, "Reemission token not preserved") + + val properReemissionRewardPart = reemissionRules.reemissionForHeight(height, emissionRules) + require(rewardsTokensOut == properReemissionRewardPart, "Rewards out condition violated") } else { - box.tokens.getOrElse(reemissionTokenId, 0L) - } - require(reemissionTokensIn > 0) - - // output positions guaranteed by emission contract - val emissionOut = outputCandidates(0) - val rewardsOut = outputCandidates(1) - - // check positions of emission NFT and reemission token - val firstEmissionBoxTokenId = emissionOut.additionalTokens.apply(0)._1 - val secondEmissionBoxTokenId = emissionOut.additionalTokens.apply(1)._1 - require(firstEmissionBoxTokenId.sameElements(emissionNftIdBytes)) - require(secondEmissionBoxTokenId.sameElements(reemissionTokenIdBytes)) - - //we're checking how emission box is paying reemission tokens below - val emissionTokensOut = emissionOut.tokens.getOrElse(reemissionTokenId, 0L) - val rewardsTokensOut = rewardsOut.tokens.getOrElse(reemissionTokenId, 0L) - require(reemissionTokensIn == emissionTokensOut + rewardsTokensOut, "Reemission token not preserved") - - val properReemissionRewardPart = reemissionRules.reemissionForHeight(height, emissionRules) - require(rewardsTokensOut == properReemissionRewardPart, "Rewards out condition violated") - } else { - //this path can be removed after EIP-27 activation - if (height >= activationHeight && box.ergoTree == chainSettings.monetary.emissionBoxProposition) { - //we require emission contract NFT and reemission token to be presented in emission output - val emissionOutTokens = outputCandidates(0).tokens - require(emissionOutTokens.contains(emissionNftId)) - require(emissionOutTokens.contains(reemissionTokenId)) + //this path can be removed after EIP-27 activation + if (height >= activationHeight && box.ergoTree == chainSettings.monetary.emissionBoxProposition) { + //we require emission contract NFT and reemission token to be presented in emission output + val emissionOutTokens = outputCandidates(0).tokens + require(emissionOutTokens.contains(emissionNftId)) + require(emissionOutTokens.contains(reemissionTokenId)) + } } + } else if (box.tokens.contains(reemissionTokenId) && height > activationHeight) { + // reemission tokens spent after EIP-27 activation + reemissionSpending = true } - } else if (box.tokens.contains(reemissionTokenId) && height > activationHeight) { - // reemission tokens spent after EIP-27 activation - reemissionSpending = true } - } - // if box with reemission tokens spent - if (reemissionSpending) { - val payToReemissionContract = reemissionRules.payToReemission - val toBurn = boxesToSpend.map { box => - box.tokens.getOrElse(reemissionTokenId, 0L) - }.sum - log.debug(s"Reemission tokens to burn: $toBurn") - val reemissionOutputs = outputCandidates.filter { out => - require(!out.tokens.contains(reemissionTokenId), "outputs contain reemission token") - out.ergoTree == payToReemissionContract + // if box with reemission tokens spent + if (reemissionSpending) { + val payToReemissionContract = reemissionRules.payToReemission + val toBurn = boxesToSpend.map { box => + box.tokens.getOrElse(reemissionTokenId, 0L) + }.sum + log.debug(s"Reemission tokens to burn: $toBurn") + val reemissionOutputs = outputCandidates.filter { out => + require(!out.tokens.contains(reemissionTokenId), "outputs contain reemission token") + out.ergoTree == payToReemissionContract + } + val sentToReemission = reemissionOutputs.map(_.value).sum + require(sentToReemission == toBurn, "Burning condition violated") } - val sentToReemission = reemissionOutputs.map(_.value).sum - require(sentToReemission == toBurn, "Burning condition violated") + } else { + Success(()) } } else { - Success(()) + log.warn("Checking EIP-27 win digest mode") } } } diff --git a/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala b/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala index e8256f46b7..3b8d899184 100644 --- a/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala +++ b/src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala @@ -12,13 +12,23 @@ import sigmastate.lang.{CompilerSettings, SigmaCompiler, TransformingSigmaBuilde import scala.util.Try +/** + * Contains re-emission contracts (defined in `ReemissionContracts`) and helper functions + * for re-emission. + */ class ReemissionRules(reemissionSettings: ReemissionSettings) extends ReemissionContracts { override val reemissionNftIdBytes: Array[Byte] = reemissionSettings.reemissionNftIdBytes override val reemissionStartHeight: Height = reemissionSettings.reemissionStartHeight + /** + * How many ERG taken from emission to re-emission initially + */ val basicChargeAmount = 12 // in ERG + /** + * @return how many re-emission tokens can be unlocked at given height + */ def reemissionForHeight(height: Height, emissionRules: EmissionRules): Long = { val emission = emissionRules.emissionAtHeight(height) @@ -36,6 +46,11 @@ class ReemissionRules(reemissionSettings: ReemissionSettings) extends Reemission object ReemissionRules { + /** + * @param mainnet - whether to create address for mainnet or testnet + * @return - P2S address for a box used to carry emission NFT and re-emission tokens + * to inject them into the emission box on activation height + */ def injectionBoxP2SAddress(mainnet: Boolean): Pay2SAddress = { val networkPrefix = if (mainnet) { ErgoAddressEncoder.MainnetNetworkPrefix