Skip to content

Commit

Permalink
Merge pull request #274 from Concordium/smartContractSchema
Browse files Browse the repository at this point in the history
Smart contract schema support
  • Loading branch information
magnusbechwind authored Dec 12, 2023
2 parents 7162b04 + c409ba0 commit 102c806
Show file tree
Hide file tree
Showing 90 changed files with 3,848 additions and 221 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Changelog

## Unreleased changes
- Added method `waitUntilFinalized` for waiting until a given transaction is finalized.
- Removed deprecated V1 API from the SDK. Consumers of the (now removed) `getBlockSummary` endpoint should refer to `GetBlockTransactionEvents`, `GetBlockSpecialEvents` and `GetBlockPendingUpdates`.
- Added support for GRPC V2 `GetWinningBakersEpoch` for getting a list of bakers that won the lottery in a particular historical epoch. Only available when querying a node with version at least 6.1.
- Added support for GRPC V2 `GetFirstBlockEpoch` for getting the block hash of the first finalized block in a specified epoch. Only available when querying a node with version at least 6.1.
- Added support for GRPC V2 `GetBakerEarliestWinTime` for getting the projected earliest time at which a particular baker will be required to bake a block. Only available when querying a node woth version at least 6.1.
- Added support for GRPC V2 `GetBakerRewardPeriodInfo` for getting all the bakers in the reward period of a block. Only available when querying a node with version at least 6.1.
- Added support for GRPC V2 `GetBlockCertificates` for retrieving certificates for a block supporting ConcordiumBF, i.e. a node with at least version 6.1.
- Extended `CurrentPaydayStatus` with `CommissionRates` that apply for the current reward period. Requires at least node version 6.1.
- Implemented custom JSON serialization of `AbstractAddress` to enable `AbstractAddress` as a smart contract parameter, and added class `ListParam` for conveniently using lists of objects, `AbstractAddress`, `ContractAddress` and `AccountAddress` as smart contract parameters.
- Added support for creating and serializing smart contract parameters using the abstract class `SchemaParameter` and a provided `Schema`.

## 5.1.0
- Fixed a regression that made it harder to deserialize transactions from bytes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.concordium.sdk.examples.contractexample.cis2nft;

import com.concordium.sdk.ClientV2;
import com.concordium.sdk.Connection;
import com.concordium.sdk.crypto.ed25519.ED25519SecretKey;
import com.concordium.sdk.exceptions.ClientInitializationException;
import com.concordium.sdk.requests.AccountQuery;
import com.concordium.sdk.requests.BlockQuery;
import com.concordium.sdk.responses.blockitemstatus.FinalizedBlockItem;
import com.concordium.sdk.responses.modulelist.ModuleRef;
import com.concordium.sdk.transactions.*;
import com.concordium.sdk.transactions.smartcontracts.SchemaParameter;
import com.concordium.sdk.types.AccountAddress;
import com.concordium.sdk.types.ContractAddress;
import com.concordium.sdk.types.Nonce;
import com.concordium.sdk.types.UInt64;
import lombok.var;
import picocli.CommandLine;

import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.concurrent.Callable;

/**
* Calls different methods on a <a href="https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis2-nft/src/lib.rs">cis2-nft smart contract</a> deployed on the chain.
* See {@link Cis2NftParameters} for how to create and initialize custom smart contract parameters.
* SENDER_ADDRESS, MODULE_REF, CONTRACT_ADDRESS and the key in SIGNER are dummy values and should be replaced
*/
@CommandLine.Command(name = "Cis2Nft", mixinStandardHelpOptions = true)
public class Cis2Nft implements Callable<Integer> {
@CommandLine.Option(
names = {"-m", "--method"},
required = true,
description = "Name of method. Valid names are: ${COMPLETION-CANDIDATES}")
private Cis2NftMethod methodName;

@CommandLine.Option(
names = {"--endpoint"},
description = "GRPC interface of the node.",
defaultValue = "http://localhost:20002")
private String endpoint;

@CommandLine.Option(
names = {"--timeout"},
description = "GRPC request timeout in milliseconds.",
defaultValue = "100000")
private int timeout;

@CommandLine.Option(
names = {"--wait"},
description = "How long to wait for transaction finalization.",
defaultValue = "100000")
private int wait;
private static final String SENDER_ADDRESS = "3WZE6etUvVp1eyhEtTxqZrQaanTAZnZCHEmZmDyCbCwxnmQuPE"; // Dummy address
private static final ModuleRef MODULE_REF = ModuleRef.from("247a7ac6efd2e46f72fd18741a6d1a0254ec14f95639df37079a576b2033873e"); // Dummy module ref
private static final ContractAddress CONTRACT_ADDRESS = ContractAddress.from(1, 0); // Dummy contract address
private static final Expiry EXPIRY = Expiry.createNew().addMinutes(5);
private static final TransactionSigner SIGNER = TransactionSigner.from(
SignerEntry.from(Index.from(0), Index.from(0),
ED25519SecretKey.from("56f60de843790c308dac2d59a5eec9f6b1649513f827e5a13d7038accfe31784")) // Dummy key
);

@Override
public Integer call() throws IOException, ClientInitializationException {
var endpointUrl = new URL(this.endpoint);
Connection connection = Connection.newBuilder()
.host(endpointUrl.getHost())
.port(endpointUrl.getPort())
.timeout(timeout)
.build();
var client = ClientV2.from(connection);
Nonce nonce = client.getAccountInfo(BlockQuery.BEST, AccountQuery.from(AccountAddress.from(SENDER_ADDRESS))).getAccountNonce();

switch (this.methodName) {
case INIT:
handleInit(client, nonce);
break;
case MINT:
SchemaParameter mintParams = Cis2NftParameters.generateMintParams();
handleMethod(client, nonce, mintParams);
break;
case TRANSFER:
SchemaParameter transferParams = Cis2NftParameters.generateTransferParams();
handleMethod(client, nonce, transferParams);
break;
case UPDATE_OPERATOR:
SchemaParameter updateOperatorParams = Cis2NftParameters.generateUpdateOperatorParams();
handleMethod(client, nonce, updateOperatorParams);
break;
case OPERATOR_OF:
SchemaParameter operatorOfParams = Cis2NftParameters.generateOperatorOfParams();
handleMethod(client, nonce, operatorOfParams);
break;
case BALANCE_OF:
SchemaParameter balanceOfParams = Cis2NftParameters.generateBalanceOfParams();
handleMethod(client, nonce, balanceOfParams);
break;
case TOKEN_METADATA:
SchemaParameter tokenMetadataParams = Cis2NftParameters.generateTokenMetadataParams();
handleMethod(client, nonce, tokenMetadataParams);
break;
case SUPPORTS:
SchemaParameter supportsParams = Cis2NftParameters.generateSupportsParameter();
handleMethod(client, nonce, supportsParams);
break;
case SET_IMPLEMENTORS:
SchemaParameter setImplementorsParams = Cis2NftParameters.generateSetImplementorsParams();
handleMethod(client, nonce, setImplementorsParams);
break;
}
return 0;
}

private void handleInit(ClientV2 client, Nonce nonce) {
InitName initName = InitName.from("init_cis2_nft");
InitContractPayload payload = InitContractPayload.from(CCDAmount.fromMicro(0), MODULE_REF, initName, Parameter.EMPTY);
InitContractTransaction initContractTransaction = TransactionFactory.newInitContract()
.sender(AccountAddress.from(SENDER_ADDRESS))
.payload(payload)
.expiry(EXPIRY)
.nonce(AccountNonce.from(nonce))
.signer(SIGNER)
.maxEnergyCost(UInt64.from(10000))
.build();
Hash txHash = client.sendTransaction(initContractTransaction);
System.out.println("Submitted transaction for " + this.methodName + " with hash: " + txHash);
Optional<FinalizedBlockItem> finalizedTransaction = client.waitUntilFinalized(txHash, wait);
finalizedTransaction.ifPresent(finalizedBlockItem -> System.out.println("Transaction finalized in block with hash: " + finalizedBlockItem.getBlockHash()));
}

private void handleMethod(ClientV2 client, Nonce nonce, SchemaParameter parameter) {
UpdateContract payload = UpdateContract.from(CONTRACT_ADDRESS, parameter);
UpdateContractTransaction transaction = TransactionFactory.newUpdateContract()
.sender(AccountAddress.from(SENDER_ADDRESS))
.payload(payload)
.expiry(EXPIRY)
.nonce(AccountNonce.from(nonce))
.signer(SIGNER)
.maxEnergyCost(UInt64.from(10000))
.build();
Hash txHash = client.sendTransaction(transaction);
System.out.println("Submitted transaction for " + this.methodName + " with hash: " + txHash);
Optional<FinalizedBlockItem> finalizedTransaction = client.waitUntilFinalized(txHash, wait);
finalizedTransaction.ifPresent(finalizedBlockItem -> System.out.println("Transaction finalized in block with hash: " + finalizedBlockItem.getBlockHash()));
}

public static void main(String[] args) {
int exitCode = new CommandLine(new Cis2Nft()).execute(args);
System.exit(exitCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.concordium.sdk.examples.contractexample.cis2nft;

import com.concordium.sdk.examples.contractexample.parameters.*;

/**
* Represents the different possible methods able to be run by {@link Cis2Nft} representing a <a href="https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis2-nft/src/lib.rs">cis2-nft contract</a>.
*/
public enum Cis2NftMethod {
/**
* The init method.
*/
INIT,
/**
* The 'mint' method. Uses parameter {@link MintParams}.
*/
MINT,
/**
* The 'transfer' method. Uses parameter {@link NftTransferParam}.
*/
TRANSFER,
/**
* The 'updateOperator' method. Uses parameter {@link UpdateOperator}.
*/
UPDATE_OPERATOR,
/**
* The 'operatorOf' of method. Uses parameter {@link OperatorOfQueryParams}.
*/
OPERATOR_OF,
/**
* The 'balanceOf' method. Uses parameter {@link NftBalanceOfQueryParams}.
*/
BALANCE_OF,
/**
* The 'tokenMetadata' method. Uses parameter {@link NftTokenMetaDataQueryParams}.
*/
TOKEN_METADATA,
/**
* The 'supports' method. Uses parameter {@link SupportsQueryParams}.
*/
SUPPORTS,
/**
* The 'setImplementors method. Uses parameter {@link SetImplementorsParams}.
*/
SET_IMPLEMENTORS,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.concordium.sdk.examples.contractexample.cis2nft;

import com.concordium.sdk.examples.contractexample.parameters.*;
import com.concordium.sdk.transactions.ReceiveName;
import com.concordium.sdk.transactions.smartcontracts.Schema;
import com.concordium.sdk.transactions.smartcontracts.SchemaParameter;
import com.concordium.sdk.types.*;
import lombok.SneakyThrows;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
* Helper class for creating and initializing parameters being used in {@link Cis2Nft} representing a <a href="https://github.com/Concordium/concordium-rust-smart-contracts/blob/main/examples/cis2-nft/src/lib.rs">cis2-nft contract</a>.
* All values are dummy values and should be replaced to get valid results.
*/
public class Cis2NftParameters {
private static final AccountAddress ACCOUNT_ADDRESS = AccountAddress.from("3XSLuJcXg6xEua6iBPnWacc3iWh93yEDMCqX8FbE3RDSbEnT9P");
private static final ContractAddress CONTRACT_ADDRESS_1 = ContractAddress.from(1, 0);
private static final ContractAddress CONTRACT_ADDRESS_2 = ContractAddress.from(2, 0);
private static final String CIS_2_NFT_CONTRACT_NAME = "cis2_nft";

private static final String SCHEMA_PATH = "./src/main/java/com/concordium/sdk/examples/contractexample/cis2nft/cis2-nft.schema.bin";

private static final Schema SCHEMA;

static {
try {
SCHEMA = Schema.from(Files.readAllBytes(Paths.get(SCHEMA_PATH)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/**
* Generates and initializes {@link MintParams} for the 'mint' method of a cis2-nft contract.
*
* @return initialized {@link MintParams}.
*/
@SneakyThrows
public static SchemaParameter generateMintParams() {
ReceiveName mintParamsReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "mint");
List<TokenIdU32> tokens = new ArrayList<>();
tokens.add(TokenIdU32.from(2));
tokens.add(TokenIdU32.from(22));
tokens.add(TokenIdU32.from(2132));
SchemaParameter mintParameter = new MintParams(SCHEMA, mintParamsReceiveName, ACCOUNT_ADDRESS, tokens);
mintParameter.initialize(true);
return mintParameter;
}

/**
* Generates and initializes {@link NftTransferParam} for the 'transfer' method of a cis2-nft contract.
*
* @return initialized {@link NftTransferParam}.
*/
@SneakyThrows
public static SchemaParameter generateTransferParams() {
ReceiveName nftTransferReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "transfer");
TokenIdU32 tokenId = TokenIdU32.from(12);
TokenAmountU8 amount = TokenAmountU8.from(1);
AbstractAddress from = CONTRACT_ADDRESS_1;
Receiver to = new Receiver(CONTRACT_ADDRESS_2, "mint");
byte[] data = new byte[]{123, -23};
NftTransfer transfer = new NftTransfer(tokenId, amount, from, to, data);
List<NftTransfer> transfers = new ArrayList<>();
transfers.add(transfer);
SchemaParameter transferParameter = new NftTransferParam(SCHEMA, nftTransferReceiveName, transfers);
transferParameter.initialize(true);
return transferParameter;
}

/**
* Generates and initializes {@link UpdateOperatorParams} for the 'updateOperator' method of a cis2-nft contract.
*
* @return initialized {@link UpdateOperatorParams}.
*/
@SneakyThrows
public static SchemaParameter generateUpdateOperatorParams() {
ReceiveName updateOperatorReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "updateOperator");
UpdateOperator update1 = new UpdateOperator(UpdateOperator.OperatorUpdate.ADD, ACCOUNT_ADDRESS);
UpdateOperator update2 = new UpdateOperator(UpdateOperator.OperatorUpdate.REMOVE, CONTRACT_ADDRESS_1);
List<UpdateOperator> updateOperatorList = new ArrayList<>();
updateOperatorList.add(update1);
updateOperatorList.add(update2);
SchemaParameter updateOperatorsParams = new UpdateOperatorParams(SCHEMA, updateOperatorReceiveName, updateOperatorList);
updateOperatorsParams.initialize(true);
return updateOperatorsParams;
}

/**
* Generates and initializes {@link OperatorOfQueryParams} for the 'operatorOf' method of a cis2-nft contract.
*
* @return initialized {@link OperatorOfQueryParams}.
*/
@SneakyThrows
public static SchemaParameter generateOperatorOfParams() {
ReceiveName operatorOfReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "operatorOf");
OperatorOfQuery operatorOfQuery1 = new OperatorOfQuery(ACCOUNT_ADDRESS, CONTRACT_ADDRESS_1);
OperatorOfQuery operatorOfQuery2 = new OperatorOfQuery(CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2);
List<OperatorOfQuery> operatorOfQueries = new ArrayList<>();
operatorOfQueries.add(operatorOfQuery1);
operatorOfQueries.add(operatorOfQuery2);
SchemaParameter operatorOfQueryParams = new OperatorOfQueryParams(SCHEMA, operatorOfReceiveName, operatorOfQueries);
operatorOfQueryParams.initialize(true);
return operatorOfQueryParams;
}

/**
* Generates and initializes {@link NftBalanceOfQueryParams} for the 'balanceOf' method of a cis2-nft contract.
*
* @return initialized {@link NftBalanceOfQueryParams}.
*/
@SneakyThrows
public static SchemaParameter generateBalanceOfParams() {
ReceiveName balanceOfReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "balanceOf");
NftBalanceOfQuery balanceOfQuery1 = new NftBalanceOfQuery(TokenIdU32.from(22222), ACCOUNT_ADDRESS);
NftBalanceOfQuery balanceOfQuery2 = new NftBalanceOfQuery(TokenIdU32.from(42), CONTRACT_ADDRESS_1);
List<NftBalanceOfQuery> balanceOfQueries = new ArrayList<>();
balanceOfQueries.add(balanceOfQuery1);
balanceOfQueries.add(balanceOfQuery2);
SchemaParameter contractBalanceOfQueryParams = new NftBalanceOfQueryParams(SCHEMA, balanceOfReceiveName, balanceOfQueries);
contractBalanceOfQueryParams.initialize(true);
return contractBalanceOfQueryParams;
}

/**
* Generates and initializes {@link NftTokenMetaDataQueryParams} for the 'tokenMetadata' method of a cis2-nft contract.
*
* @return initialized {@link NftTokenMetaDataQueryParams}.
*/
@SneakyThrows
public static SchemaParameter generateTokenMetadataParams() {
ReceiveName tokenMetadataReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "tokenMetadata");
TokenIdU32 token1 = TokenIdU32.from(21);
TokenIdU32 token2 = TokenIdU32.from(22);
List<TokenIdU32> tokensForMetadataQuery = new ArrayList<>();
tokensForMetadataQuery.add(token1);
tokensForMetadataQuery.add(token2);
SchemaParameter nftMetaDataQuery = new NftTokenMetaDataQueryParams(SCHEMA, tokenMetadataReceiveName, tokensForMetadataQuery);
nftMetaDataQuery.initialize(true);
return nftMetaDataQuery;
}

/**
* Generates and initializes {@link SupportsQueryParams} for the 'supports' method of a cis2-nft contract.
*
* @return initialized {@link SupportsQueryParams}.
*/
@SneakyThrows
public static SchemaParameter generateSupportsParameter() {
ReceiveName supportsReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "supports");
String standardIdentifier1 = "identifier1";
String standardIdentifier2 = "identifier2";
List<String> identifiers = new ArrayList<>();
identifiers.add(standardIdentifier1);
identifiers.add(standardIdentifier2);
SchemaParameter supportsQueryParams = new SupportsQueryParams(SCHEMA, supportsReceiveName, identifiers);
supportsQueryParams.initialize(true);
return supportsQueryParams;
}

/**
* Generates and initializes {@link SetImplementorsParams} for the 'setImplementors' method of a cis2-nft contract.
*
* @return initialized {@link SetImplementorsParams}.
*/
@SneakyThrows
public static SchemaParameter generateSetImplementorsParams() {
ReceiveName setImplementorsReceiveName = ReceiveName.from(CIS_2_NFT_CONTRACT_NAME, "setImplementors");
List<ContractAddress> implementors = new ArrayList<>();
String identifier = "IdentifierID";
implementors.add(CONTRACT_ADDRESS_1);
implementors.add(CONTRACT_ADDRESS_2);
SchemaParameter setImplementorsParams = new SetImplementorsParams(SCHEMA, setImplementorsReceiveName, identifier, implementors);
setImplementorsParams.initialize();
return setImplementorsParams;
}

}
Binary file not shown.
Loading

0 comments on commit 102c806

Please sign in to comment.