Skip to content

Commit

Permalink
Merge pull request #208 from ergoplatform/develop
Browse files Browse the repository at this point in the history
Release v5.0.0 (with Sigma v5.0.2 and ergo-wallet v5.0.2)
  • Loading branch information
aslesarenko authored Nov 14, 2022
2 parents a34ff06 + 1d75035 commit fbd6ba9
Show file tree
Hide file tree
Showing 37 changed files with 1,379 additions and 202 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}

- name: Runs tests and collect coverage
run: sbt -jvm-opts ci/ci.jvmopts ++${{ matrix.scala }} test
run: sbt -jvm-opts ci/ci.jvmopts ++${{ matrix.scala }} "project appkit" test; sbt -jvm-opts ci/ci.jvmopts ++${{ matrix.scala }} "project common" test; sbt -jvm-opts ci/ci.jvmopts ++${{ matrix.scala }} "project libImpl" test
# - name: Upload coverage report
# run: sbt ++${{ matrix.scala }} coverageReport coverageAggregate coveralls
# env:
Expand Down
6 changes: 6 additions & 0 deletions MIGRATION
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[5.0.0]
- block version now required to construct ColdErgoClient
- getBoxById(String boxId) was replaced by getBoxById(String boxId, boolean findInPool, boolean findInSpent)
the old behaviour (returning only confirmed unspent boxes) can be achieved by calling
getBoxById(boxId, false, false)

[4.0.9]
- ExplorerAndPoolUnspentBoxesLoader moved from package org.ergoplatform.appkit.impl to org.ergoplatform.appkit
- DefaultApi.getApiV1AddressesP1Transactions new parameter "concise" - use false for old behaviour
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ class ColdErgoClient(networkType: NetworkType, params: BlockchainParameters) ext
/**
* Convenience constructor for giving maxBlockCost
*/
def this(networkType: NetworkType, maxBlockCost: Int) {
def this(networkType: NetworkType, maxBlockCost: Int, blockVersion: Byte) {
this(networkType, new NodeInfoParameters(
new NodeInfo().parameters(new client.Parameters()
.maxBlockCost(Integer.valueOf(maxBlockCost)))))
.maxBlockCost(Integer.valueOf(maxBlockCost))
.blockVersion(Integer.valueOf(blockVersion)))))
}

override def execute[T](action: function.Function[BlockchainContext, T]): T = {
Expand Down
139 changes: 139 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,139 @@
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, 1)

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, 1)

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, 1)

babelBox3 shouldBe (null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ErgoAuthSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyC
// EIP-28: "the wallet app adds some own bytes to the obtained message from ErgoAuthRequest"
val signedMessage = new String(Random.randomBytes(16)) + requestedMessage +
new String(Random.randomBytes(32))
val signature = new ColdErgoClient(address.getNetworkType, Parameters.ColdClientMaxBlockCost)
val signature = new ColdErgoClient(address.getNetworkType, Parameters.ColdClientMaxBlockCost, Parameters.ColdClientBlockVersion)
.execute { ctx: BlockchainContext =>

val prover = ctx.newProverBuilder().withMnemonic(mnemonic, SecretString.empty(), false).build()
Expand Down
12 changes: 12 additions & 0 deletions appkit/src/test/scala/org/ergoplatform/appkit/ErgoValueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,17 @@ public void testTypeDeclarations() {
ErgoValue<Short> shortErgoValue = ErgoValue.of((short) 1);
ErgoValue<BigInt> bigIntErgoValue = ErgoValue.of(BigInteger.ZERO);
ErgoValue<Coll<Byte>> byteArrayErgoValue = ErgoValue.of(new byte[] {0, 1, 2});
ErgoValue<Coll<Short>> shortArrayErgoValue = ErgoValue.of(new short[] {1, 2, 3});
ErgoValue<Coll<Integer>> intArrayErgoValue = ErgoValue.of(new int[] {2, 3, 4});
ErgoValue<Coll<Boolean>> boolArrayErgoValue = ErgoValue.of(new boolean[] {false, true});
ErgoValue<Coll<Long>> longArrayErgoValue = ErgoValue.of(new long[] {3, 4, 5});

BigInteger bigIntValue = bigIntErgoValue.getValue().value();
boolean booleanFromCollValue = boolArrayErgoValue.getValue().apply(0);
byte byteFromCollValue = byteArrayErgoValue.getValue().apply(0);
short shortFromCollValue = shortArrayErgoValue.getValue().apply(0);
int intFromCollValue = intArrayErgoValue.getValue().apply(0);
long longFromCollValue = longArrayErgoValue.getValue().apply(0);
boolean booleanValue = booleanErgoValue.getValue();
byte byteValue = byteErgoValue.getValue();
short shortValue = shortErgoValue.getValue();
Expand All @@ -34,7 +42,11 @@ public void testTypeDeclarations() {
assertEquals(true, booleanValue);
assertEquals(1, byteValue);
assertEquals(1, shortValue);
assertFalse(booleanFromCollValue);
assertEquals(0, byteFromCollValue);
assertEquals(1, shortFromCollValue);
assertEquals(2, intFromCollValue);
assertEquals(3, longFromCollValue);
assertEquals(BigInteger.ZERO, bigIntValue);
}
}
111 changes: 105 additions & 6 deletions appkit/src/test/scala/org/ergoplatform/appkit/TxBuilderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package org.ergoplatform.appkit

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import org.ergoplatform.appkit.InputBoxesSelectionException.{InputBoxLimitExceededException, NotEnoughErgsException}
import org.ergoplatform.appkit.InputBoxesSelectionException.{InputBoxLimitExceededException, NotEnoughCoinsForChangeException, NotEnoughErgsException}
import org.ergoplatform.appkit.JavaHelpers._
import org.ergoplatform.appkit.examples.RunMockedScala.data
import org.ergoplatform.appkit.impl.{Eip4TokenBuilder, ErgoTreeContract}
import org.ergoplatform.appkit.testing.AppkitTesting
import org.ergoplatform.explorer.client.model.{Items, TokenInfo}
Expand All @@ -23,7 +22,6 @@ import java.math.BigInteger
import java.util
import java.util.Arrays
import java.util.function.Consumer
import scala.collection.JavaConversions

class TxBuilderSpec extends PropSpec with Matchers
with ScalaCheckDrivenPropertyChecks
Expand Down Expand Up @@ -161,8 +159,8 @@ class TxBuilderSpec extends PropSpec with Matchers
.build()

val changeAddr = Address.fromErgoTree(input.getErgoTree, NetworkType.MAINNET).getErgoAddress
val unsigned = txB.boxesToSpend(Arrays.asList(input))
.outputs(output, feeOut)
val unsigned = txB.addInputs(input)
.outputs(output).addOutputs(feeOut)
.sendChangeTo(changeAddr)
.build()
val prover = ctx.newProverBuilder().build()
Expand Down Expand Up @@ -308,7 +306,7 @@ class TxBuilderSpec extends PropSpec with Matchers
// the only necessary parameter can either be hard-coded or passed
// together with ReducedTransaction
val maxBlockCost = Parameters.ColdClientMaxBlockCost
val coldClient = new ColdErgoClient(NetworkType.MAINNET, maxBlockCost)
val coldClient = new ColdErgoClient(NetworkType.MAINNET, maxBlockCost, Parameters.ColdClientBlockVersion)

coldClient.execute { ctx: BlockchainContext =>
// test that context is cold
Expand Down Expand Up @@ -405,6 +403,57 @@ class TxBuilderSpec extends PropSpec with Matchers
operations.withMaxInputBoxesToSelect(1).loadTop(),
exceptionLike[InputBoxLimitExceededException]("could not cover 1000000 nanoERG")
)

// if there is only a single input box, we face NotEnoughCoinsForChangeException
val operations2 = BoxOperations.createForSenders(senders, ctx)
.withAmountToSpend(amountToSend)
.withInputBoxesLoader(new MockedBoxesLoader(util.Arrays.asList(input1)))

assertExceptionThrown(
operations2.loadTop(),
exceptionLike[NotEnoughCoinsForChangeException]()
)
}

}

property("use changebox to consolidate") {
// this demonstrates that unnecessary input boxes can be picked up to consolidate wallet boxes
// via change box on the fly (issue #182)

val ergoClient = createMockedErgoClient(data)

ergoClient.execute { ctx: BlockchainContext =>
val (_, senders) = loadStorageE2()
val recipient = address
val pkContract = recipient.toErgoContract

// send 0.5 ERG
val amountToSend = 500L * 1000 * 1000

// first box: 1 ERG
val input1 = ctx.newTxBuilder.outBoxBuilder
.value(Parameters.OneErg)
.contract(pkContract)
.build().convertToInputWith(mockTxId, 0)
// second box: 1 ERG
val input2 = ctx.newTxBuilder.outBoxBuilder
.value(Parameters.OneErg)
.contract(pkContract)
.build().convertToInputWith(mockTxId, 1)

val tx = ctx.newTxBuilder().addInputs(input1).addInputs(input2)
.outputs(ctx.newTxBuilder().outBoxBuilder().contract(pkContract).value(amountToSend).build())
.sendChangeTo(recipient.getErgoAddress)
.fee(Parameters.MinFee)
.build()

// both boxes should be selected
// ergo-wallet's DefaultBoxSelector discarded the second input because it is not necessary for
// the outputs, so this test checks if all inputs are used (size 2)
tx.getInputs.size() shouldBe 2
tx.getOutputs.size() shouldBe 3

}

}
Expand Down Expand Up @@ -469,6 +518,56 @@ class TxBuilderSpec extends PropSpec with Matchers

}

property("Test same token multiple times") {
val ergoClient = createMockedErgoClient(data)

ergoClient.execute { ctx: BlockchainContext =>
val (storage, _) = loadStorageE2()

val recipient = address

// send 1 ERG
val amountToSend = 1000L * 1000 * 1000
val pkContract = recipient.toErgoContract

val senders = Arrays.asList(storage.getAddressFor(NetworkType.MAINNET))

val input1 = ctx.newTxBuilder.outBoxBuilder
.value(amountToSend + Parameters.MinFee + Parameters.MinChangeValue)
.contract(pkContract)
// the same token twice
.tokens(new ErgoToken(mockTxId, 1), new ErgoToken(mockTxId, 1))
.build().convertToInputWith(mockTxId, 0)

val unsigned = BoxOperations.createForSenders(senders, ctx)
.withAmountToSpend(amountToSend)
.withInputBoxesLoader(new MockedBoxesLoader(Arrays.asList(input1)))
.putToContractTxUnsigned(pkContract)

// check if this succeeds without token burning, but with tokens in change box
// otherwise exception would be raised
val prover = ctx.newProverBuilder.build // prover without secrets
val reduced = prover.reduce(unsigned, 0)

// outputs should contain the two tokens going in
unsigned.getOutputs.convertTo[IndexedSeq[OutBox]]
.map(_.getTokens.convertTo[IndexedSeq[ErgoToken]])
.flatten(identity)
.filter(_.getId.toString.equals(mockTxId))
.map(_.getValue).sum shouldBe 2L

// check if this suceeds finding all tokens and not raising any exception
val spendAllTokens = BoxOperations.createForSenders(senders, ctx)
.withAmountToSpend(amountToSend)
.withTokensToSpend(Arrays.asList(new ErgoToken(mockTxId, 2)))
.withInputBoxesLoader(new MockedBoxesLoader(Arrays.asList(input1)))
.putToContractTxUnsigned(pkContract)

val reduced2 = prover.reduce(spendAllTokens, 0)
}

}

property("Special tx building cases") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,13 @@ class DataInputsSpec extends PropSpec with Matchers
.contract(truePropContract(ctx)).build()

val inputs = new java.util.ArrayList[InputBox]()
val dataInputs = new java.util.ArrayList[InputBox]()

inputs.add(input)
dataInputs.add(dataInput)

val ergoTree = JavaHelpers.decodeStringToErgoTree(dummyErgoTree)
val changeAddr = Address.fromErgoTree(ergoTree, NetworkType.MAINNET).getErgoAddress

val unsigned = txB.boxesToSpend(inputs).outputs(dummyOutput).withDataInputs(dataInputs).fee(10000000).sendChangeTo(changeAddr).build()
val unsigned = txB.boxesToSpend(inputs).outputs(dummyOutput).addDataInputs(dataInput).fee(10000000).sendChangeTo(changeAddr).build()

an[Exception] shouldBe thrownBy {
ctx.newProverBuilder().build().sign(unsigned)
Expand Down Expand Up @@ -115,15 +113,13 @@ class DataInputsSpec extends PropSpec with Matchers
.contract(truePropContract(ctx)).build()

val inputs = new java.util.ArrayList[InputBox]()
val dataInputs = new java.util.ArrayList[InputBox]()

inputs.add(input)
dataInputs.add(dataInput)

val ergoTree = JavaHelpers.decodeStringToErgoTree(dummyErgoTree)
val changeAddr = Address.fromErgoTree(ergoTree, NetworkType.MAINNET).getErgoAddress

val unsigned = txB.boxesToSpend(inputs).outputs(dummyOutput).withDataInputs(dataInputs).fee(10000000).sendChangeTo(changeAddr).build()
val unsigned = txB.boxesToSpend(inputs).outputs(dummyOutput).addDataInputs(dataInput).fee(10000000).sendChangeTo(changeAddr).build()

ctx.newProverBuilder().build().sign(unsigned)
}
Expand Down
Loading

0 comments on commit fbd6ba9

Please sign in to comment.