@@ -0,0 +1,132 @@
package org.ergoplatform.mining.emission

import org.ergoplatform.settings.MonetarySettings

import scala.annotation.tailrec

/**
* Ergo coin emission curve.
*
* Mainnet properties:
* 1000000000 nanoErgs (minimal non-divisible parts) in one Erg
* a block is coming every 2 minutes
* fixed rate 75 coins during first 2 years
* reward reduction for 3 coins every 3 month after that
* 19710000 coins after the first year
* 97739925 coins total
*
* @param settings - network settings
*/
class EmissionRules(val settings: MonetarySettings) {

lazy val (coinsTotal, blocksTotal) = {
@tailrec
def loop(height: Int, acc: Long): (Long, Int) = {
val currentRate = emissionAtHeight(height)
if (currentRate > 0) {
loop(height + 1, acc + currentRate)
} else {
(acc, height - 1)
}
}

loop(1, 0)
}

val foundersCoinsTotal: Long = remainingFoundationRewardAtHeight(0)
val minersCoinsTotal: Long = coinsTotal - foundersCoinsTotal

/**
* Returns number of coins issued at height `h` and before that
*/
def issuedCoinsAfterHeight(h: Long): Long = {
if (h < settings.fixedRatePeriod) {
settings.fixedRate * h
} else {
val fixedRateIssue: Long = settings.fixedRate * (settings.fixedRatePeriod - 1)
val epoch = (h - settings.fixedRatePeriod) / settings.epochLength
val fullEpochsIssued: Long = (1 to epoch.toInt).map { e =>
Math.max(settings.fixedRate - settings.oneEpochReduction * e, 0) * settings.epochLength
}.sum
val heightInThisEpoch = (h - settings.fixedRatePeriod) % settings.epochLength + 1
val rateThisEpoch = Math.max(settings.fixedRate - settings.oneEpochReduction * (epoch + 1), 0)
val thisEpochIssued = heightInThisEpoch * rateThisEpoch

fullEpochsIssued + fixedRateIssue + thisEpochIssued
}
}

/**
* Number not issued yet coins after height `h`
*/
def remainingCoinsAfterHeight(h: Long): Long = coinsTotal - issuedCoinsAfterHeight(h)

/**
* Number of coins to be issued at height `h`
*/
def emissionAtHeight(h: Long): Long = {
if (h < settings.fixedRatePeriod) {
settings.fixedRate
} else {
val epoch = 1 + (h - settings.fixedRatePeriod) / settings.epochLength
Math.max(settings.fixedRate - settings.oneEpochReduction * epoch, 0)
}
}.ensuring(_ >= 0, s"Negative at $h")

/**
* Returns number of coins issued at height `h` in favour of a miner
*/
def minersRewardAtHeight(h: Long): Long = {
if (h < settings.fixedRatePeriod + 2 * settings.epochLength) {
settings.fixedRate - settings.foundersInitialReward
} else {
val epoch = 1 + (h - settings.fixedRatePeriod) / settings.epochLength
Math.max(settings.fixedRate - settings.oneEpochReduction * epoch, 0)
}
}

/**
* Returns number of coins which should be kept in the foundation box at height `h`
*/
def remainingFoundationRewardAtHeight(h: Long): Long = {
val foundersInitialReward = settings.foundersInitialReward
val oneEpochReduction = settings.oneEpochReduction
val epochLength = settings.epochLength
val fixedRatePeriod = settings.fixedRatePeriod
val full15reward = (foundersInitialReward - 2 * oneEpochReduction) * epochLength
val full45reward = (foundersInitialReward - oneEpochReduction) * epochLength

if (h < fixedRatePeriod) {
full15reward + full45reward + (fixedRatePeriod - h - 1) * foundersInitialReward
} else if (h < fixedRatePeriod + epochLength) {
full15reward + (foundersInitialReward - oneEpochReduction) * (fixedRatePeriod + epochLength - h - 1)
} else if (h < fixedRatePeriod + (2 * epochLength)) {
(foundersInitialReward - 2 * oneEpochReduction) * (fixedRatePeriod + (2 * epochLength) - h - 1)
} else {
0
}
}

/**
* Returns number of coins issued at height `h` in favour of the foundation
*/
def foundationRewardAtHeight(h: Long): Long = {
if (h < settings.fixedRatePeriod) {
settings.foundersInitialReward
} else if (h < settings.fixedRatePeriod + settings.epochLength) {
settings.foundersInitialReward - settings.oneEpochReduction
} else if (h < settings.fixedRatePeriod + (2 * settings.epochLength)) {
settings.foundersInitialReward - 2 * settings.oneEpochReduction
} else {
0
}
}

}

object EmissionRules {

val CoinsInOneErgo: Long = 1000000000

}

@@ -0,0 +1,25 @@
package org.ergoplatform.settings

import org.ergoplatform.ErgoScriptPredef
import org.ergoplatform.mining.emission.EmissionRules
import sigmastate.Values.Value
import sigmastate.{SBoolean, Values}

/**
* Configuration file for monetary settings of Ergo chain
*
* @see src/main/resources/application.conf for parameters description
*/
case class MonetarySettings(fixedRatePeriod: Int = 30 * 2 * 24 * 365,
epochLength: Int = 90 * 24 * 30,
fixedRate: Long = 75L * EmissionRules.CoinsInOneErgo,
oneEpochReduction: Long = 3L * EmissionRules.CoinsInOneErgo,
minerRewardDelay: Int = 720,
foundersInitialReward: Long = 75L * EmissionRules.CoinsInOneErgo / 10) {

val feeProposition: Values.Value[SBoolean.type] = ErgoScriptPredef.feeProposition(minerRewardDelay)
val feePropositionBytes: Array[Byte] = feeProposition.bytes
val emissionBoxProposition: Value[SBoolean.type] = ErgoScriptPredef.emissionBoxProp(this)
val foundersBoxProposition: Value[SBoolean.type] = ErgoScriptPredef.foundationScript(this)

}
@@ -486,8 +486,9 @@ trait RuntimeCosting extends SigmaLibrary with DataCosting with Slicing { IR: Ev

case CostedBoxM.creationInfo(boxC) =>
val info = boxC.value.creationInfo
val l = RCCostedPrim(info._1, 0, 4L)
val r = mkCostedColl(info._2, 34, boxC.cost)
val cost = boxC.cost + sigmaDslBuilder.CostModel.SelectField
val l = RCCostedPrim(info._1, cost, 4L)
val r = mkCostedColl(info._2, 34, cost)
RCCostedPair(l, r)

case CostedOptionM.get(optC @ CostedBoxM.getReg(_, Def(Const(2)), regE)) /*if regId == ErgoBox.R2.asIndex*/ =>
@@ -1,19 +1,16 @@
package sigmastate.lang

import java.lang.reflect.InvocationTargetException
import java.math.BigInteger

import org.bitbucket.inkytonik.kiama.rewriting.Rewriter._
import sigmastate.lang.Terms._
import sigmastate._
import Values._
import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix
import org.ergoplatform._
import scorex.util.encode.Base58
import sigmastate.Values._
import sigmastate._
import sigmastate.interpreter.Interpreter.ScriptEnv
import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry
import sigmastate.lang.exceptions.{BinderException, InvalidArguments, InvalidTypeArguments}
import sigmastate.serialization.ValueSerializer
import sigmastate.lang.Terms._
import sigmastate.lang.exceptions.{BinderException, InvalidArguments}

/**
* @param env
@@ -24,7 +21,6 @@ class SigmaBinder(env: ScriptEnv, builder: SigmaBuilder,
networkPrefix: NetworkPrefix,
predefFuncRegistry: PredefinedFuncRegistry) {
import SigmaBinder._
import SigmaPredef._
import builder._

private val PKFunc = predefFuncRegistry.PKFunc(networkPrefix)
@@ -1,13 +1,13 @@
package sigmastate.lang

import sigmastate.SType
import sigmastate.lang.syntax.ParserException
import fastparse.core.Parsed
import fastparse.core.Parsed.Success
import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix
import sigmastate.Values.{SValue, SigmaTree, Value}
import sigmastate.SType
import sigmastate.Values.{SValue, Value}
import sigmastate.interpreter.Interpreter.ScriptEnv
import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry
import sigmastate.lang.syntax.ParserException

/**
* @param networkPrefix network prefix to decode an ergo address from string (PK op)
@@ -0,0 +1,59 @@
package org.ergoplatform

import org.ergoplatform.mining.emission.EmissionRules
import org.ergoplatform.settings.MonetarySettings
import org.scalacheck.Gen
import sigmastate.helpers.SigmaTestingCommons

class EmissionSpec extends SigmaTestingCommons {

private val settings = MonetarySettings(30 * 2 * 24 * 365, 90 * 24 * 30, 75L * EmissionRules.CoinsInOneErgo,
3L * EmissionRules.CoinsInOneErgo, 720, 75L * EmissionRules.CoinsInOneErgo / 10)
private val emission = new EmissionRules(settings)

def collectedFoundationReward(height: Int): Long = {
(1 to height).map { h =>
emission.foundationRewardAtHeight(h)
}.sum
}

property("emission rules vectors") {
emission.blocksTotal shouldBe 2080799
emission.coinsTotal shouldBe 97739925L * EmissionRules.CoinsInOneErgo
emission.foundersCoinsTotal shouldBe 4330792.5 * EmissionRules.CoinsInOneErgo
emission.minersCoinsTotal shouldBe 93409132.5 * EmissionRules.CoinsInOneErgo

emission.issuedCoinsAfterHeight(emission.blocksTotal) shouldBe emission.coinsTotal
emission.issuedCoinsAfterHeight(1) shouldBe settings.fixedRate
}

property("correct sum from miner and foundation parts") {
// collect coins after the fixed rate period
forAll(Gen.choose(1, emission.blocksTotal)) { height =>
val currentRate = emission.emissionAtHeight(height)
val minerPart = emission.minersRewardAtHeight(height)
val foundationPart = emission.foundationRewardAtHeight(height)
foundationPart + minerPart shouldBe currentRate
}
}

property("correct remainingFoundationRewardAtHeight") {
val totalFoundersReward = collectedFoundationReward(emission.blocksTotal)

def checkHeight(height: Int) = {
val collectedFoundersPart = collectedFoundationReward(height)
val remainingFoundersPart = emission.remainingFoundationRewardAtHeight(height)
remainingFoundersPart + collectedFoundersPart shouldBe totalFoundersReward

}
// collect coins after the fixed rate period
forAll(Gen.choose(1, emission.blocksTotal)) { height =>
checkHeight(height)
}
checkHeight(settings.fixedRatePeriod)
checkHeight(settings.fixedRatePeriod + settings.epochLength)
checkHeight(settings.fixedRatePeriod + 2 * settings.epochLength)
checkHeight(settings.fixedRatePeriod + 8 * settings.epochLength)
}

}
@@ -1,21 +1,200 @@
package org.ergoplatform

import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix
import org.ergoplatform.ErgoBox.R4
import org.ergoplatform.mining.emission.EmissionRules
import org.ergoplatform.settings.MonetarySettings
import org.scalacheck.Gen
import scorex.crypto.hash.{Blake2b256, Digest32}
import sigmastate.AvlTreeData
import scorex.util.Random
import sigmastate.Values.{ByteArrayConstant, CollectionConstant, IntConstant, SigmaPropConstant, Value}
import sigmastate._
import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog}
import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons}
import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv}
import sigmastate.interpreter.{ContextExtension, ProverResult}
import sigmastate.lang.Terms.ValueOps
import sigmastate.serialization.ValueSerializer
import sigmastate.utxo.{ByIndex, ExtractCreationInfo, SelectField}
import sigmastate.utxo.ErgoLikeTestInterpreter
import scalan.util.BenchmarkUtil._

import scala.util.Try

class ErgoScriptPredefSpec extends SigmaTestingCommons {
implicit lazy val IR = new TestingIRContext {
private implicit lazy val IR: TestingIRContext = new TestingIRContext {
override val okPrintEvaluatedEntries: Boolean = false
}
val emptyProverResult: ProverResult = ProverResult(Array.emptyByteArray, ContextExtension.empty)

private val emptyProverResult: ProverResult = ProverResult(Array.emptyByteArray, ContextExtension.empty)
private val settings = MonetarySettings(30 * 2 * 24 * 365, 90 * 24 * 30, 75L * EmissionRules.CoinsInOneErgo,
3L * EmissionRules.CoinsInOneErgo, 720, 75L * EmissionRules.CoinsInOneErgo / 10)
private val emission = new EmissionRules(settings)

property("boxCreationHeight") {
val verifier = new ErgoLikeTestInterpreter
val prover = new ErgoLikeTestProvingInterpreter
val minerProp = prover.dlogSecrets.head.publicImage
val pk = minerProp.pkBytes

val nextHeight = 1
val prop = EQ(Height, ErgoScriptPredef.boxCreationHeight(ByIndex(Outputs, IntConstant(0))))
val propInlined = EQ(Height, SelectField(ExtractCreationInfo(ByIndex(Outputs, IntConstant(0))), 1).asIntValue)
prop shouldBe propInlined
val inputBox = ErgoBox(1, prop, nextHeight, Seq(), Map())
val inputBoxes = IndexedSeq(inputBox)
val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult))
val minerBox = new ErgoBoxCandidate(1, minerProp, nextHeight, Seq(), Map())

val spendingTransaction = ErgoLikeTransaction(inputs, IndexedSeq(minerBox))

val ctx = ErgoLikeContext(
currentHeight = nextHeight,
lastBlockUtxoRoot = AvlTreeData.dummy,
minerPubkey = pk,
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBox)
val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get
verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx, pr, fakeMessage).get._1 shouldBe true
}

property("collect coins from the founders' box") {
def remaining(h: Int) = emission.remainingFoundationRewardAtHeight(h)

val prover = new ErgoLikeTestProvingInterpreter
val prop = ErgoScriptPredef.foundationScript(settings)

def R4Prop(ableToProve: Boolean): CollectionConstant[SByte.type] = if (ableToProve) {
val pks = (DLogProverInput.random() +: prover.dlogSecrets.take(2)).map(s => SigmaPropConstant(s.publicImage))
ByteArrayConstant(ValueSerializer.serialize(AtLeast(IntConstant(2), pks).isProven))
} else {
ByteArrayConstant(ValueSerializer.serialize((new ErgoLikeTestProvingInterpreter).dlogSecrets.head.publicImage))
}

val verifier = new ErgoLikeTestInterpreter

checkAtHeight(1)
checkAtHeight(settings.fixedRatePeriod)
checkAtHeight(settings.fixedRatePeriod + 1)
checkAtHeight(settings.fixedRatePeriod + settings.epochLength)
checkAtHeight(settings.fixedRatePeriod + settings.epochLength + 1)
checkAtHeight(settings.fixedRatePeriod + 2 * settings.epochLength)
checkAtHeight(settings.fixedRatePeriod + 2 * settings.epochLength + 1)

def checkAtHeight(height: Int) = {
// collect correct amount of coins, correct new script, able to satisfy R4 conditions
checkSpending(remaining(height), height, prop, R4Prop(true)) shouldBe 'success
// unable to satisfy R4 conditions
checkSpending(remaining(height), height, prop, R4Prop(false)) shouldBe 'failure
// incorrect new script
checkSpending(remaining(height), height, Values.TrueLeaf, R4Prop(true)) shouldBe 'failure
// collect less coins then possible
checkSpending(remaining(height) + 1, height, prop, R4Prop(true)) shouldBe 'success
// collect more coins then possible
checkSpending(remaining(height) - 1, height, prop, R4Prop(true)) shouldBe 'failure
}

def checkSpending(remainingAmount: Long,
height: Int,
newProp: Value[SBoolean.type],
inputR4Val: CollectionConstant[SByte.type]): Try[Unit] = Try {
val outputR4Val: CollectionConstant[SByte.type] = ByteArrayConstant(Random.randomBytes())
val inputBoxes = IndexedSeq(ErgoBox(emission.foundersCoinsTotal, prop, 0, Seq(), Map(R4 -> inputR4Val)))
val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult))
val newFoundersBox = ErgoBox(remainingAmount, newProp, 0, Seq(), Map(R4 -> outputR4Val))
val collectedBox = ErgoBox(inputBoxes.head.value - remainingAmount, Values.TrueLeaf, 0)
val spendingTransaction = ErgoLikeTransaction(inputs, IndexedSeq(newFoundersBox, collectedBox))
val ctx = ErgoLikeContext(
currentHeight = height,
lastBlockUtxoRoot = AvlTreeData.dummy,
minerPubkey = ErgoLikeContext.dummyPubkey,
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBoxes.head)
val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get
verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx, pr, fakeMessage).get._1 shouldBe true
}
}

property("collect coins from rewardOutputScript") {
val prover = new ErgoLikeTestProvingInterpreter
val minerPk = prover.dlogSecrets.head.publicImage
val prop = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk)
val verifier = new ErgoLikeTestInterpreter
val inputBoxes = IndexedSeq(ErgoBox(20, prop, 0, Seq(), Map()))
val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult))
val spendingTransaction = ErgoLikeTransaction(inputs, IndexedSeq(ErgoBox(inputBoxes.head.value, Values.TrueLeaf, 0)))

val ctx = ErgoLikeContext(
currentHeight = inputBoxes.head.creationHeight + settings.minerRewardDelay,
lastBlockUtxoRoot = AvlTreeData.dummy,
minerPubkey = ErgoLikeContext.dummyPubkey,
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBoxes.head)
val prevBlockCtx = ErgoLikeContext(
currentHeight = inputBoxes.head.creationHeight + settings.minerRewardDelay - 1,
lastBlockUtxoRoot = AvlTreeData.dummy,
minerPubkey = ErgoLikeContext.dummyPubkey,
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBoxes.head)

// should not be able to collect before minerRewardDelay
val prove = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get
verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, prevBlockCtx, prove, fakeMessage) shouldBe 'failure

// should be able to collect after minerRewardDelay
val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get
verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx, pr, fakeMessage).get._1 shouldBe true
}

property("create transaction collecting the emission box") {
val prover = new ErgoLikeTestProvingInterpreter
val minerPk = prover.dlogSecrets.head.publicImage
val prop = ErgoScriptPredef.emissionBoxProp(settings)
val emissionBox = ErgoBox(emission.coinsTotal, prop, 0, Seq(), Map())
val minerProp = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk)

// collect coins during the fixed rate period
forAll(Gen.choose(1, settings.fixedRatePeriod)) { height =>
val currentRate = emission.minersRewardAtHeight(height)
createRewardTx(currentRate, height, minerProp) shouldBe 'success
createRewardTx(currentRate + 1, height, minerProp) shouldBe 'failure
createRewardTx(currentRate - 1, height, minerProp) shouldBe 'failure
}

// collect coins after the fixed rate period
forAll(Gen.choose(1, emission.blocksTotal - 1)) { height =>
val currentRate = emission.minersRewardAtHeight(height)
createRewardTx(currentRate, height, minerProp) shouldBe 'success
createRewardTx(currentRate + 1, height, minerProp) shouldBe 'failure
createRewardTx(currentRate - 1, height, minerProp) shouldBe 'failure
}

// collect coins to incorrect proposition
forAll(Gen.choose(1, emission.blocksTotal - 1)) { height =>
val currentRate = emission.minersRewardAtHeight(height)
val pk2 = prover.dlogSecrets(1).publicImage
val correctProp = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk)
val incorrectDelay = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay + 1, minerPk)
val incorrectPk = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, pk2)
createRewardTx(currentRate, height, correctProp) shouldBe 'success
createRewardTx(currentRate, height, incorrectDelay) shouldBe 'failure
createRewardTx(currentRate, height, incorrectPk) shouldBe 'failure
createRewardTx(currentRate, height, minerPk) shouldBe 'failure
}

def createRewardTx(emissionAmount: Long, nextHeight: Int, minerProp: Value[SBoolean.type]): Try[ErgoLikeTransaction] = {
checkRewardTx(minerPk: ProveDlog,
minerProp: Value[SBoolean.type],
emissionBox: ErgoBox,
emissionAmount: Long,
nextHeight: Int)(prover)
}

}

property("tokenThreshold") {
val prover = new ErgoLikeTestProvingInterpreter
@@ -76,6 +255,14 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons {
ErgoBox(20, prop, 0, Seq((tokenId, tokenAmount / 2 + 1), (wrongId2, 1)), Map())
)
check(inputs3) shouldBe 'success

// A transaction which contains input with no tokens
val inputs4 = IndexedSeq(
ErgoBox(20, prop, 0, Seq((wrongId, 1), (tokenId, tokenAmount / 2)), Map()),
ErgoBox(20, prop, 0, Seq(), Map()),
ErgoBox(20, prop, 0, Seq((tokenId, tokenAmount / 2 + 1), (wrongId2, 1)), Map())
)
check(inputs4) shouldBe 'success
}
/*
Iter 0: 777 ms
@@ -88,4 +275,33 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons {
*/
}

def checkRewardTx(minerPk: ProveDlog,
minerProp: Value[SBoolean.type],
emissionBox: ErgoBox,
emissionAmount: Long,
nextHeight: Int)(prover: ErgoLikeTestProvingInterpreter): Try[ErgoLikeTransaction] = Try {
val verifier = new ErgoLikeTestInterpreter
val prop = emissionBox.proposition
val inputBoxes = IndexedSeq(emissionBox)
val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult))
val pkBytes = minerPk.pkBytes

val newEmissionBox: ErgoBoxCandidate = new ErgoBoxCandidate(emissionBox.value - emissionAmount, prop,
nextHeight, Seq(), Map())
val minerBox = new ErgoBoxCandidate(emissionAmount, minerProp, nextHeight, Seq(), Map())

val spendingTransaction = ErgoLikeTransaction(inputs, IndexedSeq(newEmissionBox, minerBox))

val ctx = ErgoLikeContext(
currentHeight = nextHeight,
lastBlockUtxoRoot = AvlTreeData.dummy,
minerPubkey = pkBytes,
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBoxes.head)
val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get
verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx, pr, fakeMessage).get._1 shouldBe true
spendingTransaction
}

}
@@ -161,16 +161,6 @@ class CostingTest extends BaseCtxTests with LangTests with ExampleContracts with
{ ctx => val x = IF (ctx.OUTPUTS.length > 0) THEN ctx.OUTPUTS(0).value ELSE ctx.SELF.value; x })
}


test("substConstants") {
import org.ergoplatform.ErgoScriptPredef._
val minerRewardDelay = 720
val prop = rewardOutputScriptForCurrentMiner(minerRewardDelay)
val costed = cost(env, prop)
val res @ Tuple(calcF, costF, sizeF) = split3(costed.asRep[Context => Costed[Any]])
emit("substConstants", calcF, costF, sizeF)
}

test("Crowd Funding") {
val prover = new ErgoLikeTestProvingInterpreter()
val backerPK @ DLogProtocol.ProveDlog(GroupElementConstant(backer: ECPoint)) = prover.dlogSecrets(0).publicImage
@@ -1,27 +1,26 @@
package sigmastate.utxo.examples

import org.ergoplatform.{ErgoLikeContext, Height, _}
import org.ergoplatform._
import scorex.util.ScorexLogging
import sigmastate.Values.{IntConstant, LongConstant, SValue}
import sigmastate.Values.{ConcreteCollection, IntConstant, LongConstant}
import sigmastate.eval.RuntimeIRContext
import sigmastate.Values.IntConstant
import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons}
import sigmastate.interpreter.ContextExtension
import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp, emptyEnv}
import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv}
import sigmastate.lang.Terms._
import sigmastate.serialization.{ErgoTreeSerializer, OpCodes}
import sigmastate.utxo.BlockchainSimulationSpecification.{Block, ValidationState}
import sigmastate.utxo._
import sigmastate.{SLong, _}
import sigmastate._

/**
* Coin emission specification.
* Instead of having implicit emission via coinbase transaction, we implement 1 output in a state with script,
* that controls emission rules
* An example of currency emission contract.
* Instead of having implicit emission via coinbase transaction, we put 1 coin into genesis state with a script
* that controls emission.
* This script is corresponding to the whitepaper. Please note that Ergo has different contract
* defined in ErgoScriptPredef.
*/
class CoinEmissionSpecification extends SigmaTestingCommons with ScorexLogging {
// don't use TestingIRContext, this suite also serves the purpose of testing the RuntimeIRContext
implicit lazy val IR = new TestingIRContext {
implicit lazy val IR: TestingIRContext = new TestingIRContext {
// override val okPrintEvaluatedEntries = true
}

@@ -60,33 +59,30 @@ class CoinEmissionSpecification extends SigmaTestingCommons with ScorexLogging {
}.ensuring(_ >= 0, s"Negative at $h")


ignore("emission specification") {
property("emission specification") {
val register = reg1
val prover = new ErgoLikeTestProvingInterpreter()

val rewardOut = ByIndex(Outputs, IntConstant(0))
val minerOut = ByIndex(Outputs, IntConstant(1))

val epoch = Plus(IntConstant(1), Divide(Minus(Height, IntConstant(s.fixedRatePeriod)), IntConstant(s.epochLength)))
val epoch =
Upcast(
Plus(IntConstant(1), Divide(Minus(Height, IntConstant(s.fixedRatePeriod)), IntConstant(s.epochLength))),
SLong)

val coinsToIssue = If(LT(Height, IntConstant(s.fixedRatePeriod)),
s.fixedRate,
Minus(s.fixedRate, Multiply(s.oneEpochReduction, Upcast(epoch, SLong)))
Minus(s.fixedRate, Multiply(s.oneEpochReduction, epoch))
)
val sameScriptRule = EQ(ExtractScriptBytes(Self), ExtractScriptBytes(rewardOut))
val heightCorrect = EQ(ExtractRegisterAs[SInt.type](rewardOut, register).get, Height)
val heightIncreased = GT(Height, ExtractRegisterAs[SInt.type](Self, register).get)
val correctCoinsConsumed = EQ(coinsToIssue, Minus(ExtractAmount(Self), ExtractAmount(rewardOut)))
val lastCoins = LE(ExtractAmount(Self), s.oneEpochReduction)
val outputsNum = EQ(SizeOf(Outputs), 2)
val correctMinerProposition = EQ(
ExtractScriptBytes(minerOut),
ErgoTreeSerializer.DefaultSerializer.serializedPubkeyPropValue(MinerPubkey)
)

val prop = AND(
heightIncreased,
correctMinerProposition,
BinOr(AND(outputsNum, sameScriptRule, correctCoinsConsumed, heightCorrect), lastCoins)
val prop = BinOr(
AND(heightIncreased, sameScriptRule, correctCoinsConsumed, heightCorrect),
BinAnd(heightIncreased, lastCoins)
)

val env = Map("fixedRatePeriod" -> s.fixedRatePeriod,
@@ -98,17 +94,13 @@ class CoinEmissionSpecification extends SigmaTestingCommons with ScorexLogging {
"""{
| val epoch = 1 + ((HEIGHT - fixedRatePeriod) / epochLength)
| val out = OUTPUTS(0)
| val minerOut = OUTPUTS(1)
| val coinsToIssue = if(HEIGHT < fixedRatePeriod) fixedRate else fixedRate - (oneEpochReduction * epoch.toLong)
| val coinsToIssue = if(HEIGHT < fixedRatePeriod) fixedRate else fixedRate - (oneEpochReduction * epoch)
| val correctCoinsConsumed = coinsToIssue == (SELF.value - out.value)
| val sameScriptRule = SELF.propositionBytes == out.propositionBytes
| val heightIncreased = HEIGHT > SELF.R4[Int].get
| val heightCorrect = out.R4[Int].get == HEIGHT
| val lastCoins = SELF.value <= oneEpochReduction
| val outputsNum = OUTPUTS.size == 2
| val correctMinerProposition = minerOut.propositionBytes ==
| Coll[Byte](0.toByte, 1.toByte, 7.toByte) ++ MinerPubkey ++ Coll[Byte](-51.toByte, 115.toByte, 0.toByte)
| allOf(Coll(heightIncreased, correctMinerProposition, allOf(Coll(outputsNum, sameScriptRule, correctCoinsConsumed, heightCorrect)) || lastCoins))
| allOf(Coll(heightIncreased, sameScriptRule, correctCoinsConsumed, heightCorrect)) || (heightIncreased && lastCoins)
|}""".stripMargin).asBoolValue

prop1 shouldEqual prop
@@ -130,7 +122,7 @@ class CoinEmissionSpecification extends SigmaTestingCommons with ScorexLogging {
val genesisState = ValidationState.initialState(initBlock)
val fromState = genesisState.boxesReader.byId(genesisState.boxesReader.allIds.head).get
val initialBox = ErgoBox(initialBoxCandidate.value, initialBoxCandidate.proposition, 0,
initialBoxCandidate.additionalTokens, initialBoxCandidate.additionalRegisters, initBlock.txs.head.id, 0)
initialBoxCandidate.additionalTokens, initialBoxCandidate.additionalRegisters, initBlock.txs.head.id)
initialBox shouldBe fromState

def genCoinbaseLikeTransaction(state: ValidationState,