Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token burning #61

Merged
merged 2 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package org.ergoplatform.appkit

import org.ergoplatform.{ErgoAddressEncoder, Pay2SAddress}
import org.scalatest.{Matchers, PropSpec}
import org.scalatest.{PropSpec, Matchers}
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
import scorex.util.encode.Base16
import sigmastate.eval._
import sigmastate.interpreter.CryptoConstants
import sigmastate.serialization.ErgoTreeSerializer
import special.sigma.GroupElement
import JavaHelpers._
import java.util.{List => JList}

import org.ergoplatform.appkit.Parameters.MinFee

class ChangeOutputSpec extends PropSpec with Matchers
with ScalaCheckDrivenPropertyChecks
Expand Down Expand Up @@ -97,7 +98,7 @@ class ChangeOutputSpec extends PropSpec with Matchers
}
}

property("NoTokenChangeOutput") {
property("NoTokenChangeOutput + token burning") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
val g: GroupElement = CryptoConstants.dlogGroup.generator
val x = BigInt("187235612876647164378132684712638457631278").bigInteger
Expand All @@ -120,6 +121,8 @@ class ChangeOutputSpec extends PropSpec with Matchers
)).build().convertToInputWith("f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809", 0)

val tokenId = input0.getId.toString
val tokenAmount = 5000000000L
val tokenAmountToBurn = 2000000000L

val ph = ctx.createPreHeader()
.height(ctx.getHeight + 1)
Expand All @@ -128,7 +131,7 @@ class ChangeOutputSpec extends PropSpec with Matchers
val txB = ctx.newTxBuilder().preHeader(ph) // for issuing token
val tokenBox = txB.outBoxBuilder
.value(15000000) // value of token box, doesn't really matter
.tokens(new ErgoToken(tokenId, 5000000000L)) // amount of token issuing
.tokens(new ErgoToken(tokenId, tokenAmount)) // amount of token issuing
.contract(ctx.compileContract( // contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(),
"{sigmaProp(1 < 2)}"
Expand All @@ -148,6 +151,43 @@ class ChangeOutputSpec extends PropSpec with Matchers
val outputs = signed.getOutputsToSpend
assert(outputs.size == 2)
println(signed.toJson(false))
val boxWithToken = outputs.get(0)
assert(boxWithToken.getTokens.size() == 1)

// move Ergs from boxWithToken and burn tokens in the transaction
{
val expectedChange = MinFee
val txB = ctx.newTxBuilder()
val ergAmountToSend = boxWithToken.getValue - MinFee - expectedChange
val out = txB.outBoxBuilder
.value(ergAmountToSend)
.contract(ctx.compileContract( // contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(), "{sigmaProp(1 < 2)}"
))
.build()
val unsigned = txB
.boxesToSpend(IndexedSeq(boxWithToken).convertTo[JList[InputBox]])
.outputs(out)
.tokensToBurn(new ErgoToken(tokenId, tokenAmountToBurn))
.fee(MinFee)
.sendChangeTo(changeAddr)
.build()
val signed = ctx.newProverBuilder().build().sign(unsigned)
val outputs = signed.getOutputsToSpend
assert(outputs.size == 3)
val out0 = outputs.get(0)
val fee = outputs.get(1)
val change = outputs.get(2)
out0.getValue shouldBe ergAmountToSend
out0.getTokens.size() shouldBe 0

fee.getValue shouldBe MinFee
fee.getTokens.size shouldBe 0

change.getValue shouldBe expectedChange
change.getTokens.size() shouldBe 1
change.getTokens.get(0).getValue shouldBe (tokenAmount - tokenAmountToBurn)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ assemblyMergeStrategy in assembly := {
lazy val allConfigDependency = "compile->compile;test->test"

val sigmaStateVersion = "3.3.1"
val ergoWalletVersion = "v3.2.3-11f8615f-SNAPSHOT"
val ergoWalletVersion = "v3.3.3-c42d8b5b-SNAPSHOT"
lazy val sigmaState = ("org.scorexfoundation" %% "sigma-state" % sigmaStateVersion).force()
.exclude("ch.qos.logback", "logback-classic")
.exclude("org.scorexfoundation", "scrypto")
Expand Down
33 changes: 19 additions & 14 deletions common/src/main/java/org/ergoplatform/appkit/JavaHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import java.util.{List => JList, Map => JMap}

import sigmastate.utils.Helpers._ // don't remove, required for Scala 2.11
import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix
import org.ergoplatform.wallet.TokensMap
import scorex.util.encode.Base16
import sigmastate.basics.DLogProtocol.ProveDlog
import sigmastate.basics.{ProveDHTuple, DiffieHellmanTupleProverInput}
Expand Down Expand Up @@ -79,22 +80,23 @@ object Iso extends LowPriorityIsos {
override def from(t: (TokenId, Long)): ErgoToken = new ErgoToken(t._1, t._2)
}

implicit val isoJListErgoTokenToMapPair: Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] =
implicit val isoJListErgoTokenToMapPair: Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] =
new Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] {
override def to(a: JList[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = {
import JavaHelpers._
val lhm = new mutable.LinkedHashMap[ModifierId, Long]()
a.convertTo[IndexedSeq[(TokenId, Long)]]
.map(t => bytesToId(t._1) -> t._2)
.foldLeft(lhm)(_ += _)
}
override def from(t: mutable.LinkedHashMap[ModifierId, Long]): JList[ErgoToken] = {
import JavaHelpers._
val pairs: IndexedSeq[(TokenId, Long)] = t.toIndexedSeq
.map(t => (Digest32 @@ idToBytes(t._1)) -> t._2)
pairs.convertTo[JList[ErgoToken]]
override def to(a: JList[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = {
import JavaHelpers._
val lhm = new mutable.LinkedHashMap[ModifierId, Long]()
a.convertTo[IndexedSeq[(TokenId, Long)]]
.map(t => bytesToId(t._1) -> t._2)
.foldLeft(lhm)(_ += _)
}

override def from(t: mutable.LinkedHashMap[ModifierId, Long]): JList[ErgoToken] = {
import JavaHelpers._
val pairs: IndexedSeq[(TokenId, Long)] = t.toIndexedSeq
.map(t => (Digest32 @@ idToBytes(t._1)) -> t._2)
pairs.convertTo[JList[ErgoToken]]
}
}
}

implicit val isoErgoTypeToSType: Iso[ErgoType[_], SType] = new Iso[ErgoType[_], SType] {
override def to(et: ErgoType[_]): SType = Evaluation.rtypeToSType(et.getRType)
Expand Down Expand Up @@ -347,6 +349,9 @@ object JavaHelpers {
DiffieHellmanTupleProverInput(x, dht)
}

def createTokensMap(linkedMap: mutable.LinkedHashMap[ModifierId, Long]): TokensMap = {
linkedMap.toMap
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface SignedTransaction {
List<SignedInput> getSignedInputs();

/**
* Outputs of this transaction represented as {@link InputBox} objects read to be spent in the next
* Outputs of this transaction represented as {@link InputBox} objects ready to be spent in the next
* chained transaction.
* This method can be used to create a chain of transactions. Thus {@code tx1.getOutputsToSpend()} returns
* a list of boxes which are ready to be included as input boxes to a new tx2.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ public interface UnsignedTransactionBuilder {
*/
UnsignedTransactionBuilder fee(long feeAmount);

/**
* Configures amounts for tokens to be burnt.
* Each Ergo box can store zero or more tokens (aka assets).
* In contrast to strict requirement on ERG balance between transaction inputs and outputs,
* the amounts of output tokens can be less then the amounts of input tokens.
* This is interpreted as token burning i.e. reducing the total amount of tokens in
* circulation in the blockchain.
* Note, once issued/burnt, the amount of tokens in circulation cannot be increased.
*
* @param tokens one or more tokens to be burnt as part of the transaction.
* @see ErgoToken
*/
UnsignedTransactionBuilder tokensToBurn(ErgoToken... tokens);

/**
* Adds change output to the specified address if needed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import org.ergoplatform.wallet.transactions.TransactionBuilder;
import org.ergoplatform.wallet.boxes.DefaultBoxSelector$;
import org.ergoplatform.wallet.boxes.BoxSelector;
import scala.Option;
import scala.collection.IndexedSeq;
import scala.collection.immutable.Map;
import special.collection.Coll;
import special.sigma.Header;
import special.sigma.PreHeader;
Expand All @@ -29,6 +29,7 @@ public class UnsignedTransactionBuilderImpl implements UnsignedTransactionBuilde
ArrayList<ErgoBoxCandidate> _outputCandidates = new ArrayList<>();
private List<InputBoxImpl> _inputBoxes;
private List<InputBoxImpl> _dataInputBoxes = new ArrayList<>();
private List<ErgoToken> _tokensToBurn = new ArrayList<>();
private long _feeAmount;
private ErgoAddress _changeAddress;
private PreHeaderImpl _ph;
Expand Down Expand Up @@ -86,6 +87,12 @@ public UnsignedTransactionBuilder fee(long feeAmount) {
return this;
}

@Override
public UnsignedTransactionBuilder tokensToBurn(ErgoToken... tokens) {
_tokensToBurn.addAll(Stream.of(tokens).collect(Collectors.toList()));
return this;
}

private void appendOutputs(OutBox... outputs) {
ErgoBoxCandidate[] boxes =
Stream.of(outputs).map(c -> ((OutBoxImpl)c).getErgoBoxCandidate()).toArray(n -> new ErgoBoxCandidate[n]);
Expand All @@ -101,8 +108,12 @@ public UnsignedTransactionBuilder sendChangeTo(ErgoAddress changeAddress) {

@Override
public UnsignedTransaction build() {
List<ErgoBox> boxesToSpend = _inputBoxes.stream().map(b -> b.getErgoBox()).collect(Collectors.toList());
List<ErgoBox> dataInputBoxes = _dataInputBoxes.stream().map(b -> b.getErgoBox()).collect(Collectors.toList());
List<ErgoBox> boxesToSpend = _inputBoxes.stream()
.map(b -> b.getErgoBox())
.collect(Collectors.toList());
List<ErgoBox> dataInputBoxes = _dataInputBoxes.stream()
.map(b -> b.getErgoBox())
.collect(Collectors.toList());
IndexedSeq<DataInput> dataInputs = JavaHelpers.toIndexedSeq(_dataInputs);

checkState(_feeAmount > 0, "Fee amount should be defined (using fee() method).");
Expand All @@ -111,16 +122,20 @@ public UnsignedTransaction build() {

IndexedSeq<ErgoBoxCandidate> outputCandidates = JavaHelpers.toIndexedSeq(_outputCandidates);
IndexedSeq<ErgoBox> inputBoxes = JavaHelpers.toIndexedSeq(boxesToSpend);
Map<String, Object> burnTokens = JavaHelpers.createTokensMap(
Iso$.MODULE$.isoJListErgoTokenToMapPair().to(_tokensToBurn)
);
BoxSelector boxSelector = DefaultBoxSelector$.MODULE$;
UnsignedErgoLikeTransaction tx = TransactionBuilder.buildUnsignedTx(
inputBoxes,
dataInputs,
outputCandidates,
dataInputs,
outputCandidates,
_ctx.getHeight(),
_feeAmount,
_changeAddress,
MinChangeValue,
_feeAmount,
_changeAddress,
MinChangeValue,
Parameters.MinerRewardDelay,
burnTokens,
boxSelector).get();
ErgoLikeStateContext stateContext = createErgoLikeStateContext();

Expand Down