Skip to content

Commit

Permalink
Merge pull request #196 from ergoplatform/develop
Browse files Browse the repository at this point in the history
Release v4.0.11 (with Sigma v4.0.7 and ergo-wallet v4.0.104)
  • Loading branch information
aslesarenko authored Oct 18, 2022
2 parents f3f38f8 + 7a7c982 commit a34ff06
Show file tree
Hide file tree
Showing 42 changed files with 1,039 additions and 155 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

- name: Build fat jar
run: sbt clean assembly
run: sbt 'set test in assembly := {}' clean assembly
- name: Upload release binaries
uses: alexellis/upload-assets@0.2.2
env:
Expand Down
1 change: 1 addition & 0 deletions appkit/src/test/resources/tokens.json

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions appkit/src/test/scala/org/ergoplatform/appkit/AddressSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,25 @@ class AddressSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyCh
}

property("Address fromMnemonic") {
val addr = Address.fromMnemonic(NetworkType.TESTNET, mnemonic, SecretString.empty())
val addr = Address.fromMnemonic(NetworkType.TESTNET, mnemonic, SecretString.empty(), false)
addr.toString shouldBe addrStr
checkIsTestnetP2PKAddress(addr)

val addr2 = Address.fromMnemonic(NetworkType.MAINNET, mnemonic, SecretString.empty())
val addr2 = Address.fromMnemonic(NetworkType.MAINNET, mnemonic, SecretString.empty(), false)
addr2 shouldNot be (addr)
addr2.toString shouldNot be (addrStr)

val addr3 = Address.fromErgoTree(addr.getErgoAddress.script, NetworkType.TESTNET)
addr3 shouldBe addr

val addr4 = SigmaProp.createFromAddress(addr).toAddress(NetworkType.TESTNET)
addr4 shouldBe addr

val addr5 = Address.fromSigmaBoolean(addr.getSigmaBoolean, NetworkType.MAINNET)
addr5 shouldBe addr2

val addr6 = new SigmaProp(ErgoValue.of(addr.getSigmaBoolean).getValue).toAddress(NetworkType.TESTNET)
addr6 shouldBe addr
}

property("Address from ErgoAddress") {
Expand Down Expand Up @@ -69,12 +78,12 @@ class AddressSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyCh
}

property("Address createEip3Address") {
val addr = Address.fromMnemonic(NetworkType.MAINNET, mnemonic, SecretString.empty())
val firstEip3Addr = Address.createEip3Address(0, NetworkType.MAINNET, mnemonic, SecretString.empty())
val addr = Address.fromMnemonic(NetworkType.MAINNET, mnemonic, SecretString.empty(), false)
val firstEip3Addr = Address.createEip3Address(0, NetworkType.MAINNET, mnemonic, SecretString.empty(), false)
firstEip3Addr.toString shouldBe firstEip3AddrStr
addr.toString shouldNot be (firstEip3AddrStr)

val secondEip3Addr = Address.createEip3Address(1, NetworkType.MAINNET, mnemonic, SecretString.empty())
val secondEip3Addr = Address.createEip3Address(1, NetworkType.MAINNET, mnemonic, SecretString.empty(), false)
secondEip3Addr.toString shouldBe secondEip3AddrStr

Address.fromPropositionBytes(NetworkType.MAINNET, addr.toPropositionBytes) shouldBe addr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ApiClientSpec
with HttpClientTesting {

val seed = SecretString.create("abc")
val masterKey = JavaHelpers.seedToMasterKey(seed)
val masterKey = JavaHelpers.seedToMasterKey(seed, null, false)
implicit val vs = ValidationRules.currentSettings

property("parse ErgoTree") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package org.ergoplatform.appkit

import org.ergoplatform.appkit.JavaHelpers._
import org.ergoplatform.{ErgoScriptPredef, ErgoBox, UnsignedErgoLikeTransaction}
import org.ergoplatform.appkit.impl.{BlockchainContextImpl, InputBoxImpl, UnsignedTransactionBuilderImpl, UnsignedTransactionImpl}
import org.ergoplatform.settings.ErgoAlgos
import sigmastate.helpers.NegativeTesting
import org.scalatest.{Matchers, PropSpec}
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
import sigmastate.TestsBase
import sigmastate.eval.Colls
import sigmastate.helpers.TestingHelpers.createBox

import java.util
import java.util.Collections
import util.{List => JList}

class AppkitProvingInterpreterSpec extends PropSpec
with Matchers
with ScalaCheckDrivenPropertyChecks
with AppkitTestingCommon
with HttpClientTesting
with NegativeTesting
with TestsBase {

val oneErg = 1000L * 1000 * 1000

def createBoxOps(ctx: BlockchainContext, prover: ErgoProver, inputs: IndexedSeq[ErgoBox]) = {
val ops = new BoxOperations(ctx, Collections.singletonList(prover.getAddress), prover) {
override def loadTop(): util.List[InputBox] = {
val is = inputs.map(b => new InputBoxImpl(b): InputBox)
.convertTo[JList[InputBox]]
is
}
}
ops.withAmountToSpend(oneErg * 2)
}

/** This method creates an UnsignedTransaction instance directly bypassing builders and
* their consistency logic. This allows to create invalid transactions for the tests
* below.
*/
def createUnsignedTransaction(
ctx: BlockchainContext, prover: ErgoProver,
inputs: IndexedSeq[ErgoBox],
outputs: IndexedSeq[ErgoBox],
tokensToBurn: IndexedSeq[ErgoToken]
) = {
val txB = ctx.newTxBuilder().asInstanceOf[UnsignedTransactionBuilderImpl]
val stateContext = txB.createErgoLikeStateContext
val changeAddress = prover.getAddress.getErgoAddress

val boxesToSpend = inputs
.map(b => new InputBoxImpl(b))
.map(b => ExtendedInputBox(b.getErgoBox, b.getExtension))
.convertTo[util.List[ExtendedInputBox]]
val boxesToSpendSeq = JavaHelpers.toIndexedSeq(boxesToSpend)
val tx = new UnsignedErgoLikeTransaction(
inputs = boxesToSpendSeq.map(_.toUnsignedInput),
dataInputs = IndexedSeq(),
outputCandidates = outputs
)
val unsigned = new UnsignedTransactionImpl(
tx, boxesToSpend, new util.ArrayList[ErgoBox](), changeAddress, stateContext,
ctx.asInstanceOf[BlockchainContextImpl], tokensToBurn.convertTo[JList[ErgoToken]])
unsigned
}

property("rejecting transaction with unbalanced tokens") {
val ergoClient = createMockedErgoClient(MockData(Nil, Nil))
ergoClient.execute { ctx: BlockchainContext =>
val prover = ctx.newProverBuilder()
.withMnemonic(mnemonic, SecretString.empty(), false)
.build()
val tree1 = ErgoScriptPredef.TrueProp(ergoTreeHeaderInTests)
val tree2 = ErgoScriptPredef.FalseProp(ergoTreeHeaderInTests)
val token1 = (ErgoAlgos.hash("id1"), 10L)
val token2 = (ErgoAlgos.hash("id2"), 20L)
val ergoToken1 = Iso.isoErgoTokenToPair.from(token1)
val ergoToken2 = Iso.isoErgoTokenToPair.from(token2)

val input1 = createBox(oneErg + Parameters.MinFee, tree1, additionalTokens = Seq(token1))
val input2 = createBox(oneErg, tree2, additionalTokens = Seq(token2))

// successful reduction with balanced tokens
{
val ops = createBoxOps(ctx, prover, IndexedSeq(input1, input2))
val tokens = new util.ArrayList[ErgoToken]()
tokens.add(ergoToken1)
tokens.add(ergoToken2)
val unsigned = ops
.withTokensToSpend(tokens)
.putToContractTxUnsigned(address.toErgoContract)
val reduced = prover.reduce(unsigned, 0)
reduced.getInputBoxesIds.size() shouldBe 2
reduced.getOutputs.size() shouldBe 2 // output + feeOut
}

// Transaction tries to burn tokens when no burning was requested
{
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1))
val unsigned = createUnsignedTransaction(ctx, prover, IndexedSeq(input1, input2), IndexedSeq(output1), IndexedSeq.empty)
assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException]("Transaction tries to burn tokens when no burning was requested")
cond1(e) && e.tokensDiff.exists(t => t == (Colls.fromArray(token2._1), -token2._2))
case _ => false
}
)
}

// Transaction tries to burn tokens when no burning was requested
{
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1, token2.copy(_2 = 10)))
val unsigned = createUnsignedTransaction(ctx, prover, IndexedSeq(input1, input2), IndexedSeq(output1), IndexedSeq.empty)
assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException]("Transaction tries to burn tokens when no burning was requested")
cond1(e) && e.tokensDiff.exists(t => t == (Colls.fromArray(token2._1), -10))
case _ => false
}
)
}

// Transaction tries to burn tokens when no burning was requested
// Inputs: (note, same token in two boxes)
// Box1: Token1, Amount1
// Box2: Token1. Amount2
//
// Outputs:
// Box1: Token1, Amount1
{
// another input with the same token as input1
val input2_with_token1 = createBox(oneErg, tree2, additionalTokens = Seq(token1.copy(_2 = 20)))
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1))

val unsigned = createUnsignedTransaction(ctx, prover,
IndexedSeq(input1, input2_with_token1),
IndexedSeq(output1), tokensToBurn = IndexedSeq.empty)

assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException]("Transaction tries to burn tokens when no burning was requested")
cond1(e) && e.tokensDiff.exists(t => t == (Colls.fromArray(token1._1), -20))
case _ => false
}
)
}

// invalid burning even when burning was requested
{
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1, token2.copy(_2 = 10)))
val unsigned = createUnsignedTransaction(ctx, prover,
IndexedSeq(input1, input2), IndexedSeq(output1),
tokensToBurn = IndexedSeq(ergoToken1))
assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException](
"Transaction tries to burn tokens, but not how it was requested")
val ok = cond1(e)
val token2_BurningWasNotRequested = e.tokensDiff.exists(t => t == (Colls.fromArray(token2._1), 10))
val token1_WasRequestedButNotBurned = e.tokensDiff.exists(t => t == (Colls.fromArray(token1._1), -10))
ok && token2_BurningWasNotRequested && token1_WasRequestedButNotBurned
case _ => false
}
)
}

// attempt to mint more than 1 token
{
val input1 = createBox(oneErg, tree1) // no tokens
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1, token2))
val unsigned = createUnsignedTransaction(ctx, prover,
IndexedSeq(input1), IndexedSeq(output1), tokensToBurn = IndexedSeq.empty)
assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException](
"Only one token can be minted in a transaction")
val ok = cond1(e)
val token1_mint_attempted = e.tokensDiff.exists(t => t == (Colls.fromArray(token1._1), token1._2))
val token2_mint_attempted = e.tokensDiff.exists(t => t == (Colls.fromArray(token2._1), token2._2))
ok && token1_mint_attempted && token2_mint_attempted && e.tokensDiff.length == 2
case _ => false
}
)
}

// attempt to mint 1 token but with invalid id
{
val input1 = createBox(oneErg, tree1) // no tokens
val output1 = createBox(oneErg * 2 + Parameters.MinFee, tree1, additionalTokens = Seq(token1))
val unsigned = createUnsignedTransaction(ctx, prover,
IndexedSeq(input1), IndexedSeq(output1), tokensToBurn = IndexedSeq.empty)
assertExceptionThrown(
prover.reduce(unsigned, 0),
{
case e: TokenBalanceException =>
val cond1 = exceptionLike[TokenBalanceException](
"Cannot mint a token with invalid id")
val ok = cond1(e)
val token1_mint_attempted = e.tokensDiff.exists(t => t == (Colls.fromArray(token1._1), token1._2))
ok && token1_mint_attempted && e.tokensDiff.length == 1
case _ => false
}
)
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Bip32SerializationSpec extends PropSpec with Matchers with ScalaCheckDrive
with AppkitTesting {

property("Serialization roundtrip") {
val masterKey = JavaHelpers.seedToMasterKey(mnemonic, SecretString.empty())
val masterKey = JavaHelpers.seedToMasterKey(mnemonic, SecretString.empty(), false)
val xpubString = Bip32Serialization.serializeExtendedPublicKeyToHex(masterKey, NetworkType.MAINNET)

an[IllegalArgumentException] shouldBe thrownBy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ class ChangeOutputSpec extends PropSpec with Matchers

ergoClient.execute { ctx: BlockchainContext =>

val input0 = ctx.newTxBuilder.outBoxBuilder.registers(
ErgoValue.of(gY), ErgoValue.of(gXY)
).value(30000000).contract(ctx.compileContract(
ConstantsBuilder.empty(),
val input0 = ctx.newTxBuilder.outBoxBuilder
.registers(ErgoValue.of(gY), ErgoValue.of(gXY))
.value(30000000)
.contract(ctx.compileContract(ConstantsBuilder.empty(),
"""{
| val gY = SELF.R4[GroupElement].get
| val gXY = SELF.R5[GroupElement].get
Expand All @@ -146,7 +146,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, tokenAmount)) // amount of token issuing
.tokens(new ErgoToken(tokenId, tokenAmount)) // mint a token with the given amount
.contract(ctx.compileContract(
// contract of the box containing tokens, just has to be spendable
ConstantsBuilder.empty(), "{sigmaProp(1 < 2)}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ErgoAuthSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyC
val signature = new ColdErgoClient(address.getNetworkType, Parameters.ColdClientMaxBlockCost)
.execute { ctx: BlockchainContext =>

val prover = ctx.newProverBuilder().withMnemonic(mnemonic, SecretString.empty()).build()
val prover = ctx.newProverBuilder().withMnemonic(mnemonic, SecretString.empty(), false).build()
prover.signMessage(SigmaProp.parseFromBytes(serializedSigmaBoolean),
signedMessage.getBytes(StandardCharsets.UTF_8),
HintsBag.empty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MnemonicSpec extends PropSpec with Matchers with ScalaCheckDrivenPropertyC
}

property("serializeExtendedPublicKey") {
val masterKey = JavaHelpers.seedToMasterKey(SecretString.create("lens stadium egg cage hollow noble gate belt impulse vicious middle endless angry buzz crack"), SecretString.empty())
val masterKey = JavaHelpers.seedToMasterKey(SecretString.create("lens stadium egg cage hollow noble gate belt impulse vicious middle endless angry buzz crack"), SecretString.empty(), false)

Bip32Serialization.serializeExtendedPublicKeyToHex(masterKey, NetworkType.MAINNET) shouldBe "0488b21e04220c2217000000009216e49a70865823eff5381d6fd33ac96743af1f3051dc4cc8edd66a29a740860326cfc301b0c8d4d815ac721e0551304417e6133c2c9137f9f22c33895a3e1650"
}
Expand Down
Loading

0 comments on commit a34ff06

Please sign in to comment.