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

EIP-29 attachment implementation #154

Merged
merged 19 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5b238af
Add EIP-29 Eip29Attachment, Eip29AttachmentBuilder, Eip29AttachmentTest
MrStahlfelge Mar 29, 2022
9e19c13
Fix OutBoxBuilderImpl.registers can't get called twice, OutBoxImpl.ge…
MrStahlfelge Mar 29, 2022
32bd628
EIP-29: Add BoxOperations.withAttachment and .withMessage
MrStahlfelge Mar 29, 2022
a72d96c
EIP-29: Improve Eip29AttachmentBuilder
MrStahlfelge Mar 29, 2022
118eea3
EIP-29 implementation code review feedback changes
MrStahlfelge Apr 2, 2022
7dc4431
eip29: rollback to scala.Int + fix initialization of ErgoType._unit w…
aslesarenko Apr 6, 2022
5bb1f06
ErgoValue full rollback to scala.Int, added Unit Test to demonstrate …
MrStahlfelge Apr 7, 2022
686c384
eip29: fix ErgoValueTest
aslesarenko Apr 14, 2022
ec61e5c
ergovalue-types: scala.Byte removed
aslesarenko Apr 14, 2022
7e508da
ergovalue-types: scala.Short,Int,Long,Boolean removed
aslesarenko Apr 14, 2022
ff1ec8b
ErgoValueTest completed
MrStahlfelge Apr 14, 2022
fcaf7d5
Merge branch 'develop' into eip29
MrStahlfelge Apr 19, 2022
7c622e3
EIP-29 Attachment PR review changes
MrStahlfelge Apr 20, 2022
9141ebc
Merge remote-tracking branch 'origin/develop' into eip29
MrStahlfelge Apr 20, 2022
7306b70
Merge branch 'develop' into eip29
MrStahlfelge Apr 21, 2022
9ac7bae
eip29: some improvements
aslesarenko Apr 25, 2022
e249d8c
eip29: renamed Eip29Attachment -> BoxAttachment
aslesarenko Apr 25, 2022
f4c609d
eip29: renamed Eip29Attachment -> BoxAttachmentBu
aslesarenko Apr 25, 2022
10efb81
eip29: typo fix
aslesarenko Apr 25, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.ergoplatform.appkit

import org.ergoplatform.appkit.impl.BoxAttachmentBuilder
import org.junit.Assert
import org.scalatest.Matchers.{be, convertToAnyShouldWrapper}
import org.scalatest.PropSpec

import java.util.Collections

class BoxAttachmentSpec extends PropSpec {

private val textAttachmentContent = "Your loan January"

property("build plain text") {
val attachment = BoxAttachmentBuilder.createPlainTextAttachment(textAttachmentContent)
attachment shouldNot be (null)
val ergoValue = attachment.getErgoValue
ergoValue shouldNot be (null)
val backFromValue: BoxAttachment = BoxAttachmentGeneric.createFromErgoValue(ergoValue)
BoxAttachment.Type.PLAIN_TEXT shouldBe backFromValue.getType
backFromValue.isInstanceOf[BoxAttachmentPlainText] shouldBe true
attachment.getText shouldBe backFromValue.asInstanceOf[BoxAttachmentPlainText].getText

val backFromHex: BoxAttachment = BoxAttachmentBuilder.buildFromHexEncodedErgoValue("3c0e400e035052500411596f7572206c6f616e204a616e75617279")
attachment.getText shouldBe backFromHex.asInstanceOf[BoxAttachmentPlainText].getText

val outboxRegistersForAttachment: Array[ErgoValue[_]] = backFromHex.getOutboxRegistersForAttachment
outboxRegistersForAttachment.length shouldBe 6
ErgoValue.fromHex(outboxRegistersForAttachment(0).toHex) shouldBe ErgoValue.unit
}

property("multi attachment test") {
val multiAttachment = BoxAttachmentBuilder.createMultiAttachment(
Collections.singletonList(BoxAttachmentBuilder.createPlainTextAttachment(textAttachmentContent))
)
multiAttachment.getAttachmentCount shouldBe 1
textAttachmentContent shouldBe multiAttachment.getAttachment(0).asInstanceOf[BoxAttachmentPlainText].getText
val ergoValue = multiAttachment.getErgoValue
Assert.assertEquals("3c0e400e03505250022e30633430306530313034313135393666373537323230366336663631366532303461363136653735363137323739", ergoValue.toHex)

val backFromValue = BoxAttachmentGeneric.createFromErgoValue(ergoValue)
backFromValue.getType shouldBe BoxAttachment.Type.MULTI_ATTACHMENT
backFromValue.isInstanceOf[BoxAttachmentMulti] shouldBe true
val multiBackFromValue = backFromValue.asInstanceOf[BoxAttachmentMulti]
multiAttachment.getAttachmentCount shouldBe multiBackFromValue.getAttachmentCount
textAttachmentContent shouldBe multiBackFromValue.getAttachment(0).asInstanceOf[BoxAttachmentPlainText].getText
}
}
40 changes: 40 additions & 0 deletions appkit/src/test/scala/org/ergoplatform/appkit/ErgoValueTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.ergoplatform.appkit;

import static org.junit.Assert.*;

import org.junit.Test;

import java.math.BigInteger;

import special.collection.Coll;
import special.sigma.BigInt;

public class ErgoValueTest {

@Test
public void testTypeDeclarations() {
ErgoValue<Boolean> booleanErgoValue = ErgoValue.of(true);
ErgoValue<Integer> intErgoValue = ErgoValue.of(1);
ErgoValue<Long> longErgoValue = ErgoValue.of(0L);
ErgoValue<Byte> byteErgoValue = ErgoValue.of((byte) 1);
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});

BigInteger bigIntValue = bigIntErgoValue.getValue().value();
byte byteFromCollValue = byteArrayErgoValue.getValue().apply(0);
boolean booleanValue = booleanErgoValue.getValue();
byte byteValue = byteErgoValue.getValue();
short shortValue = shortErgoValue.getValue();
int intValue = intErgoValue.getValue();
long longValue = longErgoValue.getValue();

assertEquals(1, intValue);
assertEquals(0L, longValue);
assertEquals(true, booleanValue);
assertEquals(1, byteValue);
assertEquals(1, shortValue);
assertEquals(0, byteFromCollValue);
assertEquals(BigInteger.ZERO, bigIntValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,12 @@ class TxBuilderSpec extends PropSpec with Matchers
val pkContract = recipient.toErgoContract

val senders = Arrays.asList(storage.getAddressFor(NetworkType.MAINNET))
val unsigned = BoxOperations.createForSenders(senders, ctx).withAmountToSpend(amountToSend)
val unsigned = BoxOperations.createForSenders(senders, ctx)
.withAmountToSpend(amountToSend)
.withMessage("Test message")
.putToContractTxUnsigned(pkContract)
unsigned.getOutputs.get(0).getRegisters.size() shouldBe 6
unsigned.getOutputs.get(0).getAttachment.getType shouldBe BoxAttachment.Type.PLAIN_TEXT

val prover = ctx.newProverBuilder.build // prover without secrets
val reduced = prover.reduce(unsigned, 0)
Expand Down
74 changes: 74 additions & 0 deletions common/src/main/java/org/ergoplatform/appkit/BoxAttachment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.ergoplatform.appkit;

import scala.Tuple2;
import special.collection.Coll;

/**
* Represents an attachment according to EIP-29.
*/
public interface BoxAttachment {
byte[] MAGIC_BYTES = new byte[]{0x50, 0x52, 0x50};

/**
* @return type of this attachment, if known
*/
Type getType();

/**
* @return raw value type of this attachment. Can return types unknown to this implementation.
*/
int getTypeRawValue();

/**
* @return returns full ErgoValue for this attachment, to use for register 9 of out boxes
*/
ErgoValue<Tuple2<Coll<Byte>, Tuple2<Integer, Coll<Byte>>>> getErgoValue();

/**
* @return array R4-R9 to use with OutboxBuilder#registers
*/
ErgoValue<?>[] getOutboxRegistersForAttachment();

/**
* Type of attachment
*/
enum Type {
/**
* This attachment is a list of attachments
*/
MULTI_ATTACHMENT,
/**
* This attachment is a plain text, see {@link BoxAttachmentPlainText#getText()}
*/
PLAIN_TEXT,
/**
* Attachment type is undefined or unknown
*/
UNDEFINED;

/**
* @return raw int constant for attachment type according to EIP-29
*/
public int toTypeRawValue() {
switch (this) {
case MULTI_ATTACHMENT:
return 1;
case PLAIN_TEXT:
return 2;
default:
return 0;
}
}

/**
* @return Type object for given attachment type raw value according to EIP-29
*/
public static Type fromTypeRawValue(int typeRawValue) {
for (Type value : BoxAttachment.Type.values()) {
if (value.toTypeRawValue() == typeRawValue)
return value;
}
return UNDEFINED;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.ergoplatform.appkit;

import java.util.Arrays;

import scala.Tuple2;
import special.collection.Coll;

/**
* Represents an attachment according to EIP-29.
* This is the superclass of all actual attachment types, as well as representing unknown types.
*/
public class BoxAttachmentGeneric implements BoxAttachment {
private final int attachmentType;
private final byte[] attachmentContent;

BoxAttachmentGeneric(int attachmentType, byte[] attachmentContent) {
this.attachmentType = attachmentType;
this.attachmentContent = attachmentContent;
}

@Override
public Type getType() {
return BoxAttachmentGeneric.Type.fromTypeRawValue(attachmentType);
}

@Override
public int getTypeRawValue() {
return attachmentType;
}

@Override
public ErgoValue<Tuple2<Coll<Byte>, Tuple2<Integer, Coll<Byte>>>> getErgoValue() {
ErgoValue<Tuple2<Integer, Coll<Byte>>> contentPair = ErgoValue.pairOf(
ErgoValue.of(getTypeRawValue()),
ErgoValue.of(attachmentContent));
return ErgoValue.pairOf(ErgoValue.of(BoxAttachment.MAGIC_BYTES), contentPair);
}

@Override
public ErgoValue<?>[] getOutboxRegistersForAttachment() {
ErgoValue<?>[] registers = new ErgoValue[6];

for (int i = 0; i < registers.length - 1; i++) {
registers[i] = ErgoValue.unit();
}
registers[5] = getErgoValue();
return registers;
}

/**
* @param r9 Ergo value to create the attachment object from, usually stored in register 9 of boxes
* @return object representing the attachment
*/
public static BoxAttachment createFromErgoValue(ErgoValue<?> r9) {
String illegalArgumentException = "R9 must be of pair (Coll[0x50, 0x52, 0x50], Tuple2(Int, Coll[Byte]), actual: ";

if (!(r9.getValue() instanceof Tuple2)) {
throw new IllegalArgumentException(illegalArgumentException + r9.getValue().getClass().getSimpleName());
}

Tuple2<?, ?> attachmentWrapper = (Tuple2<?, ?>) r9.getValue();
if (!(attachmentWrapper._1 instanceof Coll) || !(attachmentWrapper._2 instanceof Tuple2)) {
throw new IllegalArgumentException(illegalArgumentException + r9.getType().toString());
}

byte[] magicBytes = JavaHelpers$.MODULE$.collToByteArray((Coll<Object>) attachmentWrapper._1);
if (!Arrays.equals(BoxAttachment.MAGIC_BYTES, magicBytes)) {
throw new IllegalArgumentException(illegalArgumentException + "Magic bytes not matched.");
}

Tuple2<?, ?> attachmentValue = (Tuple2<?, ?>) attachmentWrapper._2;
if (!(attachmentValue._1 instanceof Integer) || !(attachmentValue._2 instanceof Coll)) {
throw new IllegalArgumentException(illegalArgumentException + r9.getType());
}

return createFromAttachmentTuple((Tuple2<Integer, Coll<Byte>>) attachmentValue);
}

static BoxAttachment createFromAttachmentTuple(Tuple2<Integer, Coll<Byte>> attachmentTuple) {
Integer typeConstant = attachmentTuple._1;
BoxAttachmentGeneric.Type attachmentType = BoxAttachmentGeneric.Type.fromTypeRawValue(typeConstant);
byte[] attachmentContent = ScalaHelpers.collByteToByteArray(attachmentTuple._2);

switch (attachmentType) {
case PLAIN_TEXT:
return new BoxAttachmentPlainText(attachmentContent);
case MULTI_ATTACHMENT:
return new BoxAttachmentMulti(attachmentContent);
default:
return new BoxAttachmentGeneric(typeConstant, attachmentContent);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.ergoplatform.appkit;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import scala.Tuple2;
import special.collection.Coll;

/**
* EIP-29 Attachment containing list of EIP-29 attachments
*/
public class BoxAttachmentMulti extends BoxAttachmentGeneric {
private final Tuple2<Integer, Coll<Byte>>[] attachmentList;

BoxAttachmentMulti(byte[] attachmentContent) {
super(Type.MULTI_ATTACHMENT.toTypeRawValue(), attachmentContent);

ErgoValue<?> attachmentTuples = ErgoValue.fromHex(new String(attachmentContent, StandardCharsets.UTF_8));
if (!attachmentTuples.getType().equals(ErgoType.collType(ErgoType.pairType(ErgoType.integerType(), ErgoType.collType(ErgoType.byteType()))))) {
throw new IllegalArgumentException("Multi attachment content needs to be Coll[(Int, Coll[Byte])]");
}

attachmentList = (Tuple2[]) ((Coll<?>) attachmentTuples.getValue()).toArray();
}

private BoxAttachmentMulti(byte[] attachmentContent, Tuple2<Integer, Coll<Byte>>[] attachmentList) {
super(Type.MULTI_ATTACHMENT.toTypeRawValue(), attachmentContent);
this.attachmentList = attachmentList;
}

/**
* @return i-th attachment of this multi attachment
*/
public BoxAttachment getAttachment(int i) {
return BoxAttachmentGeneric.createFromAttachmentTuple(attachmentList[i]);
}

/**
* @return count of all attachments contained in this multi attachment
*/
public int getAttachmentCount() {
return attachmentList.length;
}

/**
* @param attachments list of attachments to include in a multi attachment
* @return object representing a multi attachment
*/
public static BoxAttachmentMulti buildForList(List<BoxAttachment> attachments) {
List<Tuple2<Integer, Coll<Byte>>> attachmentTuples = new ArrayList<>(attachments.size());
for (BoxAttachment attachment : attachments) {
attachmentTuples.add(attachment.getErgoValue().getValue()._2);
}
Tuple2<Integer, Coll<Byte>>[] tupleArray = attachmentTuples.toArray(new Tuple2[]{});
ErgoValue<Coll<Tuple2<Integer, Coll<Byte>>>> ergoValue = ErgoValue.of(tupleArray, ErgoType.pairType(ErgoType.integerType(), ErgoType.collType(ErgoType.byteType())));

return new BoxAttachmentMulti(ergoValue.toHex().getBytes(StandardCharsets.UTF_8), tupleArray);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.ergoplatform.appkit;

import java.nio.charset.StandardCharsets;

/**
* EIP-29 attachment containing a simple text
*/
public class BoxAttachmentPlainText extends BoxAttachmentGeneric {
private final String text;

public BoxAttachmentPlainText(byte[] attachmentContent) {
super(Type.PLAIN_TEXT.toTypeRawValue(), attachmentContent);

text = new String(attachmentContent, StandardCharsets.UTF_8);
}

/**
* @return text, attachment content
*/
public String getText() {
return text;
}

public static BoxAttachmentPlainText buildForText(String text) {
return new BoxAttachmentPlainText(text.getBytes(StandardCharsets.UTF_8));
}
}
6 changes: 3 additions & 3 deletions common/src/main/java/org/ergoplatform/appkit/Eip4Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,23 @@ public String getNftCoverImageLink() {
* @return value of register 4 of token minting box
*/
@Nonnull
public ErgoValue<Coll<scala.Byte>> getMintingBoxR4() {
public ErgoValue<Coll<Byte>> getMintingBoxR4() {
return ErgoValue.of(name.getBytes(StandardCharsets.UTF_8));
}

/**
* @return value of register 5 of token minting box
*/
@Nonnull
public ErgoValue<Coll<scala.Byte>> getMintingBoxR5() {
public ErgoValue<Coll<Byte>> getMintingBoxR5() {
return ErgoValue.of(description.getBytes(StandardCharsets.UTF_8));
}

/**
* @return value of register 6 of token minting box
*/
@Nonnull
public ErgoValue<Coll<scala.Byte>> getMintingBoxR6() {
public ErgoValue<Coll<Byte>> getMintingBoxR6() {
return ErgoValue.of(Integer.toString(decimals).getBytes(StandardCharsets.UTF_8));
}

Expand Down
Loading