-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Cis2 (#304)
1 parent
47576a4
commit 20c837d
Showing
50 changed files
with
2,119 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
concordium-sdk-examples/src/main/java/com/concordium/sdk/examples/Cis2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.concordium.sdk.examples; | ||
|
||
import com.concordium.sdk.ClientV2; | ||
import com.concordium.sdk.Connection; | ||
import com.concordium.sdk.cis2.BalanceQuery; | ||
import com.concordium.sdk.cis2.Cis2Client; | ||
import com.concordium.sdk.cis2.TokenAmount; | ||
import com.concordium.sdk.cis2.TokenId; | ||
import com.concordium.sdk.exceptions.ClientInitializationException; | ||
import com.concordium.sdk.requests.BlockQuery; | ||
import com.concordium.sdk.transactions.Hash; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import com.concordium.sdk.types.AccountAddress; | ||
import com.concordium.sdk.types.ContractAddress; | ||
import lombok.val; | ||
import picocli.CommandLine; | ||
|
||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.util.concurrent.Callable; | ||
|
||
/** | ||
* Example usage of the CIS2 client | ||
*/ | ||
@CommandLine.Command(name = "Cis2", mixinStandardHelpOptions = true) | ||
public class Cis2 implements Callable<Integer> { | ||
|
||
@CommandLine.Option( | ||
names = {"--endpoint"}, | ||
description = "GRPC interface of the node.", | ||
defaultValue = "http://localhost:20000") | ||
private String endpoint; | ||
|
||
@CommandLine.Option( | ||
names = {"--index"}, | ||
description = "Index of the contract.", | ||
defaultValue = "9390") | ||
private long contractIndex; | ||
|
||
@Override | ||
public Integer call() throws ClientInitializationException, MalformedURLException { | ||
URL endpointUrl = new URL(this.endpoint); | ||
val client = Cis2Client.newClient(ClientV2.from(Connection.newBuilder() | ||
.host(endpointUrl.getHost()) | ||
.port(endpointUrl.getPort()) | ||
.build()), ContractAddress.from(contractIndex, 0)); | ||
|
||
val eventsForBlock = client.getEventsFor(BlockQuery.HASH(Hash.from("cfd9a3de1b7de2d2942f80b102135bcc8553f472c53c6c8074110aba38bca43c"))); | ||
while (eventsForBlock.hasNext()) { | ||
System.out.println(eventsForBlock.next()); | ||
} | ||
|
||
val balances = client.balanceOf(new BalanceQuery(TokenId.min(), AccountAddress.from("3rXssmPErqhHvDMByFLCEydYAJwot7ZkL7xcu8y296iMJxwNGC"))); | ||
for (BalanceQuery balanceQuery : balances.keySet()) { | ||
TokenId tokenId = balanceQuery.getTokenId(); | ||
AbstractAddress owner = balanceQuery.getAddress(); | ||
TokenAmount balance = balances.get(balanceQuery); | ||
System.out.println("TokenId: " + tokenId + " Owner: " + owner + " Balance " + balance); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
public static void main(String[] args) { | ||
int exitCode = new CommandLine(new Cis2()).execute(args); | ||
System.exit(exitCode); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
concordium-sdk/src/main/java/com/concordium/sdk/cis2/BalanceQuery.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An object used for querying token balances. | ||
* See <a href="https://proposals.concordium.software/CIS/cis-2.html#balanceof">here</a> for the specification. | ||
*/ | ||
@Getter | ||
@ToString | ||
@EqualsAndHashCode | ||
public class BalanceQuery { | ||
|
||
/** | ||
* The token id to query | ||
*/ | ||
private final TokenId tokenId; | ||
|
||
/** | ||
* The address to query the balance of | ||
*/ | ||
private final AbstractAddress address; | ||
|
||
public BalanceQuery(TokenId tokenId, AbstractAddress address) { | ||
this.tokenId = tokenId; | ||
this.address = address; | ||
} | ||
|
||
} |
243 changes: 243 additions & 0 deletions
243
concordium-sdk/src/main/java/com/concordium/sdk/cis2/Cis2Client.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.ClientV2; | ||
import com.concordium.sdk.cis2.events.Cis2Event; | ||
import com.concordium.sdk.cis2.events.Cis2EventWithMetadata; | ||
import com.concordium.sdk.requests.AccountQuery; | ||
import com.concordium.sdk.requests.BlockQuery; | ||
import com.concordium.sdk.requests.smartcontracts.Energy; | ||
import com.concordium.sdk.requests.smartcontracts.InvokeInstanceRequest; | ||
import com.concordium.sdk.responses.blockitemstatus.FinalizedBlockItem; | ||
import com.concordium.sdk.responses.blockitemsummary.Summary; | ||
import com.concordium.sdk.responses.blockitemsummary.Type; | ||
import com.concordium.sdk.responses.blocksatheight.BlocksAtHeightRequest; | ||
import com.concordium.sdk.responses.smartcontracts.ContractTraceElement; | ||
import com.concordium.sdk.responses.smartcontracts.ContractTraceElementType; | ||
import com.concordium.sdk.responses.transactionstatus.ContractUpdated; | ||
import com.concordium.sdk.responses.transactionstatus.Outcome; | ||
import com.concordium.sdk.responses.transactionstatus.TransactionResultEventType; | ||
import com.concordium.sdk.transactions.*; | ||
import com.concordium.sdk.types.*; | ||
import com.google.common.collect.Lists; | ||
import lombok.Getter; | ||
import lombok.val; | ||
import lombok.var; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* A client dedicated to the CIS2 <a href="https://proposals.concordium.software/CIS/cis-2.html">specification</a>. | ||
*/ | ||
@Getter | ||
public class Cis2Client { | ||
|
||
final ClientV2 client; | ||
private final ContractAddress contractAddress; | ||
private final InitName contractName; | ||
|
||
private Cis2Client(ClientV2 client, ContractAddress contractAddress, InitName contractName) { | ||
this.client = client; | ||
this.contractAddress = contractAddress; | ||
this.contractName = contractName; | ||
} | ||
|
||
/** | ||
* Construct a new {@link Cis2Client} with the provided {@link ClientV2} for the provided {@link ContractAddress} | ||
* | ||
* @param client client to use | ||
* @param address the address of the cis 2 contract | ||
* @return a cis2 client for interfacing with the provided contract | ||
*/ | ||
public static Cis2Client newClient(ClientV2 client, ContractAddress address) { | ||
val instanceInfo = client.getInstanceInfo(BlockQuery.LAST_FINAL, address); | ||
return new Cis2Client(client, address, InitName.from(instanceInfo.getName())); | ||
} | ||
|
||
/** | ||
* Perform a CIS2 transfer on the contract. | ||
* | ||
* @param sender address of the sender of the transaction. | ||
* @param signer signer of the transaction. | ||
* @param transfers the CIS2 transfers. | ||
* @return the transaction hash | ||
*/ | ||
public Hash transfer(AccountAddress sender, TransactionSigner signer, Energy maxEnergyCost, Cis2Transfer... transfers) { | ||
val listOfTransfers = Arrays.asList(transfers); | ||
val nextNonce = this.client.getAccountInfo(BlockQuery.LAST_FINAL, AccountQuery.from(sender)).getNonce(); | ||
val endpoint = ReceiveName.from(contractName, "transfer"); | ||
val parameters = SerializationUtils.serializeTransfers(listOfTransfers); | ||
return this.client.sendTransaction( | ||
TransactionFactory.newUpdateContract() | ||
.maxEnergyCost(maxEnergyCost.getValue()) | ||
.payload(UpdateContract.from(CCDAmount.from(0), this.contractAddress, endpoint, parameters)) | ||
.expiry(Expiry.createNew().addMinutes(5)) | ||
.nonce(nextNonce) | ||
.sender(sender) | ||
.signer(signer) | ||
.build()); | ||
} | ||
|
||
/** | ||
* Update the addresses of which the owner (sender of this transaction) operates. | ||
* | ||
* @param operatorUpdates the updates to carry out. The keys of the map correspond to the | ||
* addresses which the sender (owner) should or should not operate given | ||
* by the provided boolean. | ||
* @return the transaction hash | ||
*/ | ||
public Hash updateOperator(AccountAddress sender, TransactionSigner signer, Energy maxEnergyCost, Map<AbstractAddress, Boolean> operatorUpdates) { | ||
val nextNonce = this.client.getAccountInfo(BlockQuery.LAST_FINAL, AccountQuery.from(sender)).getNonce(); | ||
val endpoint = ReceiveName.from(contractName, "updateOperator"); | ||
val parameters = SerializationUtils.serializeUpdateOperators(operatorUpdates); | ||
return this.client.sendTransaction( | ||
TransactionFactory.newUpdateContract() | ||
.maxEnergyCost(maxEnergyCost.getValue()) | ||
.payload(UpdateContract.from(CCDAmount.from(0), this.contractAddress, endpoint, parameters)) | ||
.expiry(Expiry.createNew().addMinutes(5)) | ||
.nonce(nextNonce) | ||
.sender(sender) | ||
.signer(signer) | ||
.build()); | ||
|
||
} | ||
|
||
/** | ||
* Query the balance of token ids and associated {@link com.concordium.sdk.types.AbstractAddress} | ||
* | ||
* @param queries the token ids and addresses to query | ||
* @return the balances together with the queries used | ||
*/ | ||
public Map<BalanceQuery, TokenAmount> balanceOf(BalanceQuery... queries) { | ||
val listOfQueries = Arrays.asList(queries); | ||
val parameter = SerializationUtils.serializeBalanceOfParameter(listOfQueries); | ||
val endpoint = ReceiveName.from(contractName, "balanceOf"); | ||
val result = this.client.invokeInstance(InvokeInstanceRequest.from(BlockQuery.LAST_FINAL, this.contractAddress, CCDAmount.from(0), endpoint, parameter, Optional.empty())); | ||
if (result.getOutcome() == Outcome.REJECT) { | ||
throw new RuntimeException("balanceOf failed: " + result.getRejectReason().toString()); | ||
} | ||
val balances = SerializationUtils.deserializeTokenAmounts(result.getReturnValue()); | ||
val responses = new HashMap<BalanceQuery, TokenAmount>(); | ||
for (int i = 0; i < balances.length; i++) { | ||
responses.put(listOfQueries.get(i), balances[i]); | ||
} | ||
return responses; | ||
} | ||
|
||
/** | ||
* Query whether one or more owners are operators for one or more addresses. | ||
* | ||
* @param queries the addresses to query. | ||
* @return A map where the values indicate whether the specified owner was indeed operator of the supplied address. | ||
*/ | ||
public Map<OperatorQuery, Boolean> operatorOf(OperatorQuery... queries) { | ||
val listOfQueries = Arrays.asList(queries); | ||
val parameter = SerializationUtils.serializeOperatorOfParameter(listOfQueries); | ||
val endpoint = ReceiveName.from(contractName, "operatorOf"); | ||
val result = this.client.invokeInstance(InvokeInstanceRequest.from(BlockQuery.LAST_FINAL, this.contractAddress, CCDAmount.from(0), endpoint, parameter, Optional.empty())); | ||
if (result.getOutcome() == Outcome.REJECT) { | ||
throw new RuntimeException("operatorOf failed: " + result.getRejectReason().toString()); | ||
} | ||
val isOperatorOf = SerializationUtils.deserializeOperatorOfResponse(result.getReturnValue()); | ||
val responses = new HashMap<OperatorQuery, Boolean>(); | ||
for (int i = 0; i < isOperatorOf.length; i++) { | ||
responses.put(listOfQueries.get(i), isOperatorOf[i]); | ||
} | ||
return responses; | ||
} | ||
|
||
/** | ||
* Query the token metadata for each provided token id | ||
* | ||
* @param tokenIds the token ids to query | ||
* @return A map where the values indicate the token metadata responses for each {@link TokenId} | ||
*/ | ||
public Map<TokenId, TokenMetadata> tokenMetadata(TokenId... tokenIds) { | ||
val listOfQueries = Arrays.asList(tokenIds); | ||
val parameter = SerializationUtils.serializeTokenIds(listOfQueries); | ||
val endpoint = ReceiveName.from(contractName, "tokenMetadata"); | ||
val result = this.client.invokeInstance(InvokeInstanceRequest.from(BlockQuery.LAST_FINAL, this.contractAddress, CCDAmount.from(0), endpoint, parameter, Optional.empty())); | ||
if (result.getOutcome() == Outcome.REJECT) { | ||
throw new RuntimeException("tokenMetadata failed: " + result.getRejectReason().toString()); | ||
} | ||
val tokenMetadatas = SerializationUtils.deserializeTokenMetadatas(result.getReturnValue()); | ||
val responses = new HashMap<TokenId, TokenMetadata>(); | ||
for (int i = 0; i < tokenMetadatas.length; i++) { | ||
responses.put(listOfQueries.get(i), tokenMetadatas[i]); | ||
} | ||
return responses; | ||
} | ||
|
||
/** | ||
* Retrieve all events emitted from the CIS2 contract. | ||
* | ||
* @param from block to start from | ||
* @param to block to end from | ||
* @return the list of events. | ||
*/ | ||
public Iterator<Cis2EventWithMetadata> getEvents(BlockQuery from, BlockQuery to) { | ||
final long[] current = {this.client.getBlockInfo(from).getBlockHeight().getValue()}; | ||
val end = this.client.getBlockInfo(to).getBlockHeight().getValue(); | ||
if (current[0] >= end) { | ||
throw new IllegalArgumentException("Starting block must be before the end block"); | ||
} | ||
return new Cis2EventIterator(this, new Iterator<BlockQuery>() { | ||
@Override | ||
public boolean hasNext() { | ||
return end > current[0]; | ||
} | ||
|
||
@Override | ||
public BlockQuery next() { | ||
BlockQuery query = BlockQuery.HEIGHT(BlocksAtHeightRequest.newAbsolute(current[0])); | ||
current[0] = current[0] + 1; | ||
return query; | ||
} | ||
}); | ||
} | ||
|
||
|
||
/** | ||
* Get any events associated emitted from the specified CIS2 contract. | ||
* | ||
* @param queries blocks to query | ||
* @return the list of events. | ||
*/ | ||
public Iterator<Cis2EventWithMetadata> getEventsFor(BlockQuery... queries) { | ||
return new Cis2EventIterator(this, Lists.newArrayList(queries).iterator()); | ||
} | ||
|
||
/** | ||
* Get any events associated emitted from the specified CIS2 contract by the | ||
* supplied transaction hash. | ||
* | ||
* @param transactionHash the hash of the transaction to query outcome for. | ||
* @return the list of events which originated from the specified transaction hash. | ||
* @throws IllegalArgumentException if the transaction was not finalized. | ||
*/ | ||
public Iterator<Cis2EventWithMetadata> getEventsForFinalizedTransaction(Hash transactionHash) { | ||
val status = this.client.getBlockItemStatus(transactionHash); | ||
if (!status.getFinalizedBlockItem().isPresent()) { | ||
if (status.getCommittedBlockItem().isPresent()) { | ||
throw new IllegalArgumentException("Transaction was not finalized. But it was committed in block(s) " + status.getCommittedBlockItem().get().getSummaries().keySet()); | ||
} | ||
throw new IllegalArgumentException("Transaction was not finalized, but was " + status.getStatus().toString()); | ||
} | ||
val accumulator = new ArrayList<Cis2EventWithMetadata>(); | ||
val finalizedTransaction = status.getFinalizedBlockItem().get(); | ||
return new Cis2EventIterator(this, Lists.newArrayList(BlockQuery.HASH(finalizedTransaction.getBlockHash())).iterator()); | ||
} | ||
|
||
|
||
/** | ||
* Get any events associated with the CIS2 contract that this client is instantiated with. | ||
* | ||
* @param blockQuery the block to query events for. | ||
* @return The list of events if there are any. | ||
*/ | ||
private Iterator<Cis2EventWithMetadata> getEventsFor(BlockQuery blockQuery) { | ||
return new Cis2EventIterator(this, Lists.newArrayList(blockQuery).iterator()); | ||
} | ||
|
||
|
||
} |
46 changes: 46 additions & 0 deletions
46
concordium-sdk/src/main/java/com/concordium/sdk/cis2/Cis2Error.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.responses.transactionstatus.RejectReason; | ||
import com.concordium.sdk.responses.transactionstatus.RejectReasonRejectedInit; | ||
import com.concordium.sdk.responses.transactionstatus.RejectReasonRejectedReceive; | ||
import com.concordium.sdk.responses.transactionstatus.RejectReasonType; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
|
||
/** | ||
* Errors defined by the CIS2 standard https://proposals.concordium.software/CIS/cis-2.html#rejection-errors | ||
*/ | ||
@EqualsAndHashCode | ||
@ToString | ||
@Getter | ||
public class Cis2Error { | ||
|
||
/** | ||
* Type of the error. | ||
* See {@link Cis2Error.Type} for standardized variants. | ||
*/ | ||
private final Type type; | ||
|
||
private final int rawErrorCode; | ||
|
||
Cis2Error(Type type, int rawErrorCode) { | ||
this.type = type; | ||
this.rawErrorCode = rawErrorCode; | ||
} | ||
|
||
public static Cis2Error from(int errorCode) { | ||
if (errorCode == -42000001) return new Cis2Error(Type.INVALID_TOKEN_ID, errorCode); | ||
if (errorCode == -42000002) return new Cis2Error(Type.INSUFFICIENT_FUNDS, errorCode); | ||
if (errorCode == -42000003) return new Cis2Error(Type.UNAUTHORIZED, errorCode); | ||
return new Cis2Error(Type.CUSTOM, errorCode); | ||
} | ||
|
||
public enum Type { | ||
INVALID_TOKEN_ID, | ||
INSUFFICIENT_FUNDS, | ||
UNAUTHORIZED, | ||
CUSTOM | ||
} | ||
} |
119 changes: 119 additions & 0 deletions
119
concordium-sdk/src/main/java/com/concordium/sdk/cis2/Cis2EventIterator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.cis2.events.Cis2EventWithMetadata; | ||
import com.concordium.sdk.requests.BlockQuery; | ||
import com.concordium.sdk.responses.blockitemsummary.AccountTransactionDetails; | ||
import com.concordium.sdk.responses.blockitemsummary.Summary; | ||
import com.concordium.sdk.responses.blockitemsummary.Type; | ||
import com.concordium.sdk.responses.smartcontracts.ContractTraceElementType; | ||
import com.concordium.sdk.responses.transactionstatus.*; | ||
import com.concordium.sdk.transactions.Hash; | ||
import lombok.val; | ||
|
||
import java.util.*; | ||
|
||
class Cis2EventIterator implements Iterator<Cis2EventWithMetadata> { | ||
|
||
private final Cis2Client client; | ||
|
||
private final Iterator<BlockQuery> queries; | ||
|
||
private final Queue<Cis2EventWithMetadata> buffer = new LinkedList<>(); | ||
|
||
Cis2EventIterator(Cis2Client client, Iterator<BlockQuery> queries) { | ||
this.client = client; | ||
this.queries = queries; | ||
} | ||
|
||
@Override | ||
public boolean hasNext() { | ||
if (!queries.hasNext() && buffer.isEmpty()) return false; | ||
val query = queries.next(); | ||
tryAddEvents(query); | ||
if (!buffer.isEmpty()) { | ||
return true; | ||
} else { | ||
// deplete the queries buffer until we fill up the events buffer or there are no | ||
// more queries left. | ||
while (queries.hasNext()) { | ||
tryAddEvents(queries.next()); | ||
if (!buffer.isEmpty()) return true; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
@Override | ||
public Cis2EventWithMetadata next() { | ||
return buffer.remove(); | ||
} | ||
|
||
private void tryAddEvents(BlockQuery query) { | ||
val blockEvents = client.getClient().getBlockTransactionEvents(query); | ||
while (blockEvents.hasNext()) { | ||
buffer.addAll(extractCis2Events(query, blockEvents.next())); | ||
} | ||
} | ||
|
||
/** | ||
* Extract any events from the specified contract. | ||
* The events are added to the supplied accumulator. | ||
* | ||
* @param blockQuery a block identifier. | ||
* @param summary the transaction summary to extract from. | ||
* @return list of cis2 events emitted from the contract in the block specified. | ||
*/ | ||
private List<Cis2EventWithMetadata> extractCis2Events(BlockQuery blockQuery, Summary summary) { | ||
val accumulator = new ArrayList<Cis2EventWithMetadata>(); | ||
if (summary.getDetails().getType() == Type.ACCOUNT_TRANSACTION) { | ||
val details = summary.getDetails().getAccountTransactionDetails(); | ||
if (details.isSuccessful()) { | ||
accumulator.addAll(getSuccessEvents(details, blockQuery, summary.getTransactionHash())); | ||
} else { | ||
val rejectReason = details.getRejectReason(); | ||
if (rejectReason.getType() == RejectReasonType.REJECTED_RECEIVE) { | ||
val rejectReceive = (RejectReasonRejectedReceive) rejectReason; | ||
if (this.client.getContractAddress().equals(rejectReceive.getContractAddress())) { | ||
accumulator.add(Cis2EventWithMetadata.err(Cis2Error.from(rejectReceive.getRejectReason()), blockQuery, summary.getTransactionHash())); | ||
} | ||
} | ||
} | ||
} | ||
return accumulator; | ||
} | ||
|
||
/** | ||
* Parse events from the contract specified that originated from a successfully executed transaction. | ||
* The events can either origin from a contract update or a contract initialization. | ||
* | ||
* @param details the account transaction details | ||
* @param blockQuery the block identifier | ||
* @param transactionHash the origin transaction | ||
* @return list of parsed events. | ||
*/ | ||
private List<Cis2EventWithMetadata> getSuccessEvents(AccountTransactionDetails details, BlockQuery blockQuery, Hash transactionHash) { | ||
val accumulator = new ArrayList<Cis2EventWithMetadata>(); | ||
val eventType = details.getType(); | ||
if (eventType == TransactionResultEventType.CONTRACT_UPDATED) { | ||
val contractUpdatedEvents = details.getContractUpdated(); | ||
for (val e : contractUpdatedEvents) { | ||
if (e.getTraceType() == ContractTraceElementType.INSTANCE_UPDATED) { | ||
val updatedEvent = (ContractUpdated) e; | ||
if (this.client.getContractAddress().equals(updatedEvent.getAddress())) { | ||
for (val rawUpdateEvent : updatedEvent.getEvents()) { | ||
accumulator.add(Cis2EventWithMetadata.ok(SerializationUtils.deserializeCis2Event(rawUpdateEvent), blockQuery, transactionHash)); | ||
} | ||
} | ||
} | ||
} | ||
} else if (eventType == TransactionResultEventType.CONTRACT_INITIALIZED) { | ||
val contractInitialized = details.getContractInitialized(); | ||
if (this.client.getContractAddress().equals(contractInitialized.getAddress())) { | ||
for (val rawInitializeEvent : contractInitialized.getEvents()) { | ||
accumulator.add(Cis2EventWithMetadata.ok(SerializationUtils.deserializeCis2Event(rawInitializeEvent), blockQuery, transactionHash)); | ||
} | ||
} | ||
} | ||
return accumulator; | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
concordium-sdk/src/main/java/com/concordium/sdk/cis2/Cis2Transfer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* Object representing a CIS2 transfer https://proposals.concordium.software/CIS/cis-2.html#parameter | ||
* | ||
* Note that the maximum allowed size of the serialized parameter is 64kb. | ||
*/ | ||
@ToString | ||
@Getter | ||
@EqualsAndHashCode | ||
public class Cis2Transfer { | ||
/** | ||
* The token id. | ||
*/ | ||
private final TokenId tokenId; | ||
|
||
/** | ||
* The token amount. | ||
*/ | ||
private final TokenAmount tokenAmount; | ||
|
||
/** | ||
* Sender of the token. | ||
*/ | ||
private final AbstractAddress sender; | ||
|
||
/** | ||
* Receiver of the token. | ||
*/ | ||
private final AbstractAddress receiver; | ||
|
||
/** | ||
* Additional data. | ||
*/ | ||
private final byte[] additionalData; | ||
|
||
public Cis2Transfer(TokenId tokenId, TokenAmount tokenAmount, AbstractAddress sender, AbstractAddress receiver, byte[] additionalData) { | ||
this.tokenId = tokenId; | ||
this.tokenAmount = tokenAmount; | ||
this.sender = sender; | ||
this.receiver = receiver; | ||
this.additionalData = additionalData; | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
concordium-sdk/src/main/java/com/concordium/sdk/cis2/OperatorQuery.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An object used for querying the operator of a specific {@link AbstractAddress} | ||
* See <a href="https://proposals.concordium.software/CIS/cis-2.html#balanceof">here</a> for the specification. | ||
*/ | ||
@Getter | ||
@ToString | ||
@EqualsAndHashCode | ||
public class OperatorQuery { | ||
/** | ||
* Potential operator | ||
*/ | ||
private final AbstractAddress owner; | ||
|
||
/** | ||
* The address which operates {@link OperatorQuery#owner}. | ||
*/ | ||
private final AbstractAddress address; | ||
|
||
public OperatorQuery(AbstractAddress owner, AbstractAddress address) { | ||
this.owner = owner; | ||
this.address = address; | ||
} | ||
} |
272 changes: 272 additions & 0 deletions
272
concordium-sdk/src/main/java/com/concordium/sdk/cis2/SerializationUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.cis2.events.*; | ||
import com.concordium.sdk.transactions.Parameter; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import com.concordium.sdk.types.AccountAddress; | ||
import com.concordium.sdk.types.ContractAddress; | ||
import com.concordium.sdk.types.UInt16; | ||
import lombok.SneakyThrows; | ||
import lombok.val; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public class SerializationUtils { | ||
|
||
/** | ||
* Size of a serialized {@link AccountAddress} | ||
* Tag + the bytes of the account address. | ||
*/ | ||
private static final int ACCOUNT_ADDRESS_SIZE = 1 + AccountAddress.BYTES; | ||
|
||
/** | ||
* Size of a serialized {@link ContractAddress} | ||
* Tag + 8 bytes for the {@link ContractAddress#getIndex()} + 8 bytes for the {@link ContractAddress#getSubIndex()}. | ||
*/ | ||
private static final int CONTRACT_ADDRESS_SIZE = 1 + (2 * 8); | ||
|
||
@SneakyThrows | ||
public static Parameter serializeTransfers(List<Cis2Transfer> listOfTransfers) { | ||
val bos = new ByteArrayOutputStream(); | ||
bos.write(UInt16.from(listOfTransfers.size()).getBytesLittleEndian()); | ||
for (Cis2Transfer transfer : listOfTransfers) { | ||
bos.write(SerializationUtils.serializeTokenId(transfer.getTokenId())); | ||
bos.write(transfer.getTokenAmount().encode()); | ||
bos.write(SerializationUtils.serializeAddress(transfer.getSender())); | ||
bos.write(SerializationUtils.serializeAddress(transfer.getReceiver())); | ||
if (Objects.isNull(transfer.getAdditionalData()) || transfer.getAdditionalData().length == 0) { | ||
bos.write(UInt16.from(0).getBytesLittleEndian()); | ||
} else { | ||
bos.write(UInt16.from(transfer.getAdditionalData().length).getBytesLittleEndian()); | ||
bos.write(transfer.getAdditionalData()); | ||
} | ||
} | ||
return Parameter.from(bos.toByteArray()); | ||
} | ||
|
||
@SneakyThrows | ||
static Parameter serializeBalanceOfParameter(Collection<BalanceQuery> queries) { | ||
val bos = new ByteArrayOutputStream(); | ||
// lengths are stored as little endian. | ||
bos.write(UInt16.from(queries.size()).getBytesLittleEndian()); | ||
for (BalanceQuery balanceQuery : queries) { | ||
bos.write(SerializationUtils.serializeTokenId(balanceQuery.getTokenId())); | ||
bos.write(SerializationUtils.serializeAddress(balanceQuery.getAddress())); | ||
} | ||
return Parameter.from(bos.toByteArray()); | ||
} | ||
|
||
public static TokenAmount[] deserializeTokenAmounts(byte[] returnValue) { | ||
val resultBuffer = ByteBuffer.wrap(returnValue); | ||
// lengths are stored as little endian. | ||
resultBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
val noOfOutputs = UInt16.from(resultBuffer.getShort()).getValue(); | ||
resultBuffer.order(ByteOrder.BIG_ENDIAN); | ||
val outputs = new TokenAmount[noOfOutputs]; | ||
for (int i = 0; i < noOfOutputs; i++) { | ||
outputs[i] = TokenAmount.decode(resultBuffer); | ||
} | ||
return outputs; | ||
} | ||
|
||
@SneakyThrows | ||
static Parameter serializeOperatorOfParameter(Collection<OperatorQuery> queries) { | ||
val bos = new ByteArrayOutputStream(); | ||
// lengths are stored as little endian. | ||
bos.write(UInt16.from(queries.size()).getBytesLittleEndian()); | ||
for (OperatorQuery operatorQuery : queries) { | ||
bos.write(SerializationUtils.serializeAddress(operatorQuery.getOwner())); | ||
bos.write(SerializationUtils.serializeAddress(operatorQuery.getAddress())); | ||
} | ||
return Parameter.from(bos.toByteArray()); | ||
} | ||
|
||
static boolean[] deserializeOperatorOfResponse(byte[] returnValue) { | ||
val resultBuffer = ByteBuffer.wrap(returnValue); | ||
// lengths are stored as little endian. | ||
resultBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
val noOfOutputs = UInt16.from(resultBuffer.getShort()).getValue(); | ||
resultBuffer.order(ByteOrder.BIG_ENDIAN); | ||
val outputs = new boolean[noOfOutputs]; | ||
for (int i = 0; i < noOfOutputs; i++) { | ||
outputs[i] = resultBuffer.get() != 0; | ||
} | ||
return outputs; | ||
} | ||
|
||
@SneakyThrows | ||
static Parameter serializeTokenIds(List<TokenId> listOfQueries) { | ||
val bos = new ByteArrayOutputStream(); | ||
// lengths are stored as little endian. | ||
bos.write(UInt16.from(listOfQueries.size()).getBytesLittleEndian()); | ||
for (TokenId tokenId : listOfQueries) { | ||
bos.write(SerializationUtils.serializeTokenId(tokenId)); | ||
} | ||
return Parameter.from(bos.toByteArray()); | ||
} | ||
|
||
@SneakyThrows | ||
static TokenMetadata[] deserializeTokenMetadatas(byte[] returnValue) { | ||
val resultBuffer = ByteBuffer.wrap(returnValue); | ||
// lengths are stored as little endian. | ||
resultBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
val noOfOutputs = UInt16.from(resultBuffer.getShort()).getValue(); | ||
val outputs = new TokenMetadata[noOfOutputs]; | ||
for (int i = 0; i < noOfOutputs; i++) { | ||
outputs[i] = deserializeTokenMetadata(resultBuffer); | ||
resultBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
} | ||
return outputs; | ||
} | ||
|
||
private static TokenMetadata deserializeTokenMetadata(ByteBuffer resultBuffer) throws MalformedURLException { | ||
resultBuffer.order(ByteOrder.LITTLE_ENDIAN); | ||
val urlLength = UInt16.from(resultBuffer.getShort()).getValue(); | ||
resultBuffer.order(ByteOrder.BIG_ENDIAN); | ||
val urlBytes = new byte[urlLength]; | ||
resultBuffer.get(urlBytes); | ||
val hasChecksum = resultBuffer.get() != 0; | ||
byte[] checksumBuffer = null; | ||
if (hasChecksum) { | ||
checksumBuffer = new byte[32]; | ||
resultBuffer.get(checksumBuffer); | ||
} | ||
return new TokenMetadata(new URL(new String(urlBytes, StandardCharsets.UTF_8)), checksumBuffer); | ||
} | ||
|
||
@SneakyThrows | ||
static byte[] serializeTokenId(TokenId tokenId) { | ||
// size of token + serialized token id. | ||
val tokenSize = tokenId.getSize(); | ||
val buffer = ByteBuffer.allocate(1 + tokenSize); | ||
buffer.put((byte) tokenSize); | ||
if (tokenSize != 0) { | ||
buffer.put(tokenId.getBytes()); | ||
} | ||
return buffer.array(); | ||
} | ||
|
||
static byte[] serializeAddress(AbstractAddress address) { | ||
if (address instanceof AccountAddress) { | ||
val accountAddress = (AccountAddress) address; | ||
val buffer = ByteBuffer.allocate(ACCOUNT_ADDRESS_SIZE); | ||
buffer.put((byte) 0); // tag | ||
buffer.put(accountAddress.getBytes()); | ||
return buffer.array(); | ||
} else if (address instanceof ContractAddress) { | ||
ContractAddress contractAddress = (ContractAddress) address; | ||
val buffer = ByteBuffer.allocate(CONTRACT_ADDRESS_SIZE); | ||
buffer.put((byte) 1); // tag | ||
// index and sub-index are stored as little endian. | ||
buffer.order(ByteOrder.LITTLE_ENDIAN); | ||
buffer.putLong(contractAddress.getIndex()); | ||
buffer.putLong(contractAddress.getSubIndex()); | ||
return buffer.array(); | ||
} | ||
throw new IllegalArgumentException("AbstractAddress must be either an account address or contract address"); | ||
} | ||
|
||
@SneakyThrows | ||
public static Parameter serializeUpdateOperators(Map<AbstractAddress, Boolean> operatorUpdates) { | ||
val bos = new ByteArrayOutputStream(); | ||
bos.write(UInt16.from(operatorUpdates.size()).getBytesLittleEndian()); | ||
for (AbstractAddress address : operatorUpdates.keySet()) { | ||
bos.write((byte) (operatorUpdates.get(address) ? 1 : 0)); | ||
bos.write(SerializationUtils.serializeAddress(address)); | ||
} | ||
return Parameter.from(bos.toByteArray()); | ||
} | ||
|
||
public static Cis2Event deserializeCis2Event(byte[] eventBytes) { | ||
val buffer = ByteBuffer.wrap(eventBytes); | ||
val tag = buffer.get(); | ||
val eventType = Cis2Event.Type.parse(tag); | ||
switch (eventType) { | ||
case TRANSFER: | ||
return SerializationUtils.deserializeTransferEvent(buffer); | ||
case MINT: | ||
return SerializationUtils.deserializeMintEvent(buffer); | ||
case BURN: | ||
return SerializationUtils.deserializeBurnEvent(buffer); | ||
case UPDATE_OPERATOR_OF: | ||
return SerializationUtils.deserializeUpdateOperatorOfEvent(buffer); | ||
case TOKEN_METADATA: | ||
return SerializationUtils.deserializeTokenMetadataEvent(buffer); | ||
case CUSTOM: | ||
return SerializationUtils.deserializeCustomEvent(tag, buffer); | ||
} | ||
throw new IllegalArgumentException("Malformed CIS2 event"); | ||
} | ||
|
||
private static Cis2Event deserializeCustomEvent(byte tag, ByteBuffer buffer) { | ||
return new CustomEvent(tag, buffer.array()); | ||
} | ||
|
||
@SneakyThrows | ||
private static Cis2Event deserializeTokenMetadataEvent(ByteBuffer buffer) { | ||
val tokenId = deserializeTokenId(buffer); | ||
val tokenMetadata = deserializeTokenMetadata(buffer); | ||
return new TokenMetadataEvent(tokenId, tokenMetadata); | ||
} | ||
|
||
private static Cis2Event deserializeUpdateOperatorOfEvent(ByteBuffer buffer) { | ||
val isOperator = buffer.get() != 0; | ||
val owner = deserializeAddress(buffer); | ||
val operator = deserializeAddress(buffer); | ||
return new UpdateOperatorEvent(isOperator, owner, operator); | ||
} | ||
|
||
private static Cis2Event deserializeBurnEvent(ByteBuffer buffer) { | ||
val tokenId = deserializeTokenId(buffer); | ||
val tokenAmount = TokenAmount.decode(buffer); | ||
val owner = deserializeAddress(buffer); | ||
return new BurnEvent(tokenId, tokenAmount, owner); | ||
} | ||
|
||
private static Cis2Event deserializeMintEvent(ByteBuffer buffer) { | ||
val tokenId = deserializeTokenId(buffer); | ||
val tokenAmount = TokenAmount.decode(buffer); | ||
val owner = deserializeAddress(buffer); | ||
return new MintEvent(tokenId, tokenAmount, owner); | ||
} | ||
|
||
public static Cis2Event deserializeTransferEvent(ByteBuffer buffer) { | ||
val tokenId = deserializeTokenId(buffer); | ||
val tokenAmount = TokenAmount.decode(buffer); | ||
val from = deserializeAddress(buffer); | ||
val to = deserializeAddress(buffer); | ||
return new TransferEvent(tokenId, tokenAmount, from, to); | ||
} | ||
|
||
@SneakyThrows | ||
static TokenId deserializeTokenId(ByteBuffer buffer) { | ||
byte tokenLength = buffer.get(); | ||
val tokenBuffer = new byte[tokenLength]; | ||
buffer.get(tokenBuffer); | ||
return TokenId.from(tokenBuffer); | ||
} | ||
|
||
static AbstractAddress deserializeAddress(ByteBuffer buffer) { | ||
byte tag = buffer.get(); | ||
if (tag == 0) { | ||
val addressBuffer = new byte[AccountAddress.BYTES]; | ||
buffer.get(addressBuffer); | ||
return AccountAddress.from(addressBuffer); | ||
} | ||
if (tag == 1) { | ||
long index = buffer.getLong(); | ||
long subIndex = buffer.getLong(); | ||
return ContractAddress.from(index, subIndex); | ||
} | ||
throw new IllegalArgumentException("Malformed address"); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
concordium-sdk/src/main/java/com/concordium/sdk/cis2/TokenAmount.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import lombok.*; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.math.BigInteger; | ||
import java.nio.ByteBuffer; | ||
|
||
/** | ||
* An amount as specified in the CIS2 specification. | ||
* https://proposals.concordium.software/CIS/cis-2.html#tokenamount | ||
* <p> | ||
* It is an unsigned integer where the max value is 2^256 - 1. | ||
*/ | ||
@EqualsAndHashCode | ||
@ToString | ||
public class TokenAmount { | ||
|
||
/** | ||
* The maximum value of a CIS2 token as per the standard 2^256 - 1. | ||
*/ | ||
public static final BigInteger MAX_VALUE = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16); | ||
|
||
@Getter | ||
private final BigInteger amount; | ||
|
||
private TokenAmount(BigInteger value) { | ||
if (value.compareTo(MAX_VALUE) > 0) throw new IllegalArgumentException("TokenAmount exceeds max value"); | ||
this.amount = value; | ||
} | ||
|
||
public static TokenAmount from(long value) { | ||
return new TokenAmount(BigInteger.valueOf(value)); | ||
} | ||
|
||
public static TokenAmount from(String value) { | ||
if (value.startsWith("-")) throw new IllegalArgumentException("TokenAmount must be positive"); | ||
return new TokenAmount(new BigInteger(value)); | ||
} | ||
|
||
/** | ||
* Encode the {@link TokenAmount} in LEB128 unsigned format. | ||
* | ||
* @return the serialized token amount | ||
* @throws RuntimeException if the resulting byte array would exceed 37 bytes. | ||
*/ | ||
@SneakyThrows | ||
public byte[] encode() { | ||
if (this.amount.equals(BigInteger.ZERO)) return new byte[]{0}; | ||
val bos = new ByteArrayOutputStream(); | ||
var value = this.amount; | ||
// Loop until the most significant byte is zero or less | ||
while (value.compareTo(BigInteger.ZERO) > 0) { | ||
// Take the 7 least significant bits of the current value and set the MSB | ||
var currentByte = value.and(BigInteger.valueOf(0x7F)).byteValue(); | ||
value = value.shiftRight(7); | ||
if (value.compareTo(BigInteger.ZERO) != 0) { | ||
currentByte |= 0x80; // Set the MSB to 1 to indicate there are more bytes to come | ||
} | ||
bos.write(currentByte); | ||
if (bos.size() > 37) | ||
throw new IllegalArgumentException("Invalid encoding of TokenAmount. Must not exceed 37 byes."); | ||
} | ||
return bos.toByteArray(); | ||
} | ||
|
||
/** | ||
* Deserialize a {@link TokenAmount} from the provided buffer. | ||
* This function assumes that the token amounts are LEB128U encoded. | ||
* | ||
* @param buffer the buffer to read from. | ||
* @return the parsed {@link TokenAmount} | ||
* @throws RuntimeException if the encoding is more than 37 bytes. | ||
*/ | ||
public static TokenAmount decode(ByteBuffer buffer) { | ||
var result = BigInteger.ZERO; | ||
int shift = 0; | ||
int count = 0; | ||
while (true) { | ||
if (count > 37) | ||
throw new IllegalArgumentException("Tried to decode a TokenAmount which consists of more than 37 bytes."); | ||
byte b = buffer.get(); | ||
BigInteger byteValue = BigInteger.valueOf(b & 0x7F); // Mask to get 7 least significant bits | ||
result = result.or(byteValue.shiftLeft(shift)); | ||
if ((b & 0x80) == 0) { | ||
break; // If MSB is 0, this is the last byte | ||
} | ||
shift += 7; | ||
count++; | ||
} | ||
return new TokenAmount(result); | ||
} | ||
|
||
|
||
} |
98 changes: 98 additions & 0 deletions
98
concordium-sdk/src/main/java/com/concordium/sdk/cis2/TokenId.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.types.UInt16; | ||
import com.concordium.sdk.types.UInt32; | ||
import com.concordium.sdk.types.UInt64; | ||
import com.google.common.collect.Lists; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
import lombok.val; | ||
import org.apache.commons.lang3.ArrayUtils; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
/** | ||
* A simple wrapper around the actual token id. | ||
* https://proposals.concordium.software/CIS/cis-2.html#tokenid | ||
* The token id simply consists of 0 or more bytes. | ||
*/ | ||
@EqualsAndHashCode | ||
@Getter | ||
@ToString | ||
public class TokenId { | ||
|
||
private static final int MAX_BYTES_SIZE = 255; | ||
|
||
private final byte[] bytes; | ||
|
||
private TokenId(byte[] bytes) { | ||
this.bytes = bytes; | ||
} | ||
|
||
/** | ||
* Construct a minimum token id i.e., the empty one. | ||
* @return the token id | ||
*/ | ||
public static TokenId min() { | ||
return new TokenId(new byte[0]); | ||
} | ||
|
||
/** | ||
* Get the size of the token (in number of bytes) | ||
* @return the size of the token | ||
*/ | ||
public int getSize() { | ||
return this.bytes.length; | ||
} | ||
|
||
/** | ||
* Create a token from the provided byte array | ||
* @param bytes the token id in raw bytes | ||
* @return the token id | ||
*/ | ||
public static TokenId from(byte[] bytes) { | ||
if (bytes.length > MAX_BYTES_SIZE) { | ||
throw new IllegalArgumentException("TokenId supersedes max allowed size."); | ||
} | ||
return new TokenId(bytes); | ||
} | ||
|
||
/** | ||
* Create a token id from the {@link UInt16} in little endian | ||
* @param value the token id | ||
* @return a new token id | ||
*/ | ||
public static TokenId from(UInt16 value) { | ||
val tokenBytes = value.getBytes(); | ||
ArrayUtils.reverse(tokenBytes); | ||
return new TokenId(tokenBytes); | ||
} | ||
|
||
/** | ||
* Create a token id from the {@link UInt32} in little endian | ||
* @param value the token id | ||
* @return a new token id | ||
*/ | ||
public static TokenId from(UInt32 value) { | ||
val tokenBytes = value.getBytes(); | ||
ArrayUtils.reverse(tokenBytes); | ||
return new TokenId(tokenBytes); | ||
} | ||
|
||
/** | ||
*Create a token id from the {@link UInt64} in little endian | ||
* @param value the token id | ||
* @return a new token id | ||
*/ | ||
public static TokenId from(UInt64 value) { | ||
val tokenBytes = value.getBytes(); | ||
ArrayUtils.reverse(tokenBytes); | ||
return new TokenId(tokenBytes); | ||
} | ||
|
||
} |
41 changes: 41 additions & 0 deletions
41
concordium-sdk/src/main/java/com/concordium/sdk/cis2/TokenMetadata.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
import java.net.URL; | ||
import java.util.Optional; | ||
|
||
/** | ||
* A single response to a {@link Cis2Client#tokenMetadata(String...)} query. | ||
*/ | ||
@ToString | ||
@EqualsAndHashCode | ||
public class TokenMetadata { | ||
|
||
/** | ||
* The metadata url. | ||
*/ | ||
@Getter | ||
private final URL metadataUrl; | ||
|
||
/** | ||
* An optional checksum of the metadata url. | ||
*/ | ||
private final byte[] checksum; | ||
|
||
|
||
public TokenMetadata(URL metadataUrl, byte[] checksum) { | ||
this.metadataUrl = metadataUrl; | ||
this.checksum = checksum; | ||
} | ||
|
||
/** | ||
* Get the checksum if available. | ||
* @return the checksum | ||
*/ | ||
public Optional<byte[]> getChecksum() { | ||
return Optional.ofNullable(this.checksum); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/BurnEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.cis2.TokenAmount; | ||
import com.concordium.sdk.cis2.TokenId; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An event emitted by a CIS2 compliant smart contract when one or more tokens have been burned. | ||
* https://proposals.concordium.software/CIS/cis-2.html#burnevent | ||
*/ | ||
@Getter | ||
@ToString | ||
@EqualsAndHashCode | ||
public class BurnEvent implements Cis2Event { | ||
|
||
/** | ||
* The token that was burned. | ||
*/ | ||
private final TokenId tokenId; | ||
/** | ||
* The amount of tokens that was burned. | ||
*/ | ||
private final TokenAmount tokenAmount; | ||
/** | ||
* The owner of the token | ||
*/ | ||
private final AbstractAddress owner; | ||
|
||
public BurnEvent(TokenId tokenId, TokenAmount tokenAmount, AbstractAddress owner) { | ||
this.tokenId = tokenId; | ||
this.tokenAmount = tokenAmount; | ||
this.owner = owner; | ||
} | ||
|
||
@Override | ||
public Type getType() { | ||
return Type.BURN; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/Cis2Event.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
/** | ||
* A common API for an event emitted by a CIS2 compliant contract. | ||
*/ | ||
public interface Cis2Event { | ||
/** | ||
* Get the type of the CIS2 event. | ||
*/ | ||
Type getType(); | ||
|
||
|
||
/** | ||
* Types of events that are supported by the CIS2 specification | ||
*/ | ||
enum Type { | ||
TRANSFER, | ||
MINT, | ||
BURN, | ||
UPDATE_OPERATOR_OF, | ||
TOKEN_METADATA, | ||
CUSTOM; | ||
|
||
/** | ||
* Parse the type of CIS2 event. | ||
* @param tag the tag of the event. | ||
* @return the resulting {@link Type} | ||
*/ | ||
public static Type parse(byte tag) { | ||
if (tag == -1) return TRANSFER; | ||
if (tag == -2) return MINT; | ||
if (tag == -3) return BURN; | ||
if (tag == -4) return UPDATE_OPERATOR_OF; | ||
if (tag == -5) return TOKEN_METADATA; | ||
return CUSTOM; | ||
} | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/Cis2EventWithMetadata.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.cis2.Cis2Error; | ||
import com.concordium.sdk.requests.BlockQuery; | ||
import com.concordium.sdk.transactions.Hash; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
import java.util.Objects; | ||
|
||
/** | ||
* An event emitted from a CIS2 compliant contract with some metadata. | ||
*/ | ||
@EqualsAndHashCode | ||
@ToString | ||
@Getter | ||
public class Cis2EventWithMetadata { | ||
|
||
/** | ||
* The event of a successfully executed transaction. | ||
* This is only present if the transaction was successfully executed. | ||
*/ | ||
private final Cis2Event event; | ||
|
||
/** | ||
* The error of a failed transaction. | ||
* This is only present if the tranasction failed. | ||
*/ | ||
private final Cis2Error error; | ||
|
||
/** | ||
* The block of which the event was emitted in. | ||
*/ | ||
private final BlockQuery blockIdentifier; | ||
|
||
/** | ||
* The hash of the transaction that caused the event. | ||
*/ | ||
private final Hash transactionHashOrigin; | ||
|
||
private Cis2EventWithMetadata(Cis2Event event, Cis2Error error, BlockQuery blockIdentifier, Hash origin) { | ||
this.event = event; | ||
this.error = error; | ||
this.blockIdentifier = blockIdentifier; | ||
this.transactionHashOrigin = origin; | ||
} | ||
|
||
/** | ||
* A flag that indicates whether the event retains an | ||
* {@link Cis2Event} (as a result of a successfully executed transaction) | ||
* or a {@link Cis2Error} as a result of a failed transaction. | ||
*/ | ||
public boolean isSuccessfull() { | ||
return Objects.isNull(error); | ||
} | ||
|
||
public static Cis2EventWithMetadata ok(Cis2Event event, BlockQuery blockIdentifier, Hash origin) { | ||
return new Cis2EventWithMetadata(event, null, blockIdentifier, origin); | ||
} | ||
|
||
public static Cis2EventWithMetadata err(Cis2Error error, BlockQuery blockIdentifier, Hash origin) { | ||
return new Cis2EventWithMetadata(null, error, blockIdentifier, origin); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/CustomEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* A custom event emitted from a CIS2 compliant contract. | ||
*/ | ||
@EqualsAndHashCode | ||
@ToString | ||
@Getter | ||
public class CustomEvent implements Cis2Event { | ||
|
||
private final byte tag; | ||
private final byte[] data; | ||
|
||
public CustomEvent(byte tag, byte[] data) { | ||
this.tag = tag; | ||
this.data = data; | ||
} | ||
|
||
@Override | ||
public Type getType() { | ||
return Type.CUSTOM; | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/MintEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.cis2.TokenAmount; | ||
import com.concordium.sdk.cis2.TokenId; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An event emitted by a CIS2 compliant smart contract when one or more tokens have been minted. | ||
* https://proposals.concordium.software/CIS/cis-2.html#mintevent | ||
*/ | ||
@Getter | ||
@ToString | ||
@EqualsAndHashCode | ||
public class MintEvent implements Cis2Event { | ||
|
||
/** | ||
* ID of the token that was minted. | ||
*/ | ||
private final TokenId tokenId; | ||
|
||
/** | ||
* Amount of tokens that was minted. | ||
*/ | ||
private final TokenAmount tokenAmount; | ||
|
||
/** | ||
* The owner of the minted tokens. | ||
*/ | ||
private final AbstractAddress owner; | ||
|
||
public MintEvent(TokenId tokenId, TokenAmount tokenAmount, AbstractAddress owner) { | ||
this.tokenId = tokenId; | ||
this.tokenAmount = tokenAmount; | ||
this.owner = owner; | ||
} | ||
|
||
@Override | ||
public Type getType() { | ||
return Type.MINT; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/TokenMetadataEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.cis2.TokenId; | ||
import com.concordium.sdk.cis2.TokenMetadata; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An event that is emitted by a CIS2 compliant contract when a | ||
* particular token has its metadata url set. | ||
*/ | ||
@Getter | ||
@EqualsAndHashCode | ||
@ToString | ||
public class TokenMetadataEvent implements Cis2Event { | ||
|
||
/** | ||
* The token id which was queried | ||
*/ | ||
private final TokenId tokenId; | ||
|
||
/** | ||
* The new metadata url for the token. | ||
*/ | ||
private final TokenMetadata metadataUrl; | ||
|
||
public TokenMetadataEvent(TokenId tokenId, TokenMetadata metadataUrl) { | ||
this.tokenId = tokenId; | ||
this.metadataUrl = metadataUrl; | ||
} | ||
|
||
|
||
@Override | ||
public Type getType() { | ||
return Type.TOKEN_METADATA; | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/TransferEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.cis2.TokenAmount; | ||
import com.concordium.sdk.cis2.TokenId; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An event that is emitted from a CIS2 compliant contract when a token has been transferred. | ||
* https://proposals.concordium.software/CIS/cis-2.html#transferevent | ||
*/ | ||
@Getter | ||
@EqualsAndHashCode | ||
@ToString | ||
public class TransferEvent implements Cis2Event { | ||
|
||
/** | ||
* The id of the token(s) that were transferred. | ||
*/ | ||
private final TokenId tokenId; | ||
|
||
/** | ||
* The amount of tokens that was transferred. | ||
*/ | ||
private final TokenAmount tokenAmount; | ||
|
||
/** | ||
* The previous owner of the token(s). | ||
*/ | ||
private final AbstractAddress from; | ||
|
||
/** | ||
* The new owner of the token(s). | ||
*/ | ||
private final AbstractAddress to; | ||
|
||
public TransferEvent(TokenId tokenId, TokenAmount tokenAmount, AbstractAddress from, AbstractAddress to) { | ||
this.tokenId = tokenId; | ||
this.tokenAmount = tokenAmount; | ||
this.from = from; | ||
this.to = to; | ||
} | ||
|
||
@Override | ||
public Type getType() { | ||
return Type.TRANSFER; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
concordium-sdk/src/main/java/com/concordium/sdk/cis2/events/UpdateOperatorEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.concordium.sdk.cis2.events; | ||
|
||
import com.concordium.sdk.types.AbstractAddress; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
/** | ||
* An event that is emitted from a CIS2 compliant contract when the operator has been updated | ||
* for a particular address. | ||
* https://proposals.concordium.software/CIS/cis-2.html#updateoperatorevent | ||
*/ | ||
@Getter | ||
@EqualsAndHashCode | ||
@ToString | ||
public class UpdateOperatorEvent implements Cis2Event { | ||
|
||
/** | ||
* Whether an operator was added or removed. | ||
*/ | ||
private final boolean isOperator; | ||
|
||
/** | ||
* Invoker of the updateOperatorOf | ||
*/ | ||
private final AbstractAddress owner; | ||
|
||
/** | ||
* The operator that is either added or removed. | ||
*/ | ||
private final AbstractAddress operator; | ||
|
||
public UpdateOperatorEvent(boolean isOperator, AbstractAddress owner, AbstractAddress operator) { | ||
this.isOperator = isOperator; | ||
this.owner = owner; | ||
this.operator = operator; | ||
} | ||
|
||
@Override | ||
public Type getType() { | ||
return Type.UPDATE_OPERATOR_OF; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
282 changes: 208 additions & 74 deletions
282
...rc/main/java/com/concordium/sdk/responses/blockitemsummary/AccountTransactionDetails.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
concordium-sdk/src/test/java/com/concordium/sdk/cis2/Cis2SerializationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import com.concordium.sdk.cis2.events.*; | ||
import com.concordium.sdk.transactions.Parameter; | ||
import com.concordium.sdk.types.AbstractAddress; | ||
import com.concordium.sdk.types.AccountAddress; | ||
import com.concordium.sdk.types.UInt16; | ||
import lombok.SneakyThrows; | ||
import lombok.val; | ||
import org.apache.commons.codec.binary.Hex; | ||
import org.bouncycastle.util.Arrays; | ||
import org.junit.Test; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
|
||
import static org.junit.Assert.*; | ||
|
||
public class Cis2SerializationTest { | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testDeserializeTransferEvent() { | ||
val event = Hex.decodeHex("ff00d0ff9f05009e15fc57bbe167411d4d9c0686e31e8e937d751625972f7c566de4a97f650dc500fd3dd07c83e42461554cf0dd90d73c1ff04531fc2b9c90b9762df8793319e48d"); | ||
val cis2Event = SerializationUtils.deserializeCis2Event(event); | ||
assertEquals(Cis2Event.Type.TRANSFER, cis2Event.getType()); | ||
val transferEvent = (TransferEvent) cis2Event; | ||
assertEquals(TokenId.min(), transferEvent.getTokenId()); | ||
assertEquals(TokenAmount.from(11010000), transferEvent.getTokenAmount()); | ||
assertEquals(AccountAddress.from("49NGYqmPtbuCkXSQt7298mL6Xp52UpSR4U2jVzJjKW9P3b3whw"), transferEvent.getFrom()); | ||
assertEquals(AccountAddress.from("4sGtbuGKgakv5pKSMsy3CEQbW3sn2PbTzTVLZLA6zxX5bB3C5a"), transferEvent.getTo()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testDeserializeMintEvent() { | ||
val event = Hex.decodeHex("fe00c0843d0097567a23128fb54e3f9fa2f0236f7468ed3c61c4f66a92907d4162f5742ec3ca"); | ||
val cis2Event = SerializationUtils.deserializeCis2Event(event); | ||
assertEquals(Cis2Event.Type.MINT, cis2Event.getType()); | ||
val mintEvent = (MintEvent) cis2Event; | ||
assertEquals(TokenId.min(), mintEvent.getTokenId()); | ||
assertEquals(TokenAmount.from(1000000), mintEvent.getTokenAmount()); | ||
assertEquals(AccountAddress.from("46Pu3wVfURgihzAXoDxMxWucyFo5irXvaEmacNgeK7i49MKyiD"), mintEvent.getOwner()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testDeserializeBurnEvent() { | ||
val event = Hex.decodeHex("fd00c0843d0097567a23128fb54e3f9fa2f0236f7468ed3c61c4f66a92907d4162f5742ec3ca"); | ||
val cis2Event = SerializationUtils.deserializeCis2Event(event); | ||
assertEquals(Cis2Event.Type.BURN, cis2Event.getType()); | ||
val burnEvent = (BurnEvent) cis2Event; | ||
assertEquals(TokenId.min(), burnEvent.getTokenId()); | ||
assertEquals(TokenAmount.from(1000000), burnEvent.getTokenAmount()); | ||
assertEquals(AccountAddress.from("46Pu3wVfURgihzAXoDxMxWucyFo5irXvaEmacNgeK7i49MKyiD"), burnEvent.getOwner()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testDeserializeUpdateOperatorOfEvent() { | ||
val event = Hex.decodeHex("fc0100023302aff95a61564321285047ebe9eb4dbc5a17b7ae6da94f66d91eaa062c3a0081f65db5aab2469518cadb87d4f3d8f3b01773dfe0da103353a80ad8118e393e"); | ||
val cis2Event = SerializationUtils.deserializeCis2Event(event); | ||
assertEquals(Cis2Event.Type.UPDATE_OPERATOR_OF, cis2Event.getType()); | ||
val updateOperatorOfEvent = (UpdateOperatorEvent) cis2Event; | ||
assertEquals(AccountAddress.from("2xiMWLvvDjfsYoVHTVJimq68u3qhsMwHbf7QK7iRTYTBiKkJgw"), updateOperatorOfEvent.getOwner()); | ||
assertEquals(AccountAddress.from("3vytfBE9RMZz57dhzKsRuniqeErBC8z4N52ef4LYFFib6XvA1E"), updateOperatorOfEvent.getOperator()); | ||
assertTrue(updateOperatorOfEvent.isOperator()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testSerializeTransfer() { | ||
// this is a raw transfer parameter i.e. the length of the actual parameter is not included. | ||
val expectedParameter = Hex.decodeHex("010000a995a405009e15fc57bbe167411d4d9c0686e31e8e937d751625972f7c566de4a97f650dc500fd3dd07c83e42461554cf0dd90d73c1ff04531fc2b9c90b9762df8793319e48d0000"); | ||
val transfers = new ArrayList<Cis2Transfer>(); | ||
transfers.add(new Cis2Transfer(TokenId.min(), TokenAmount.from(11078313), AccountAddress.from("49NGYqmPtbuCkXSQt7298mL6Xp52UpSR4U2jVzJjKW9P3b3whw"), AccountAddress.from("4sGtbuGKgakv5pKSMsy3CEQbW3sn2PbTzTVLZLA6zxX5bB3C5a"), null)); | ||
Parameter parameter = SerializationUtils.serializeTransfers(transfers); | ||
byte[] lengthBytes = UInt16.from(expectedParameter.length).getBytes(); | ||
assertArrayEquals(Arrays.concatenate(lengthBytes, expectedParameter), parameter.getBytes()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testSerializeTransferWithAdditionalData() { | ||
// this is a raw transfer parameter i.e. the length of the actual parameter is not included. | ||
val expectedParameter = Hex.decodeHex("010000a995a405009e15fc57bbe167411d4d9c0686e31e8e937d751625972f7c566de4a97f650dc500fd3dd07c83e42461554cf0dd90d73c1ff04531fc2b9c90b9762df8793319e48d010001"); | ||
val transfers = new ArrayList<Cis2Transfer>(); | ||
transfers.add(new Cis2Transfer(TokenId.min(), TokenAmount.from("11078313"), AccountAddress.from("49NGYqmPtbuCkXSQt7298mL6Xp52UpSR4U2jVzJjKW9P3b3whw"), AccountAddress.from("4sGtbuGKgakv5pKSMsy3CEQbW3sn2PbTzTVLZLA6zxX5bB3C5a"), new byte[]{1})); | ||
Parameter parameter = SerializationUtils.serializeTransfers(transfers); | ||
byte[] lengthBytes = UInt16.from(expectedParameter.length).getBytes(); | ||
assertArrayEquals(Arrays.concatenate(lengthBytes, expectedParameter), parameter.getBytes()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test | ||
public void testSerializeUpdateOperatorOf() { | ||
// this is a raw update parameter i.e. the length of the actual parameter is not included. | ||
val expectedParameter = Hex.decodeHex("0100010081f65db5aab2469518cadb87d4f3d8f3b01773dfe0da103353a80ad8118e393e"); | ||
val updates = new HashMap<AbstractAddress, Boolean>(); | ||
updates.put(AccountAddress.from("3vytfBE9RMZz57dhzKsRuniqeErBC8z4N52ef4LYFFib6XvA1E"), true); | ||
Parameter parameter = SerializationUtils.serializeUpdateOperators(updates); | ||
byte[] lengthBytes = UInt16.from(expectedParameter.length).getBytes(); | ||
assertArrayEquals(Arrays.concatenate(lengthBytes, expectedParameter), parameter.getBytes()); | ||
} | ||
|
||
@Test | ||
public void testSerializeTokenId() { | ||
val emptyTokenId = TokenId.min(); | ||
val serializedEmptyTokenId = SerializationUtils.serializeTokenId(emptyTokenId); | ||
assertArrayEquals(new byte[]{0}, serializedEmptyTokenId); | ||
val tokenId = TokenId.from(new byte[]{1, 2, 3, 4}); | ||
val serializedTokenId = SerializationUtils.serializeTokenId(tokenId); | ||
assertArrayEquals(new byte[]{4, 1, 2, 3, 4}, serializedTokenId); | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
concordium-sdk/src/test/java/com/concordium/sdk/cis2/TokenAmountTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.concordium.sdk.cis2; | ||
|
||
import lombok.SneakyThrows; | ||
import lombok.val; | ||
import org.apache.commons.codec.binary.Hex; | ||
import org.junit.Test; | ||
|
||
import java.lang.reflect.Field; | ||
import java.math.BigInteger; | ||
import java.nio.ByteBuffer; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertFalse; | ||
|
||
public class TokenAmountTest { | ||
@Test | ||
public void testCreateTokenWithMinValue() { | ||
TokenAmount.from("0"); | ||
} | ||
|
||
@Test | ||
public void testCreateTokenWithMaxValue() { | ||
TokenAmount.from("115792089237316195423570985008687907853269984665640564039457584007913129639935"); | ||
} | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testCreateTokenWithNegativeValue() { | ||
TokenAmount.from("-1"); | ||
} | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
public void testCreateTokenWithMoreThanMaxValue() { | ||
TokenAmount.from("115792089237316195423570985008687907853269984665640564039457584007913129639936"); | ||
} | ||
|
||
@Test | ||
public void testSerializeDeserializeMinimumTokenAmount() { | ||
val tokenAmount = TokenAmount.from(0); | ||
val buffer = ByteBuffer.wrap(tokenAmount.encode()); | ||
assertEquals(tokenAmount, TokenAmount.decode(buffer)); | ||
assertFalse(buffer.hasRemaining()); | ||
} | ||
|
||
@Test | ||
public void testSerializeDeserializeMaximumTokenAmount() { | ||
val tokenAmount = TokenAmount.from("115792089237316195423570985008687907853269984665640564039457584007913129639935"); | ||
val buffer = ByteBuffer.wrap(tokenAmount.encode()); | ||
assertEquals(tokenAmount, TokenAmount.decode(buffer)); | ||
assertFalse(buffer.hasRemaining()); | ||
} | ||
|
||
@SneakyThrows | ||
@Test(expected = IllegalArgumentException.class) | ||
public void testDeserializeTokenAmountThatExceeds37Bytes() { | ||
TokenAmount.decode(ByteBuffer.wrap(Hex.decodeHex("8080808080808080808080d2e38b94e9f4d3ca9bc3f0b19393ad8db7ab8ff3efc2e2ef969a01"))); | ||
} | ||
|
||
@SneakyThrows | ||
@Test(expected = IllegalArgumentException.class) | ||
public void testSeserializeTokenAmountThatExceeds37Bytes() { | ||
val invalidTokenAmount = TokenAmount.from(0); | ||
Field amountField = TokenAmount.class.getDeclaredField("amount"); | ||
amountField.setAccessible(true); | ||
amountField.set(invalidTokenAmount, new BigInteger("1115792089237316195423570985008687907853269984665640564039457584007913129639936")); | ||
invalidTokenAmount.encode(); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
concordium-sdk/src/test/java/com/concordium/sdk/transactions/ReceiveNameTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.concordium.sdk.transactions; | ||
|
||
import lombok.val; | ||
import org.junit.Test; | ||
|
||
import java.nio.ByteBuffer; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
|
||
public class ReceiveNameTest { | ||
|
||
@Test | ||
public void testSerializeAndDeserializeReceiveName() { | ||
val receiveName = ReceiveName.from("mycontract", "myfunction"); | ||
assertEquals(receiveName, ReceiveName.from(ByteBuffer.wrap(receiveName.getBytes()))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
concordium-sdk/src/test/java/com/concordium/sdk/types/ContractAddressTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.concordium.sdk.types; | ||
|
||
import lombok.val; | ||
import org.junit.Test; | ||
|
||
import java.nio.ByteBuffer; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
|
||
public class ContractAddressTest { | ||
|
||
@Test | ||
public void testSerializeDeserializeContractAddress() { | ||
val contractAddress = ContractAddress.from(1, 0); | ||
assertEquals(contractAddress, ContractAddress.from(ByteBuffer.wrap(contractAddress.getBytes()))); | ||
} | ||
} |