Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
44218b4
EIP-31 Basic implementation of data classes and transaction to create…
MrStahlfelge Nov 1, 2022
c8a613a
EIP-0031 add BabelFeeOperations.cancelBabelFeeContract
MrStahlfelge Nov 1, 2022
448ff3d
EIP-0031 Add BabelFeeOperations#getBabelFeeTransactionBuilder
MrStahlfelge Nov 1, 2022
d440b20
EIP-0031 BabelFeeOperations#findBabelFeeBox
MrStahlfelge Nov 1, 2022
fd5ac70
Fix Java 8 compile
MrStahlfelge Nov 1, 2022
daa5610
eip-31-babelfees: typo fixes
aslesarenko Nov 3, 2022
61b6657
eip-31-babelfees: some formatting fixes
aslesarenko Nov 3, 2022
5102a52
EIP-0031 use ErgoTreeSerializer.DefaultSerializer.substituteConstants…
MrStahlfelge Nov 3, 2022
31f229f
Improve exception message
MrStahlfelge Nov 3, 2022
77b7096
EIP-0031 test for findBabelFeeBox, documentation changes, improve Bab…
MrStahlfelge Nov 3, 2022
6610b7f
Merge branch 'transactionbuilder-addmethods' into eip-31-babelfees
MrStahlfelge Nov 5, 2022
933f8a7
EIP-0031 use new methods from #205
MrStahlfelge Nov 5, 2022
93f2eb7
EIP-0031 use ErgoTreeTemplate
MrStahlfelge Nov 5, 2022
c2c965a
Merge remote-tracking branch 'origin/develop' into eip-31-babelfees
MrStahlfelge Nov 7, 2022
15fee58
transactionbuilder-addmethods: correct implementation of ErgoTreeTemp…
aslesarenko Nov 7, 2022
fe3f0fd
transactionbuilder-addmethods: tests for ErgoTreeTemplate
aslesarenko Nov 7, 2022
9ec3f36
Added BabelFeeOperations.findBabelFeeBox parameter to search through …
MrStahlfelge Nov 9, 2022
e4400fc
Linked to EIP version from PR
MrStahlfelge Nov 9, 2022
93faed4
eip-31-babelfees: fix box loading + formatting
aslesarenko Nov 10, 2022
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
138 changes: 138 additions & 0 deletions appkit/src/test/scala/org/ergoplatform/appkit/BabelFeeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.ergoplatform.appkit

import org.ergoplatform.appkit.babelfee.{BabelFeeBoxContract, BabelFeeBoxState, BabelFeeOperations}
import org.scalatest.{Matchers, PropSpec}
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks

import java.util
import java.util.Arrays

class BabelFeeSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyChecks
with HttpClientTesting
with AppkitTestingCommon {

val mockTokenId = "f9e5ce5aa0d95f5d54a7bc89c46730d9662397067250aa18a0039631c0f5b809"

property("babel fee box creation and revoke") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
val creator = address

val amountToSend = Parameters.OneErg * 100

val input1 = ctx.newTxBuilder.outBoxBuilder
.value(amountToSend + Parameters.MinFee)
.contract(creator.toErgoContract)
.build().convertToInputWith(mockTokenId, 0)

val txCreate = BabelFeeOperations.createNewBabelContractTx(
BoxOperations.createForSender(creator, ctx)
.withAmountToSpend(amountToSend)
.withInputBoxesLoader(new MockedBoxesLoader(Arrays.asList(input1))),
ErgoId.create(mockTokenId),
Parameters.OneErg
)

ctx.newProverBuilder().build().reduce(txCreate, 0)

val babelFeeErgoBox = txCreate.getOutputs.get(0).convertToInputWith(mockTokenId, 0)

// now we cancel the babel box
val txCancel = BabelFeeOperations.cancelBabelFeeContract(
BoxOperations.createForSender(creator, ctx)
.withInputBoxesLoader(new MockedBoxesLoader(Arrays.asList(input1))),
babelFeeErgoBox)

ctx.newProverBuilder()
.withMnemonic(mnemonic, SecretString.empty(), false)
.build()
.sign(txCancel)

// check if the contract really is for the token
new BabelFeeBoxContract(babelFeeErgoBox.getErgoTree).getTokenId.toString shouldBe mockTokenId
// check template hash
ErgoTreeTemplate.fromErgoTree(babelFeeErgoBox.getErgoTree).getTemplateHashHex shouldBe BabelFeeBoxContract.templateHash
}
}

property("babel fee box use") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
val sender = address

val txB = ctx.newTxBuilder
val babelFeeBoxState = BabelFeeBoxState.newBuilder().withValue(Parameters.OneErg)
.withTokenId(ErgoId.create(mockTokenId))
.withBoxCreator(Address.create(secondEip3AddrStr))
.withPricePerToken(Parameters.MinFee)
.build()

val fee = Parameters.MinFee

val output = txB.outBoxBuilder
.value(Parameters.MinChangeValue)
.contract(sender.toErgoContract)
.tokens(new ErgoToken(
ErgoId.create(mockTokenId),
1000 - babelFeeBoxState.calcTokensToSellForErgAmount(fee)))
.build()

val input = txB.outBoxBuilder
.value(Parameters.MinChangeValue)
.contract(sender.toErgoContract)
.tokens(new ErgoToken(ErgoId.create(mockTokenId), 1000))
.build().convertToInputWith(mockTokenId, 0)

txB.fee(fee)
.outputs(output)
.boxesToSpend(util.Arrays.asList(input))
.sendChangeTo(sender)

BabelFeeOperations.addBabelFeeBoxes(txB,
babelFeeBoxState.buildOutbox(txB, null)
.convertToInputWith(mockTokenId, 0),
fee)

val tx = txB.build()

ctx.newProverBuilder()
.withMnemonic(mnemonic, SecretString.empty(), false)
.build()
.sign(tx)
}
}

property("fetch babel fee boxes") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
val creator = address

val tockenId = ErgoId.create(mockTokenId)

// find no boxes
val babelBox1 = BabelFeeOperations.findBabelFeeBox(ctx, new MockedBoxesLoader(new util.ArrayList[InputBox]()),
tockenId, Parameters.MinFee)

babelBox1 shouldBe (null)

val inputBabelBox = BabelFeeBoxState.newBuilder()
.withValue(Parameters.OneErg)
.withTokenId(tockenId)
.withPricePerToken(Parameters.MinFee)
.withBoxCreator(creator)
.build().buildOutbox(ctx.newTxBuilder(), null)
.convertToInputWith(mockTokenId, 0)

val babelBox2 = BabelFeeOperations.findBabelFeeBox(ctx, new MockedBoxesLoader(util.Arrays.asList(inputBabelBox)),
tockenId, Parameters.MinFee)

babelBox2 shouldBe inputBabelBox

// the amount needed (2 ERG) is more than inputBabelBox can offer, so it is discarded
val babelBox3 = BabelFeeOperations.findBabelFeeBox(ctx, new MockedBoxesLoader(util.Arrays.asList(inputBabelBox)),
tockenId, Parameters.OneErg * 2)

babelBox3 shouldBe (null)
}
}
}
51 changes: 33 additions & 18 deletions common/src/main/java/org/ergoplatform/appkit/ErgoTreeTemplate.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
package org.ergoplatform.appkit;

import scala.NotImplementedError;
import scala.collection.IndexedSeq;
import java.util.Arrays;

import scorex.util.encode.Base16;
import sigmastate.SType;
import sigmastate.Values;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import sigmastate.serialization.ErgoTreeSerializer;

/**
* Represents ErgoTree template, which is an ErgoTree instance with placeholders.
Expand Down Expand Up @@ -48,14 +44,22 @@ public int hashCode() {
* @return template bytes at the tail of the serialized ErgoTree (i.e. exclusing header and segregated
* constants)
*/
public byte[] getBytes() { return _templateBytes; }
public byte[] getBytes() {
return _templateBytes;
}

/**
* Returns template bytes encoded as Base16 string.
*
* @see ErgoTreeTemplate#getBytes
*/
public String getEncodedBytes() { return Base16.encode(getBytes()); }
public String getEncodedBytes() {
return Base16.encode(getBytes());
}

public String getTemplateHashHex() {
return Base16.encode(scorex.crypto.hash.Sha256.hash(_templateBytes));
}

/**
* A number of placeholders in the template, which can be substituted (aka parameters).
Expand All @@ -64,16 +68,16 @@ public int hashCode() {
* {@link ErgoTreeTemplate#applyParameters} method.
* In general, constants of ErgoTree cannot be replaced, but every placeholder can.
*/
public int getParameterCount() { return _tree.constants().length(); }
public int getParameterCount() {
return _tree.constants().length();
}

/**
* Returns types of all template parameters (placeholders in the ErgoTree).
* @param index 0-based
* @return value object of paramter
*/
public List<ErgoType<?>> getParameterTypes() {
Iso<List<Values.Constant<SType>>, IndexedSeq<Values.Constant<SType>>> iso =
Iso.JListToIndexedSeq(Iso.identityIso());
List<Values.Constant<SType>> ergoValues = iso.from(_tree.constants());
return ergoValues.stream().map(v -> Iso.isoErgoTypeToSType().from(v.tpe())).collect(Collectors.toList());
public ErgoValue<?> getParameter(int index) {
return Iso.isoErgoValueToSValue().from(_tree.constants().apply(index + 1));
Comment thread
aslesarenko marked this conversation as resolved.
Outdated
}
Comment thread
aslesarenko marked this conversation as resolved.

/**
Expand All @@ -89,11 +93,22 @@ public List<ErgoType<?>> getParameterTypes() {
* @return new ErgoTree with the same template as this but with all it's parameters
* replaced with `newValues`
*/
public Values.ErgoTree applyParameters(ErgoValue<?> newValues) {
throw new NotImplementedError();
public Values.ErgoTree applyParameters(ErgoValue<?>... newValues) {
int[] positions = new int[newValues.length];
for (int position : positions) {
positions[position] = position + 1;
}

return JavaHelpers.substituteErgoTreeConstants(_tree.bytes(), positions, newValues);
}

public static ErgoTreeTemplate fromErgoTree(Values.ErgoTree tree) {
return new ErgoTreeTemplate(tree);
}

public static ErgoTreeTemplate fromErgoTreeBytes(byte[] treeBytes) {
return fromErgoTree(ErgoTreeSerializer.DefaultSerializer().deserializeErgoTree(treeBytes));
}

// TODO public static ErgoTreeTemplate fromTemplateBytes(byte[] templateBytes)
}
4 changes: 4 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/ErgoValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ static public ErgoValue<special.sigma.SigmaProp> of(Values.SigmaBoolean value) {
return new ErgoValue<>(JavaHelpers.SigmaDsl().SigmaProp(value), ErgoType.sigmaPropType());
}

static public ErgoValue<special.sigma.SigmaProp> of(org.ergoplatform.appkit.SigmaProp value) {
return new ErgoValue<>(JavaHelpers.SigmaDsl().SigmaProp(value.getSigmaBoolean()), ErgoType.sigmaPropType());
}

static public ErgoValue<AvlTree> of(AvlTreeData value) {
return new ErgoValue<>(JavaHelpers.SigmaDsl().avlTree(value), ErgoType.avlTreeType());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator
import org.bouncycastle.crypto.params.KeyParameter
import org.ergoplatform.appkit.JavaHelpers.{TokenColl, TokenIdRType}
import sigmastate.eval.Colls.outerJoin
import sigmastate.eval.CostingSigmaDslBuilder.validationSettings
import special.collection.ExtensionMethods.PairCollOps

/** Type-class of isomorphisms between types.
Expand Down Expand Up @@ -327,6 +328,12 @@ object JavaHelpers {
ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(Base16.decode(base16).get)
}

def substituteErgoTreeConstants(ergoTreeBytes: Array[Byte], positions: Array[Int], newValues: Array[ErgoValue[_]]): ErgoTree = {
val newBytes = ErgoTreeSerializer.DefaultSerializer.substituteConstants(
ergoTreeBytes, positions, newValues.map(Iso.isoErgoValueToSValue.to))
ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(newBytes._1)
}

def createP2PKAddress(pk: ProveDlog, networkPrefix: NetworkPrefix): P2PKAddress = {
implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix)
P2PKAddress(pk)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
public interface TransactionBox {
/**
* Returns the ERG value stored in this box, i.e. unspent value in UTXO.
* Returns the nanoERG value stored in this box, i.e. unspent value in UTXO.
*/
long getValue();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,18 @@ public interface UnsignedTransactionBuilder {
* Adds change output to the specified address if needed.
*
* @param address address to send output
* @deprecated use {@link #sendChangeTo(Address)}
*/
@Deprecated
UnsignedTransactionBuilder sendChangeTo(ErgoAddress address);

/**
* Adds change output to the specified address if needed.
*
* @param address address to send output
*/
UnsignedTransactionBuilder sendChangeTo(Address address);

/**
* Builds a new unsigned transaction in the {@link BlockchainContext context} inherited from this builder.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.ergoplatform.appkit.babelfee;

import org.ergoplatform.appkit.ErgoId;
import org.ergoplatform.appkit.ErgoTreeTemplate;
import org.ergoplatform.appkit.ErgoValue;
import org.ergoplatform.appkit.ScalaHelpers;

import scorex.util.encode.Base16;
import sigmastate.Values;
import special.collection.Coll;

public class BabelFeeBoxContract {

/**
* babel fee box ErgoTreeTemplateHash to be used for Explorer requests
*/
public static final String templateHash = "4e83fa68ef3ed9794bbab5d8799998a9e09fb10a0afe8d1bf928936dfd11c465";
// this was calced by ErgoTreeTemplate.fromTemplateBytes(contractTemplate).getTemplateHashHex()

private static final String contractTemplateHex = "100604000e000400040005000500d803d601e30004d602e4c6a70408d603e4c6a7050595e67201d804d604b2a5e4720100d605b2db63087204730000d606db6308a7d60799c1a7c17204d1968302019683050193c27204c2a7938c720501730193e4c672040408720293e4c672040505720393e4c67204060ec5a796830201929c998c7205029591b1720673028cb272067303000273047203720792720773057202";
private static final byte[] contractTemplate;

static {
contractTemplate = Base16.decode(contractTemplateHex).get();
}
Comment thread
aslesarenko marked this conversation as resolved.

private final ErgoId tokenId;
private final Values.ErgoTree ergoTree;

public BabelFeeBoxContract(ErgoId tokenId) {
this.tokenId = tokenId;
byte[] idBytes = tokenId.getBytes();
ergoTree = ErgoTreeTemplate.fromErgoTreeBytes(contractTemplate).applyParameters(new ErgoValue[]{ErgoValue.of(idBytes)});
}

public BabelFeeBoxContract(Values.ErgoTree ergoTree) {
this.ergoTree = ergoTree;
tokenId = new ErgoId(ScalaHelpers.collByteToByteArray((Coll<Byte>) ErgoTreeTemplate.fromErgoTree(ergoTree).getParameter(0).getValue()));
}

public Values.ErgoTree getErgoTree() {
return ergoTree;
}

public ErgoId getTokenId() {
return tokenId;
}
}
Loading