Skip to content

Commit

Permalink
checking EIP-27 rules only in UTXO mode
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Apr 25, 2022
1 parent f2d3371 commit db6f718
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/main/scala/org/ergoplatform/reemission/ReemissionRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit db6f718

Please sign in to comment.