All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.hashgraph.sdk.ContractCreateFlow Maven / Gradle / Ivy

The newest version!
/*-
 *
 * Hedera Java SDK
 *
 * Copyright (C) 2020 - 2024 Hedera Hashgraph, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.hedera.hashgraph.sdk;

import com.google.protobuf.ByteString;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javax.annotation.Nullable;
import org.bouncycastle.util.encoders.Hex;

/**
 * Start a new smart contract instance. After the instance is created, the ContractID for it is in the receipt.
 * 

* The instance will exist for autoRenewPeriod seconds. When that is reached, it will renew itself for another * autoRenewPeriod seconds by charging its associated cryptocurrency account (which it creates here). If it has * insufficient cryptocurrency to extend that long, it will extend as long as it can. If its balance is zero, the * instance will be deleted. *

* A smart contract instance normally enforces rules, so "the code is law". For example, an ERC-20 contract prevents a * transfer from being undone without a signature by the recipient of the transfer. This is always enforced if the * contract instance was created with the adminKeys being null. But for some uses, it might be desirable to create * something like an ERC-20 contract that has a specific group of trusted individuals who can act as a "supreme court" * with the ability to override the normal operation, when a sufficient number of them agree to do so. If adminKeys is * not null, then they can sign a transaction that can change the state of the smart contract in arbitrary ways, such as * to reverse a transaction that violates some standard of behavior that is not covered by the code itself. The admin * keys can also be used to change the autoRenewPeriod, and change the adminKeys field itself. The API currently does * not implement this ability. But it does allow the adminKeys field to be set and queried, and will in the future * implement such admin abilities for any instance that has a non-null adminKeys. *

* If this constructor stores information, it is charged gas to store it. There is a fee in hbars to maintain that * storage until the expiration time, and that fee is added as part of the transaction fee. *

* An entity (account, file, or smart contract instance) must be created in a particular realm. If the realmID is left * null, then a new realm will be created with the given admin key. If a new realm has a null adminKey, then anyone can * create/modify/delete entities in that realm. But if an admin key is given, then any transaction to * create/modify/delete an entity in that realm must be signed by that key, though anyone can still call functions on * smart contract instances that exist in that realm. A realm ceases to exist when everything within it has expired and * no longer exists. *

* The current API ignores shardID, realmID, and newRealmAdminKey, and creates everything in shard 0 and realm 0, with a * null key. Future versions of the API will support multiple realms and multiple shards. *

* The optional memo field can contain a string whose length is up to 100 bytes. That is the size after Unicode NFD then * UTF-8 conversion. This field can be used to describe the smart contract. It could also be used for other purposes. * One recommended purpose is to hold a hexadecimal string that is the SHA-384 hash of a PDF file containing a * human-readable legal contract. Then, if the admin keys are the public keys of human arbitrators, they can use that * legal document to guide their decisions during a binding arbitration tribunal, convened to consider any changes to * the smart contract in the future. The memo field can only be changed using the admin keys. If there are no admin * keys, then it cannot be changed after the smart contract is created. */ // Re-use the WithExecute interface that was generated for Executable public class ContractCreateFlow { static final int FILE_CREATE_MAX_BYTES = 2048; private String bytecode = ""; @Nullable private Integer maxChunks = null; @Nullable private Key adminKey = null; private long gas = 0; private Hbar initialBalance = Hbar.ZERO; @Nullable private AccountId proxyAccountId = null; private int maxAutomaticTokenAssociations = 0; @Nullable private Duration autoRenewPeriod = null; @Nullable private AccountId autoRenewAccountId = null; private byte[] constructorParameters = {}; @Nullable private String contractMemo = null; @Nullable private List nodeAccountIds = null; private String createBytecode = ""; private String appendBytecode = ""; @Nullable private AccountId stakedAccountId = null; @Nullable private Long stakedNodeId = null; private boolean declineStakingReward = false; @Nullable private Client freezeWithClient = null; @Nullable private PrivateKey signPrivateKey = null; @Nullable private PublicKey signPublicKey = null; @Nullable private UnaryOperator transactionSigner = null; /** * Constructor */ public ContractCreateFlow() { } /** * Extract the hex-encoded bytecode of the contract. * * @return the hex-encoded bytecode of the contract. */ public String getBytecode() { return bytecode; } /** * Sets the bytecode of the contract in hex. * * @param bytecode the string to assign * @return {@code this} */ public ContractCreateFlow setBytecode(String bytecode) { Objects.requireNonNull(bytecode); this.bytecode = bytecode; return this; } /** * Sets the bytecode of the contract in raw bytes. * * @param bytecode the byte array * @return {@code this} */ public ContractCreateFlow setBytecode(byte[] bytecode) { Objects.requireNonNull(bytecode); this.bytecode = Hex.toHexString(bytecode); return this; } /** * Sets the bytecode of the contract in raw bytes. * * @param bytecode the byte string * @return the contract in raw bytes */ public ContractCreateFlow setBytecode(ByteString bytecode) { Objects.requireNonNull(bytecode); return setBytecode(bytecode.toByteArray()); } /** * Get the maximum number of chunks * * @return the maxChunks */ @Nullable public Integer getMaxChunks() { return maxChunks; } /** * Set the maximal number of chunks * * @param maxChunks the maximum number of chunks * @return {@code this} */ public ContractCreateFlow setMaxChunks(int maxChunks) { this.maxChunks = maxChunks; return this; } /** * Extract the admin key. * * @return the admin key */ @Nullable public Key getAdminKey() { return adminKey; } /** * Sets the state of the instance and its fields can be modified arbitrarily if this key signs a transaction to * modify it. If this is null, then such modifications are not possible, and there is no administrator that can * override the normal operation of this smart contract instance. Note that if it is created with no admin keys, * then there is no administrator to authorize changing the admin keys, so there can never be any admin keys for * that instance. * * @param adminKey The Key to be set * @return {@code this} */ public ContractCreateFlow setAdminKey(Key adminKey) { Objects.requireNonNull(adminKey); this.adminKey = adminKey; return this; } /** * Extract the gas. * * @return the gas */ public long getGas() { return gas; } /** * Sets the gas to run the constructor. * * @param gas The long to be set as gas * @return {@code this} */ public ContractCreateFlow setGas(long gas) { this.gas = gas; return this; } /** * Extract the initial balance in hbar. * * @return the initial balance in hbar */ public Hbar getInitialBalance() { return initialBalance; } /** * Sets the initial number of hbars to put into the cryptocurrency account associated with and owned by the smart * contract. * * @param initialBalance The Hbar to be set as the initial balance * @return {@code this} */ public ContractCreateFlow setInitialBalance(Hbar initialBalance) { Objects.requireNonNull(initialBalance); this.initialBalance = initialBalance; return this; } /** * @return the proxy account id * @deprecated with no replacement *

* Extract the proxy account id. */ @Nullable @Deprecated public AccountId getProxyAccountId() { return proxyAccountId; } /** * @param proxyAccountId The AccountId to be set * @return {@code this} * @deprecated with no replacement *

* Sets the ID of the account to which this account is proxy staked. *

* If proxyAccountID is null, or is an invalid account, or is an account that isn't a node, then this account is * automatically proxy staked to a node chosen by the network, but without earning payments. *

* If the proxyAccountID account refuses to accept proxy staking , or if it is not currently running a node, then it * will behave as if proxyAccountID was null. */ @Deprecated public ContractCreateFlow setProxyAccountId(AccountId proxyAccountId) { Objects.requireNonNull(proxyAccountId); this.proxyAccountId = proxyAccountId; return this; } /** * The maximum number of tokens that an Account can be implicitly associated with. Defaults to 0 and up to a maximum * value of 1000. * * @return The maxAutomaticTokenAssociations. */ public int getMaxAutomaticTokenAssociations() { return maxAutomaticTokenAssociations; } /** * The maximum number of tokens that an Account can be implicitly associated with. Defaults to 0 and up to a maximum * value of 1000. * * @param maxAutomaticTokenAssociations The maxAutomaticTokenAssociations to set * @return {@code this} */ public ContractCreateFlow setMaxAutomaticTokenAssociations(int maxAutomaticTokenAssociations) { this.maxAutomaticTokenAssociations = maxAutomaticTokenAssociations; return this; } /** * Extract the auto renew period. * * @return the auto renew period */ @Nullable @SuppressFBWarnings( value = "EI_EXPOSE_REP", justification = "A Duration can't actually be mutated" ) public Duration getAutoRenewPeriod() { return autoRenewPeriod; } /** * Sets the period that the instance will charge its account every this many seconds to renew. * * @param autoRenewPeriod The Duration to be set for auto renewal * @return {@code this} */ @SuppressFBWarnings( value = "EI_EXPOSE_REP2", justification = "A Duration can't actually be mutated" ) public ContractCreateFlow setAutoRenewPeriod(Duration autoRenewPeriod) { Objects.requireNonNull(autoRenewPeriod); this.autoRenewPeriod = autoRenewPeriod; return this; } /** * Get the account ID which will be charged for renewing this account * * @return the auto-renewal account id */ @Nullable public AccountId getAutoRenewAccountId() { return autoRenewAccountId; } /** * Set the account ID which will be charged for renewing this account * * @param autoRenewAccountId the autoRenewAccountId to set * @return {@code this} */ public ContractCreateFlow setAutoRenewAccountId(AccountId autoRenewAccountId) { Objects.requireNonNull(autoRenewAccountId); this.autoRenewAccountId = autoRenewAccountId; return this; } /** * Extract the byte string representation. * * @return the byte string representation */ public ByteString getConstructorParameters() { return ByteString.copyFrom(constructorParameters); } /** * Sets the constructor parameters as their raw bytes. *

* Use this instead of {@link #setConstructorParameters(ContractFunctionParameters)} if you have already pre-encoded * a solidity function call. * * @param constructorParameters The constructor parameters * @return {@code this} */ public ContractCreateFlow setConstructorParameters(byte[] constructorParameters) { this.constructorParameters = Arrays.copyOf(constructorParameters, constructorParameters.length); return this; } /** * Sets the parameters to pass to the constructor. * * @param constructorParameters The contructor parameters * @return {@code this} */ public ContractCreateFlow setConstructorParameters(ContractFunctionParameters constructorParameters) { Objects.requireNonNull(constructorParameters); return setConstructorParameters(constructorParameters.toBytes(null).toByteArray()); } /** * Extract the contract memo. * * @return the contract memo */ public String getContractMemo() { return contractMemo; } /** * Sets the memo to be associated with this contract. * * @param memo The String to be set as the memo * @return {@code this} */ public ContractCreateFlow setContractMemo(String memo) { Objects.requireNonNull(memo); contractMemo = memo; return this; } /** * ID of the account to which this contract will stake * * @return ID of the account to which this contract will stake. */ @Nullable public AccountId getStakedAccountId() { return stakedAccountId; } /** * Set the account to which this contract will stake * * @param stakedAccountId ID of the account to which this contract will stake. * @return {@code this} */ public ContractCreateFlow setStakedAccountId(@Nullable AccountId stakedAccountId) { this.stakedAccountId = stakedAccountId; this.stakedNodeId = null; return this; } /** * The node to which this contract will stake * * @return ID of the node this contract will be staked to. */ @Nullable public Long getStakedNodeId() { return stakedNodeId; } /** * Set the node to which this contract will stake * * @param stakedNodeId ID of the node this contract will be staked to. * @return {@code this} */ public ContractCreateFlow setStakedNodeId(@Nullable Long stakedNodeId) { this.stakedNodeId = stakedNodeId; this.stakedAccountId = null; return this; } /** * If true, the contract declines receiving a staking reward. The default value is false. * * @return If true, the contract declines receiving a staking reward. The default value is false. */ public boolean getDeclineStakingReward() { return declineStakingReward; } /** * If true, the contract declines receiving a staking reward. The default value is false. * * @param declineStakingReward - If true, the contract declines receiving a staking reward. The default value is * false. * @return {@code this} */ public ContractCreateFlow setDeclineStakingReward(boolean declineStakingReward) { this.declineStakingReward = declineStakingReward; return this; } /** * Extract the list of node account id's. * * @return the list of node account id's */ @Nullable public List getNodeAccountIds() { return nodeAccountIds != null ? Collections.unmodifiableList(nodeAccountIds) : null; } /** * Set the account IDs of the nodes that this transaction will be submitted to. *

* Providing an explicit node account ID interferes with client-side load balancing of the network. By default, the * SDK will pre-generate a transaction for 1/3 of the nodes on the network. If a node is down, busy, or otherwise * reports a fatal error, the SDK will try again with a different node. * * @param nodeAccountIds The list of node AccountIds to be set * @return {@code this} */ public ContractCreateFlow setNodeAccountIds(List nodeAccountIds) { Objects.requireNonNull(nodeAccountIds); this.nodeAccountIds = new ArrayList(nodeAccountIds); return this; } /** * Set the client that this transaction will be frozen with. * * @param client the client with the transaction to execute * @return {@code this} */ public ContractCreateFlow freezeWith(Client client) { this.freezeWithClient = client; return this; } /** * Set the private key that this transaction will be signed with. * * @param privateKey the private key used for signing * @return {@code this} */ public ContractCreateFlow sign(PrivateKey privateKey) { this.signPrivateKey = privateKey; this.signPublicKey = null; this.transactionSigner = null; return this; } /** * Set the public key and key list that this transaction will be signed with. * * @param publicKey the public key * @param transactionSigner the key list * @return {@code this} */ public ContractCreateFlow signWith(PublicKey publicKey, UnaryOperator transactionSigner) { this.signPublicKey = publicKey; this.transactionSigner = transactionSigner; this.signPrivateKey = null; return this; } /** * Set the operator that this transaction will be signed with. * * @param client the client with the transaction to execute * @return {@code this} */ public ContractCreateFlow signWithOperator(Client client) { var operator = Objects.requireNonNull(client.getOperator()); this.signPublicKey = operator.publicKey; this.transactionSigner = operator.transactionSigner; this.signPrivateKey = null; return this; } private void splitBytecode() { if (bytecode.length() > FILE_CREATE_MAX_BYTES) { createBytecode = bytecode.substring(0, FILE_CREATE_MAX_BYTES); appendBytecode = bytecode.substring(FILE_CREATE_MAX_BYTES); } else { createBytecode = bytecode; appendBytecode = ""; } } private FileCreateTransaction createFileCreateTransaction(Client client) { var fileCreateTx = new FileCreateTransaction() .setKeys(Objects.requireNonNull(client.getOperatorPublicKey())) .setContents(createBytecode); if (nodeAccountIds != null) { fileCreateTx.setNodeAccountIds(nodeAccountIds); } return fileCreateTx; } private FileAppendTransaction createFileAppendTransaction(FileId fileId) { var fileAppendTx = new FileAppendTransaction() .setFileId(fileId) .setContents(appendBytecode); if (maxChunks != null) { fileAppendTx.setMaxChunks(maxChunks); } if (nodeAccountIds != null) { fileAppendTx.setNodeAccountIds(nodeAccountIds); } return fileAppendTx; } private ContractCreateTransaction createContractCreateTransaction(FileId fileId) { var contractCreateTx = new ContractCreateTransaction() .setBytecodeFileId(fileId) .setConstructorParameters(constructorParameters) .setGas(gas) .setInitialBalance(initialBalance) .setMaxAutomaticTokenAssociations(maxAutomaticTokenAssociations) .setDeclineStakingReward(declineStakingReward); if (adminKey != null) { contractCreateTx.setAdminKey(adminKey); } if (proxyAccountId != null) { contractCreateTx.setProxyAccountId(proxyAccountId); } if (autoRenewPeriod != null) { contractCreateTx.setAutoRenewPeriod(autoRenewPeriod); } if (autoRenewAccountId != null) { contractCreateTx.setAutoRenewAccountId(autoRenewAccountId); } if (contractMemo != null) { contractCreateTx.setContractMemo(contractMemo); } if (nodeAccountIds != null) { contractCreateTx.setNodeAccountIds(nodeAccountIds); } if (stakedAccountId != null) { contractCreateTx.setStakedAccountId(stakedAccountId); } else if (stakedNodeId != null) { contractCreateTx.setStakedNodeId(stakedNodeId); } if (freezeWithClient != null) { contractCreateTx.freezeWith(freezeWithClient); } if (signPrivateKey != null) { contractCreateTx.sign(signPrivateKey); } else if (signPublicKey != null && transactionSigner != null) { contractCreateTx.signWith(signPublicKey, transactionSigner); } return contractCreateTx; } /** * Create a new transaction receipt query. * * @param response the transaction response * @return the receipt query */ TransactionReceiptQuery createTransactionReceiptQuery(TransactionResponse response) { return new TransactionReceiptQuery() .setNodeAccountIds(Collections.singletonList(response.nodeId)) .setTransactionId(response.transactionId); } /** * Execute the transactions in the flow with the passed in client. * * @param client the client with the transaction to execute * @return the response * @throws PrecheckStatusException when the precheck fails * @throws TimeoutException when the transaction times out */ public TransactionResponse execute(Client client) throws PrecheckStatusException, TimeoutException { return execute(client, client.getRequestTimeout()); } /** * Execute the transactions in the flow with the passed in client. * * @param client the client with the transaction to execute * @param timeoutPerTransaction The timeout after which each transaction's execution attempt will be cancelled. * @return the response * @throws PrecheckStatusException when the precheck fails * @throws TimeoutException when the transaction times out */ public TransactionResponse execute(Client client, Duration timeoutPerTransaction) throws PrecheckStatusException, TimeoutException { try { splitBytecode(); var fileId = createFileCreateTransaction(client) .execute(client, timeoutPerTransaction) .getReceipt(client, timeoutPerTransaction) .fileId; Objects.requireNonNull(fileId); if (!appendBytecode.isEmpty()) { createFileAppendTransaction(fileId) .execute(client, timeoutPerTransaction); } var response = createContractCreateTransaction(fileId).execute(client, timeoutPerTransaction); response.getReceipt(client, timeoutPerTransaction); new FileDeleteTransaction() .setFileId(fileId) .execute(client, timeoutPerTransaction); return response; } catch (ReceiptStatusException e) { throw new RuntimeException(e); } } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @return the response */ public CompletableFuture executeAsync(Client client) { return executeAsync(client, client.getRequestTimeout()); } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @param timeoutPerTransaction The timeout after which each transaction's execution attempt will be cancelled. * @return the response */ public CompletableFuture executeAsync(Client client, Duration timeoutPerTransaction) { splitBytecode(); return createFileCreateTransaction(client).executeAsync(client, timeoutPerTransaction) .thenCompose(fileCreateResponse -> createTransactionReceiptQuery(fileCreateResponse) .executeAsync(client, timeoutPerTransaction) .thenApply(receipt -> receipt.fileId)).thenCompose(fileId -> { CompletableFuture appendFuture = appendBytecode.isEmpty() ? CompletableFuture.completedFuture(null) : createFileAppendTransaction(fileId).executeAsync(client, timeoutPerTransaction) .thenApply(ignored -> null); return appendFuture.thenCompose( ignored -> createContractCreateTransaction(fileId).executeAsync(client, timeoutPerTransaction) .thenApply(contractCreateResponse -> { createTransactionReceiptQuery(contractCreateResponse).executeAsync(client, timeoutPerTransaction).thenRun(() -> { new FileDeleteTransaction() .setFileId(fileId) .executeAsync(client, timeoutPerTransaction); }); return contractCreateResponse; })); }); } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @param callback a BiConsumer which handles the result or error. */ public void executeAsync(Client client, BiConsumer callback) { ConsumerHelper.biConsumer(executeAsync(client), callback); } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @param timeoutPerTransaction The timeout after which each transaction's execution attempt will be cancelled. * @param callback a BiConsumer which handles the result or error. */ public void executeAsync(Client client, Duration timeoutPerTransaction, BiConsumer callback) { ConsumerHelper.biConsumer(executeAsync(client, timeoutPerTransaction), callback); } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void executeAsync(Client client, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(executeAsync(client), onSuccess, onFailure); } /** * Execute the transactions in the flow with the passed in client asynchronously. * * @param client the client with the transaction to execute * @param timeoutPerTransaction The timeout after which each transaction's execution attempt will be cancelled. * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void executeAsync(Client client, Duration timeoutPerTransaction, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(executeAsync(client, timeoutPerTransaction), onSuccess, onFailure); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy