diff --git a/README.md b/README.md index 044ed97c..816a6f0e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,9 @@ You can use each submodule individually. Click the module below to get more deta * [Liteclient](liteclient/README.md) - wrapper for using with external precompiled lite-client binary. * [Fift](fift/README.md) - wrapper for using with external precompiled fift binary. * [Func](func/README.md) - wrapper for using with external precompiled func binary. +* [Tolk](tolk/README.md) - wrapper for using with external precompiled tolk binary. * [TonConnect](tonconnect/README.md) - implementation of Ton Connect standard. +* [Blockchain](blockchain/README.md) - To Do. * [Utils](utils/README.md) - create private and public keys, convert data, etc. ### Features diff --git a/blockchain/README.md b/blockchain/README.md new file mode 100644 index 00000000..4fe6a34f --- /dev/null +++ b/blockchain/README.md @@ -0,0 +1,39 @@ +# Blockchain module + +Description + +## Maven [![Maven Central][maven-central-svg]][maven-central] + +```xml + + + io.github.neodix42 + blockchain + 0.7.1 + +``` + +## Jitpack + +```xml + + + io.github.neodix42.ton4j + blockchain + 0.7.1 + +``` + +## Usage + +```java +// todo +``` + +[maven-central-svg]: https://img.shields.io/maven-central/v/io.github.neodix42/blockchain + +[maven-central]: https://mvnrepository.com/artifact/io.github.neodix42/blockchain + +[ton-svg]: https://img.shields.io/badge/Based%20on-TON-blue + +[ton]: https://ton.org \ No newline at end of file diff --git a/blockchain/src/main/java/org/ton/java/Blockchain.java b/blockchain/src/main/java/org/ton/java/Blockchain.java index a154230d..851e8e93 100644 --- a/blockchain/src/main/java/org/ton/java/Blockchain.java +++ b/blockchain/src/main/java/org/ton/java/Blockchain.java @@ -212,15 +212,15 @@ private void initializeSmartContractCompiler() { private void printBlockchainInfo() { if (super.network == Network.EMULATOR) { - System.out.printf( + log.info( "Blockchain configuration:\n" - + "Target network: %s\n" - + "Emulator location: %s, configType: %s, txVerbosity: %s, tvmVerbosity: %s\n" - + "Emulator ShardAccount: balance %s, address: %s, lastPaid: %s, lastTransLt: %s\n" - + "Func location: %s\n" - + "Tolk location: %s\n" - + "Fift location: %s, FIFTPATH=%s\n" - + "Contract: %s\n\n", + + "Target network: {}\n" + + "Emulator location: {}, configType: {}, txVerbosity: {}, tvmVerbosity: {}\n" + + "Emulator ShardAccount: balance {}, address: {}, lastPaid: {}, lastTransLt: {}\n" + + "Func location: {}\n" + + "Tolk location: {}" + + "Fift location: {}, FIFTPATH={}\n" + + "Contract: {}\n", super.network, Utils.detectAbsolutePath("emulator", true), txEmulator.getConfigType(), @@ -240,16 +240,16 @@ private void printBlockchainInfo() { ? "integrated resource " + super.customContractAsResource : super.customContractPath); } else { - System.out.printf( + log.info( "Blockchain configuration:\n" - + "Target network: %s\n" + + "Target network: {}\n" + "Emulator not used\n" - + "Tonlib location: %s\n" - + "Tonlib global config: %s\n" - + "Func location: %s\n" - + "Tolk location: %s\n" - + "Fift location: %s, FIFTPATH=%s\n" - + "Contract: %s\n\n", + + "Tonlib location: {}\n" + + "Tonlib global config: {}\n" + + "Func location: {}\n" + + "Tolk location: {}\n" + + "Fift location: {}, FIFTPATH={}\n" + + "Contract: {}\n", super.network, super.tonlib.pathToTonlibSharedLib, super.tonlib.pathToGlobalConfig, @@ -323,32 +323,58 @@ private void initializeTonlib() { } public SendExternalResult sendExternal(Message message) { - System.out.printf("sending external message on %s\n", network); - ExtMessageInfo tonlibResult = null; try { if (network != Network.EMULATOR) { - tonlibResult = tonlib.sendRawMessage(message.toCell().toBase64()); + String bounceableAddress = + (network == Network.TESTNET) + ? contract.getAddress().toBounceableTestnet() + : contract.getAddress().toBounceable(); + log.info( + "Sending external message to bounceable address {} on {}...", + bounceableAddress, + network); + ExtMessageInfo tonlibResult = tonlib.sendRawMessage(message.toCell().toBase64()); if (tonlibResult.getError().getCode() != 0) { throw new Error( "Cannot send external message on " + network - + ". Error code " + + ". Error code: " + tonlibResult.getError().getCode()); } else { - System.out.printf("successfully sent external message on %s\n", network); + log.info("Successfully sent external message on {}", network); } return SendExternalResult.builder().tonlibResult(tonlibResult).build(); } else { // emulator + log.info( + "Sending external message to bounceable address {} on {}...", + stateInit.getAddress().toBounceable(), + network); EmulateTransactionResult emulateTransactionResult = txEmulator.emulateTransaction( customEmulatorShardAccount.toCell().toBase64(), message.toCell().toBase64()); - if (emulateTransactionResult.isSuccess()) { + if (emulateTransactionResult.isSuccess() + && emulateTransactionResult.getVm_exit_code() == 0) { customEmulatorShardAccount = emulateTransactionResult.getNewShardAccount(); + log.info("Successfully emulated external message on {}", network); + + // reinit TVM emulator with a new stateInit + tvmEmulator = + TvmEmulator.builder() + .codeBoc(emulateTransactionResult.getNewStateInit().getCode().toBase64()) + .dataBoc(emulateTransactionResult.getNewStateInit().getData().toBase64()) + .verbosityLevel(tvmEmulatorVerbosityLevel) + .printEmulatorInfo(false) + .build(); + emulateTransactionResult.getTransaction().printTransactionFees(true, true); emulateTransactionResult.getTransaction().printAllMessages(true); } else { - log.error("Cannot emulate transaction. Error " + emulateTransactionResult.getError()); + log.error( + "Cannot emulate transaction. Error: " + + emulateTransactionResult.getError() + + ", VM exit code: " + + emulateTransactionResult.getVm_exit_code()); } return SendExternalResult.builder().emulatorResult(emulateTransactionResult).build(); } @@ -360,15 +386,15 @@ public SendExternalResult sendExternal(Message message) { } public boolean deploy(int waitForDeploymentSeconds) { - System.out.printf("deploying on %s\n", network); try { - if (nonNull(contract)) { deployRegularContract(contract, waitForDeploymentSeconds); } else { // deploy on emulator custom contract deployCustomContract(stateInit, waitForDeploymentSeconds); } - System.out.printf("deployed on %s\n", network); + if (network == Network.EMULATOR) { + log.info("Deployed on {}", network); + } return true; } catch (Exception e) { log.error("Cannot deploy the contract on " + network + ". Error " + e.getMessage()); @@ -378,15 +404,21 @@ public boolean deploy(int waitForDeploymentSeconds) { } public GetterResult runGetMethod(String methodName) { - System.out.printf("running GetMethod %s on %s\n", methodName, network); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + methodName, + stateInit.getAddress().toBounceable(), + network); + GetterResult result = GetterResult.builder().emulatorResult(tvmEmulator.runGetMethod(methodName)).build(); if (result.getEmulatorResult().getVm_exit_code() != 0) { throw new Error( "Cannot execute run method (" + methodName - + "), Error:\n" + + "), Error:" + result.getEmulatorResult().getVm_log()); } return result; @@ -397,13 +429,22 @@ public GetterResult runGetMethod(String methodName) { } else { address = stateInit.getAddress(); } + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info("Running GetMethod {} against {} on {}...", methodName, bounceableAddress, network); + return GetterResult.builder().tonlibResult(tonlib.runMethod(address, methodName)).build(); } } public BigInteger runGetSeqNo() { - System.out.printf("running %s on %s\n", "seqno", network); if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "seqno", + stateInit.getAddress().toBounceable(), + network); return tvmEmulator.runGetSeqNo(); } else { @@ -413,21 +454,17 @@ public BigInteger runGetSeqNo() { } else { address = stateInit.getAddress(); } + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info("Running GetMethod {} against {} on {}...", "seqno", bounceableAddress, network); RunResult result = tonlib.runMethod(address, "seqno"); if (result.getExit_code() != 0) { - if (network == Network.TESTNET) { - throw new Error( - "Cannot get seqno from contract " - + address.toBounceableTestnet() - + ", exitCode " - + result.getExit_code()); - } else { - throw new Error( - "Cannot get seqno from contract " - + address.toBounceable() - + ", exitCode " - + result.getExit_code()); - } + throw new Error( + "Cannot get seqno from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); } TvmStackEntryNumber seqno = (TvmStackEntryNumber) result.getStack().get(0); @@ -436,8 +473,13 @@ public BigInteger runGetSeqNo() { } public String runGetPublicKey() { - System.out.printf("running %s on %s\n", "get_public_key", network); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "get_public_key", + stateInit.getAddress().toBounceable(), + network); return tvmEmulator.runGetPublicKey(); } else { Address address; @@ -446,21 +488,18 @@ public String runGetPublicKey() { } else { address = stateInit.getAddress(); } + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info( + "Running GetMethod {} against {} on {}...", "get_public_key", bounceableAddress, network); RunResult result = tonlib.runMethod(address, "get_public_key"); if (result.getExit_code() != 0) { - if (network == Network.TESTNET) { - throw new Error( - "Cannot get_public_key from contract " - + address.toBounceableTestnet() - + ", exitCode " - + result.getExit_code()); - } else { - throw new Error( - "Cannot get_public_key from contract " - + address.toBounceable() - + ", exitCode " - + result.getExit_code()); - } + throw new Error( + "Cannot get_public_key from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); } TvmStackEntryNumber publicKeyNumber = (TvmStackEntryNumber) result.getStack().get(0); return publicKeyNumber.getNumber().toString(16); @@ -468,8 +507,13 @@ public String runGetPublicKey() { } public BigInteger runGetSubWalletId() { - System.out.printf("running %s on %s\n", "get_subwallet_id", network); + if (network == Network.EMULATOR) { + log.info( + "Running GetMethod {} against {} on {}...", + "get_subwallet_id", + stateInit.getAddress().toBounceable(), + network); return tvmEmulator.runGetSubWalletId(); } else { Address address; @@ -478,21 +522,21 @@ public BigInteger runGetSubWalletId() { } else { address = stateInit.getAddress(); } + String bounceableAddress = + (network == Network.TESTNET) ? address.toBounceableTestnet() : address.toBounceable(); + + log.info( + "Running GetMethod {} against {} on {}...", + "get_subwallet_id", + bounceableAddress, + network); RunResult result = tonlib.runMethod(address, "get_subwallet_id"); if (result.getExit_code() != 0) { - if (network == Network.TESTNET) { - throw new Error( - "Cannot get_subwallet_id from contract " - + address.toBounceableTestnet() - + ", exitCode " - + result.getExit_code()); - } else { - throw new Error( - "Cannot get_subwallet_id from contract " - + address.toBounceable() - + ", exitCode " - + result.getExit_code()); - } + throw new Error( + "Cannot get_subwallet_id from contract " + + bounceableAddress + + ", exitCode " + + result.getExit_code()); } TvmStackEntryNumber subWalletId = (TvmStackEntryNumber) result.getStack().get(0); @@ -512,14 +556,14 @@ private BigInteger topUpFromMyLocalTonFaucet(Address address) { Utils.hexToSignedBytes( "44e67357b8e3333b617eb62f759890c95a6bb3cc95557ba60b80b8619f8b7c9d"))) .build(); - System.out.printf( - "faucetMyLocalTonWallet address %s\n", faucetMyLocalTonWallet.getAddress().toRaw()); + log.info("faucetMyLocalTonWallet address {}", faucetMyLocalTonWallet.getAddress().toRaw()); - System.out.printf("myLocalTon faucet balance %s\n", faucetMyLocalTonWallet.getBalance()); + log.info("myLocalTon faucet balance {}", faucetMyLocalTonWallet.getBalance()); nonBounceableAddress = address.toNonBounceable(); - System.out.printf( - "topping up %s with %s toncoin from MyLocalTon Faucet\n", - nonBounceableAddress, Utils.formatNanoValue(initialDeployTopUpAmount)); + log.info( + "Topping up ({}) with {} toncoin from MyLocalTon Faucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); WalletV3Config walletV3Config = WalletV3Config.builder() @@ -534,7 +578,11 @@ private BigInteger topUpFromMyLocalTonFaucet(Address address) { result = faucetMyLocalTonWallet.send(walletV3Config); if (result.getError().getCode() != 0) { - throw new Error("Cannot send external message. Error: " + result.getError().getMessage()); + throw new Error( + "Cannot send external message to " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); } tonlib.waitForBalanceChange(address, 20); @@ -544,63 +592,89 @@ private BigInteger topUpFromMyLocalTonFaucet(Address address) { private void deployRegularContract(Contract contract, int waitForDeploymentSeconds) throws InterruptedException { Address address = contract.getAddress(); - System.out.printf("contract address %s\n", address.toRaw()); + log.info("Deploying {} ({}) on {}...", contract.getName(), address.toRaw(), network); // contract.getTonlib(); if (network != Network.EMULATOR) { ExtMessageInfo result; if (waitForDeploymentSeconds != 0) { - String nonBounceableAddress = address.toNonBounceableTestnet(); - if (network == Network.MAINNET) { - System.out.printf( - "waiting %ss for toncoins to be deposited to address %s\n", - waitForDeploymentSeconds, nonBounceableAddress); + if (network == Network.MAINNET) { + String nonBounceableAddress = address.toNonBounceable(); + log.info( + "Waiting {}s for toncoins to be deposited to address {} ({})", + waitForDeploymentSeconds, + nonBounceableAddress, + address.toRaw()); tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); - System.out.println("sending external message with deploy instructions..."); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); Message msg = contract.prepareDeployMsg(); result = tonlib.sendRawMessage(msg.toCell().toBase64()); assert result.getError().getCode() != 0; tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf( - "%s deployed at address %s\n", contract.getName(), nonBounceableAddress); + log.info( + "{} deployed at non-bounceable address {} ({})", + contract.getName(), + nonBounceableAddress, + address.toBounceable()); } else if (network == Network.TESTNET) { - - System.out.printf( - "topping up %s with %s toncoin from TestnetFaucet\n", - nonBounceableAddress, Utils.formatNanoValue(initialDeployTopUpAmount)); + String nonBounceableAddress = address.toNonBounceableTestnet(); + log.info( + "Topping up {} with {} toncoin from TestnetFaucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); BigInteger newBalance = TestnetFaucet.topUpContract(tonlib, contract.getAddress(), initialDeployTopUpAmount); - System.out.printf( - "topped up successfully, new balance %s\n", Utils.formatNanoValue(newBalance)); - System.out.println("sending external message with deploy instructions..."); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); Message msg = contract.prepareDeployMsg(); result = tonlib.sendRawMessage(msg.toCell().toBase64()); if (result.getError().getCode() != 0) { throw new Error( - "Cannot send external message. Error: " + result.getError().getMessage()); + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); } tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf( - "%s deployed at address %s\n", contract.getName(), nonBounceableAddress); + log.info( + "{} deployed at bounceable address {} ({})", + contract.getName(), + address.toBounceableTestnet(), + address.toRaw()); } else { // myLocalTon - + String nonBounceableAddress = address.toNonBounceable(); // top up first BigInteger newBalance = topUpFromMyLocalTonFaucet(address); - System.out.printf( - "topped up successfully, new balance %s\n", Utils.formatNanoValue(newBalance)); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); // deploy smc Message msg = contract.prepareDeployMsg(); result = tonlib.sendRawMessage(msg.toCell().toBase64()); if (result.getError().getCode() != 0) { throw new Error( - "Cannot send external message. Error: " + result.getError().getMessage()); + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); } tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf( - "%s deployed at address %s\n", contract.getName(), nonBounceableAddress); + log.info( + "{} deployed at bounceable address {} ({})", + contract.getName(), + address.toBounceable(), + address.toRaw()); } } } @@ -612,19 +686,23 @@ private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeco String contractName = isNull(customContractAsResource) ? customContractPath : customContractAsResource; Address address = stateInit.getAddress(); - System.out.printf("contract address %s\n", address.toRaw()); + log.info("Deploying {} on {}...", address.toRaw(), network); if (network != Network.EMULATOR) { ExtMessageInfo result; if (waitForDeploymentSeconds != 0) { - String nonBounceableAddress = address.toNonBounceableTestnet(); + if (network == Network.MAINNET) { + String nonBounceableAddress = address.toNonBounceable(); - System.out.printf( - "waiting %ss for toncoins to be deposited to address %s\n", - waitForDeploymentSeconds, nonBounceableAddress); + log.info( + "Waiting {}s for toncoins to be deposited to non-bounceable address {}", + waitForDeploymentSeconds, + nonBounceableAddress); tonlib.waitForBalanceChange(address, waitForDeploymentSeconds); - System.out.println("sending external message with deploy instructions..."); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); Message msg = MsgUtils.createExternalMessage( address, @@ -633,17 +711,26 @@ private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeco result = tonlib.sendRawMessage(msg.toCell().toBase64()); assert result.getError().getCode() != 0; tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf("%s deployed at address %s", contractName, nonBounceableAddress); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceable(), + address.toRaw()); } else if (network == Network.TESTNET) { - - System.out.printf( - "topping up %s with %s toncoin from TestnetFaucet\n", - nonBounceableAddress, Utils.formatNanoValue(initialDeployTopUpAmount)); + String nonBounceableAddress = address.toNonBounceableTestnet(); + log.info( + "Topping up non-bounceable {} with {} toncoin from TestnetFaucet", + nonBounceableAddress, + Utils.formatNanoValue(initialDeployTopUpAmount)); BigInteger newBalance = TestnetFaucet.topUpContract(tonlib, address, initialDeployTopUpAmount); - System.out.printf( - "topped up successfully, new balance %s", Utils.formatNanoValue(newBalance)); - System.out.println("sending external message with deploy instructions..."); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); + log.info( + "Sending external message to non-bounceable address {} with deploy instructions...", + nonBounceableAddress); Message msg = MsgUtils.createExternalMessage( address, @@ -652,17 +739,26 @@ private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeco result = tonlib.sendRawMessage(msg.toCell().toBase64()); if (result.getError().getCode() != 0) { throw new Error( - "Cannot send external message. Error: " + result.getError().getMessage()); + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); } tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf("%s deployed at address %s\n", contractName, nonBounceableAddress); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceableTestnet(), + address.toRaw()); } else { // myLocalTon - + String nonBounceableAddress = address.toNonBounceable(); // top up first BigInteger newBalance = topUpFromMyLocalTonFaucet(address); - System.out.printf( - "topped up successfully, new balance %s\n", Utils.formatNanoValue(newBalance)); + log.info( + "Topped up ({}) successfully, new balance {}", + nonBounceableAddress, + Utils.formatNanoValue(newBalance)); // deploy smc Message msg = MsgUtils.createExternalMessage( @@ -672,11 +768,18 @@ private void deployCustomContract(StateInit stateInit, int waitForDeploymentSeco result = tonlib.sendRawMessage(msg.toCell().toBase64()); if (result.getError().getCode() != 0) { throw new Error( - "Cannot send external message. Error: " + result.getError().getMessage()); + "Cannot send external message to non-bounceable address " + + nonBounceableAddress + + ". Error: " + + result.getError().getMessage()); } tonlib.waitForDeployment(address, waitForDeploymentSeconds); - System.out.printf("%s deployed at address %s\n", contractName, nonBounceableAddress); + log.info( + "{} deployed at bounceable address {} ({})", + contractName, + address.toBounceable(), + address.toRaw()); } } } diff --git a/blockchain/src/main/resources/logback.xml b/blockchain/src/main/resources/logback.xml new file mode 100644 index 00000000..2c36c50b --- /dev/null +++ b/blockchain/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %msg%n + + + + + + + + \ No newline at end of file diff --git a/blockchain/src/test/java/org/ton/java/BlockchainTest.java b/blockchain/src/test/java/org/ton/java/BlockchainTest.java index dc91aada..812a3c62 100644 --- a/blockchain/src/test/java/org/ton/java/BlockchainTest.java +++ b/blockchain/src/test/java/org/ton/java/BlockchainTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.iwebpp.crypto.TweetNaclFast; +import java.math.BigInteger; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; @@ -251,8 +252,6 @@ public void testSendMessageV3R2ContractOnEmulator() { Blockchain blockchain = Blockchain.builder().network(Network.EMULATOR).contract(wallet).build(); assertThat(blockchain.deploy(30)).isTrue(); - blockchain.runGetMethod("seqno"); - WalletV3Config configA = WalletV3Config.builder() .walletId(42) @@ -293,7 +292,17 @@ public void testSendMessageCustomContractOnTestnetTolk() { assertThat(blockchain.deploy(30)).isTrue(); GetterResult result = blockchain.runGetMethod("unique"); System.out.printf("result %s\n", result); - System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); + + Cell bodyCell = + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .endCell(); + + Message extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); + + blockchain.sendExternal(extMsg); + System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); } @Test @@ -312,7 +321,7 @@ public void testSendMessageCustomContractOnEmulatorTolk() { .build(); assertThat(blockchain.deploy(30)).isTrue(); blockchain.runGetMethod("unique"); - System.out.printf("returned seqno %s\n", blockchain.runGetSeqNo()); + System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); Cell bodyCell = CellBuilder.beginCell() @@ -321,8 +330,50 @@ public void testSendMessageCustomContractOnEmulatorTolk() { Message extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); - SendExternalResult result = blockchain.sendExternal(extMsg); - // log.info("result {}", result); + blockchain.sendExternal(extMsg); + System.out.printf("current seqno %s\n", blockchain.runGetSeqNo()); + } + + @Test + public void testSendMessagesChainCustomContractOnEmulatorTolk() { + Blockchain blockchain = + Blockchain.builder() + .network(Network.EMULATOR) + .customContractAsResource("simple.tolk") + .customContractDataCell( + CellBuilder.beginCell() + .storeUint(0, 32) + .storeInt(Utils.getRandomInt(), 32) + .endCell()) + // .tvmEmulatorVerbosityLevel(TvmVerbosityLevel.WITH_ALL_STACK_VALUES) + // .txEmulatorVerbosityLevel(TxVerbosityLevel.WITH_ALL_STACK_VALUES) + .build(); + assertThat(blockchain.deploy(30)).isTrue(); + blockchain.runGetMethod("unique"); + BigInteger currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("current seqno %s\n", currentSeqno); + + Cell bodyCell = + CellBuilder.beginCell() + .storeUint(0, 32) // seqno + .endCell(); + + Message extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); + + blockchain.sendExternal(extMsg); + + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("current seqno %s\n", currentSeqno); + + bodyCell = + CellBuilder.beginCell() + .storeUint(1, 32) // seqno + .endCell(); + + extMsg = MsgUtils.createExternalMessage(dummyAddress, null, bodyCell); + blockchain.sendExternal(extMsg); + currentSeqno = blockchain.runGetSeqNo(); + System.out.printf("current seqno %s\n", currentSeqno); } } diff --git a/cell/pom.xml b/cell/pom.xml index b9f485bf..7ea08942 100644 --- a/cell/pom.xml +++ b/cell/pom.xml @@ -90,7 +90,6 @@ ch.qos.logback logback-classic - test org.assertj diff --git a/cell/src/main/java/org/ton/java/cell/Cell.java b/cell/src/main/java/org/ton/java/cell/Cell.java index e5fe9f1b..ad70d596 100644 --- a/cell/src/main/java/org/ton/java/cell/Cell.java +++ b/cell/src/main/java/org/ton/java/cell/Cell.java @@ -10,11 +10,13 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.*; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.bitstring.BitString; import org.ton.java.utils.Utils; /** Implements Cell class, where BitString having elements of Boolean type. */ +@Slf4j public class Cell { BitString bits; @@ -210,7 +212,6 @@ public void calculateHashes() { off = hashIndex - hashIndexOffset - 1; byte[] partHash = new byte[32]; System.arraycopy(hashes, off * 32, partHash, 0, (off + 1) * 32); - // System.out.println("partHash "+Utils.bytesToHex(partHash)); hash = Utils.concatBytes(hash, partHash); } @@ -597,7 +598,7 @@ public void toFile(String filename, boolean withCrc) { try { Files.write(Paths.get(filename), boc); } catch (Exception e) { - System.err.println("Cannot write to file. " + e.getMessage()); + log.error("Cannot write to file. Error: {} ", e.getMessage()); } } diff --git a/cell/src/main/java/org/ton/java/tlb/types/Block.java b/cell/src/main/java/org/ton/java/tlb/types/Block.java index 761ff0b4..933695ec 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/Block.java +++ b/cell/src/main/java/org/ton/java/tlb/types/Block.java @@ -4,6 +4,7 @@ import java.util.List; import lombok.Builder; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.ton.java.cell.Cell; import org.ton.java.cell.CellBuilder; import org.ton.java.cell.CellSlice; @@ -22,6 +23,7 @@ */ @Builder @Data +@Slf4j public class Block { long magic; int globalId; @@ -105,7 +107,7 @@ public List getAllTransactions() { public void printAllTransactions() { List txs = getAllTransactions(); if (txs.isEmpty()) { - System.out.println("No transactions"); + log.info("No transactions"); return; } Transaction.printTxHeader(); @@ -128,7 +130,7 @@ public List getAllMessageFees() { public void printAllMessages() { List msgFees = getAllMessageFees(); if (msgFees.isEmpty()) { - System.out.println("No messages"); + log.info("No messages"); return; } MessageFees.printMessageFeesHeader(); diff --git a/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java b/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java index 76b970de..d9293ff3 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java +++ b/cell/src/main/java/org/ton/java/tlb/types/MessageFees.java @@ -5,11 +5,13 @@ import java.math.BigInteger; import lombok.Builder; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.utils.Utils; @Builder @Data +@Slf4j public class MessageFees { String direction; String type; @@ -46,22 +48,23 @@ public void printMessageFees() { isNull(msgFee.getCreatedLt()) ? "N/A" : msgFee.getCreatedLt().toString(), getSrc(), getDst()); - System.out.println(str); + log.info(str); } public static void printMessageFeesHeader() { String header = "| in/out | type | op | value | fwdFee | ihrFee | importFee | timestamp | lt | src | dst |"; - System.out.println("\nMessages"); - System.out.println( + log.info(""); + log.info("Messages"); + log.info( "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - System.out.println(header); - System.out.println( + log.info(header); + log.info( "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } public static void printMessageFeesFooter() { - System.out.println( + log.info( "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } diff --git a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java index 8ad8f8dc..9b1708f7 100644 --- a/cell/src/main/java/org/ton/java/tlb/types/Transaction.java +++ b/cell/src/main/java/org/ton/java/tlb/types/Transaction.java @@ -8,6 +8,7 @@ import java.util.List; import lombok.Builder; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.ton.java.cell.Cell; import org.ton.java.cell.CellBuilder; @@ -39,6 +40,7 @@ */ @Builder @Data +@Slf4j public class Transaction { int magic; BigInteger accountAddr; @@ -132,7 +134,7 @@ public static Transaction deserialize(CellSlice cs) { // if (nonNull(tx.getInOut().getOut())) { // todo cleanup // for (Map.Entry entry : tx.getInOut().getOut().elements.entrySet()) // { - // System.out.println("key " + entry.getKey() + ", value " + ((Message) + // log.info("key " + entry.getKey() + ", value " + ((Message) // entry.getValue())); // } // } @@ -390,7 +392,7 @@ public void printTransactionFees(boolean withHeader, boolean withFooter) { txFees.getExitCode(), txFees.getActionCode(), txFees.getAccount()); - System.out.println(str); + log.info(str); if (withFooter) { printTxFooter(); } @@ -555,7 +557,7 @@ public List getAllMessageFees() { public void printAllMessages(boolean withHeader) { List msgFees = getAllMessageFees(); if (msgFees.isEmpty()) { - System.out.println("No messages"); + log.info("No messages"); return; } @@ -570,17 +572,18 @@ public void printAllMessages(boolean withHeader) { } public static void printTxHeader() { - System.out.println("\nTransactions"); - System.out.println( + log.info(""); + log.info("Transactions"); + log.info( "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - System.out.println( + log.info( "| op | type | valueIn | valueOut | totalFees | inForwardFee | outForwardFee | outActions | outMsgs | computeFee | exitCode | actionCode | account |"); - System.out.println( + log.info( "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } public static void printTxFooter() { - System.out.println( + log.info( "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"); } diff --git a/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java b/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java index 68cb39bc..5e8997c1 100644 --- a/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java +++ b/emulator/src/main/java/org/ton/java/emulator/tx/TxEmulator.java @@ -8,6 +8,8 @@ import com.sun.jna.Native; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinNT; +import java.io.FileOutputStream; +import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -134,32 +136,40 @@ public TxEmulator build() { private static void redirectNativeOutput() { - // Redirect native output on Windows - WinNT.HANDLE originalOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); - WinNT.HANDLE originalErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE); - - // try (FileOutputStream nulStream = new FileOutputStream("NUL")) { - WinNT.HANDLE hNul = - Kernel32.INSTANCE.CreateFile( - "NUL", - Kernel32.GENERIC_WRITE, - Kernel32.FILE_SHARE_WRITE, - null, - Kernel32.OPEN_EXISTING, - 0, - null); - - // Redirect stdout and stderr to NUL - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, hNul); - Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, hNul); - - // // Close the handle to NUL - // Kernel32.INSTANCE.CloseHandle(hNul); - // } finally { - // // Restore original stdout and stderr - // Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, originalOut); - // Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, originalErr); - // } + if ((Utils.getOS() == Utils.OS.WINDOWS) || (Utils.getOS() == Utils.OS.WINDOWS_ARM)) { + // Redirect native output on Windows + WinNT.HANDLE originalOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE); + WinNT.HANDLE originalErr = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_ERROR_HANDLE); + + try (FileOutputStream nulStream = new FileOutputStream("NUL")) { + WinNT.HANDLE hNul = + Kernel32.INSTANCE.CreateFile( + "NUL", + Kernel32.GENERIC_WRITE, + Kernel32.FILE_SHARE_WRITE, + null, + Kernel32.OPEN_EXISTING, + 0, + null); + + // Redirect stdout and stderr to NUL + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, hNul); + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, hNul); + + // Close the handle to NUL + Kernel32.INSTANCE.CloseHandle(hNul); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // Restore original stdout and stderr + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_OUTPUT_HANDLE, originalOut); + Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, originalErr); + } + } else if ((Utils.getOS() == Utils.OS.LINUX) || (Utils.getOS() == Utils.OS.LINUX_ARM)) { + // asdf + } else if ((Utils.getOS() == Utils.OS.MAC) || (Utils.getOS() == Utils.OS.MAC_ARM64)) { + // asdf + } } public void destroy() { diff --git a/fift/src/main/java/org/ton/java/fift/Executor.java b/fift/src/main/java/org/ton/java/fift/Executor.java deleted file mode 100644 index b04a4ff2..00000000 --- a/fift/src/main/java/org/ton/java/fift/Executor.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.ton.java.fift; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.ton.java.utils.Utils; - -@Slf4j -public class Executor { - - public static Pair execute( - String pathToBinary, String workDir, String... command) { - - String[] withBinaryCommand = new String[] {pathToBinary}; - - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - - try { - log.info("execute: {}", String.join(" ", withBinaryCommand)); - - final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); - - pb.directory(new File(workDir)); - Process p = pb.start(); - - p.waitFor(1, TimeUnit.SECONDS); - - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - if (p.exitValue() == 2 || p.exitValue() == 0) { - return Pair.of(p, resultInput); - } else { - log.info("exit value {}", p.exitValue()); - log.info(resultInput); - throw new Exception("Error running " + Arrays.toString(withBinaryCommand)); - } - - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } catch (Exception e) { - log.info(e.getMessage()); - throw new RuntimeException(e); - } - } - - public static Pair executeStdIn( - String pathToBinary, String workDir, String stdin, String include) { - - try { - // log.info("execute: {}", withBinaryCommand); - final ProcessBuilder pb; - String cmd = ""; - if (Utils.getOS() == Utils.OS.WINDOWS) { - - pb = - new ProcessBuilder( - "powershell", "-c", "'" + stdin + "' | " + pathToBinary + " " + include) - .redirectErrorStream(true); - cmd = - String.join( - " ", "powershell", "-c", "'" + stdin + "' | " + pathToBinary + " " + include); - } else { // linux & macos - pb = null; // todo test - cmd = String.join(" ", "echo", "'" + stdin + "' | " + pathToBinary + " " + include); - } - - log.info("execute: {}", cmd); - - pb.directory(new File(workDir)); - Process p = pb.start(); - - p.waitFor(1, TimeUnit.SECONDS); - - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - if (p.exitValue() == 2 || p.exitValue() == 0) { - return Pair.of(p, resultInput); - } else { - log.info("exit value {}", p.exitValue()); - log.info(resultInput); - throw new Exception("Error running " + cmd); - } - - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } catch (Exception e) { - log.info(e.getMessage()); - throw new RuntimeException(e); - } - } -} diff --git a/fift/src/main/java/org/ton/java/fift/FiftRunner.java b/fift/src/main/java/org/ton/java/fift/FiftRunner.java index ae35e987..396445b9 100644 --- a/fift/src/main/java/org/ton/java/fift/FiftRunner.java +++ b/fift/src/main/java/org/ton/java/fift/FiftRunner.java @@ -4,9 +4,13 @@ import static java.util.Objects.nonNull; import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -116,12 +120,12 @@ public String run(String workdir, String... params) { withInclude = new String[] {"-I", fiftAsmLibraryPath + ":" + fiftSmartcontLibraryPath}; } String[] all = ArrayUtils.addAll(withInclude, params); - Pair result = Executor.execute(fiftExecutable, workdir, all); + Pair result = execute(fiftExecutable, workdir, all); if (nonNull(result)) { try { return result.getRight(); } catch (Exception e) { - log.info("executeFift error " + e.getMessage()); + log.error("executeFift error " + e.getMessage()); return null; } } else { @@ -138,13 +142,12 @@ public String runStdIn(String workdir, String stdin) { withInclude = "-I" + fiftAsmLibraryPath + ":" + fiftSmartcontLibraryPath + " -s -"; } - Pair result = - Executor.executeStdIn(fiftExecutable, workdir, stdin, withInclude); + Pair result = executeStdIn(fiftExecutable, workdir, stdin, withInclude); if (nonNull(result)) { try { return result.getRight(); } catch (Exception e) { - log.info("executeFift error " + e.getMessage()); + log.error("executeFift error " + e.getMessage()); return null; } } else { @@ -163,4 +166,95 @@ public String getLibsPath() { public String getFiftPath() { return Utils.detectAbsolutePath("fift", false); } + + public Pair execute(String pathToBinary, String workDir, String... command) { + + String[] withBinaryCommand = new String[] {pathToBinary}; + + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + + try { + if (printInfo) { + log.info("execute: {}", String.join(" ", withBinaryCommand)); + } + + final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); + + pb.directory(new File(workDir)); + Process p = pb.start(); + + p.waitFor(1, TimeUnit.SECONDS); + + String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + if (p.exitValue() == 2 || p.exitValue() == 0) { + return Pair.of(p, resultInput); + } else { + log.error("exit value {}", p.exitValue()); + log.error(resultInput); + throw new Exception("Error running " + Arrays.toString(withBinaryCommand)); + } + + } catch (final IOException e) { + log.error(e.getMessage()); + return null; + } catch (Exception e) { + log.error(e.getMessage()); + throw new RuntimeException(e); + } + } + + public Pair executeStdIn( + String pathToBinary, String workDir, String stdin, String include) { + + try { + final ProcessBuilder pb; + String cmd = ""; + if (Utils.getOS() == Utils.OS.WINDOWS) { + + pb = + new ProcessBuilder( + "powershell", "-c", "'" + stdin + "' | " + pathToBinary + " " + include) + .redirectErrorStream(true); + cmd = + String.join( + " ", "powershell", "-c", "'" + stdin + "' | " + pathToBinary + " " + include); + } else { // linux & macos + pb = null; // todo test + cmd = String.join(" ", "echo", "'" + stdin + "' | " + pathToBinary + " " + include); + } + + if (printInfo) { + log.info("execute: {}", cmd); + } + + pb.directory(new File(workDir)); + Process p = pb.start(); + + p.waitFor(1, TimeUnit.SECONDS); + + String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + if (p.exitValue() == 2 || p.exitValue() == 0) { + return Pair.of(p, resultInput); + } else { + log.error("exit value {}", p.exitValue()); + log.error(resultInput); + throw new Exception("Error running " + cmd); + } + + } catch (final IOException e) { + log.info(e.getMessage()); + return null; + } catch (Exception e) { + log.info(e.getMessage()); + throw new RuntimeException(e); + } + } } diff --git a/func/src/main/java/org/ton/java/func/Executor.java b/func/src/main/java/org/ton/java/func/Executor.java deleted file mode 100644 index 586bd5e3..00000000 --- a/func/src/main/java/org/ton/java/func/Executor.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.ton.java.func; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.Pair; - -@Slf4j -public class Executor { - - public static Pair execute( - String pathToBinary, String workDir, String... command) { - - String[] withBinaryCommand = new String[] {pathToBinary}; - - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - - try { - log.info("execute: " + String.join(" ", withBinaryCommand)); - - final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); - - pb.directory(new File(workDir)); - Process p = pb.start(); - - p.waitFor(1, TimeUnit.SECONDS); - - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - if (p.exitValue() == 2 || p.exitValue() == 0) { - return Pair.of(p, resultInput); - } else { - log.info("exit value {}", p.exitValue()); - log.info(resultInput); - throw new Exception("Error running " + withBinaryCommand); - } - - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } catch (Exception e) { - log.info(e.getMessage()); - throw new RuntimeException(e); - } - } -} diff --git a/func/src/main/java/org/ton/java/func/FuncRunner.java b/func/src/main/java/org/ton/java/func/FuncRunner.java index 9ab5b368..53601a82 100644 --- a/func/src/main/java/org/ton/java/func/FuncRunner.java +++ b/func/src/main/java/org/ton/java/func/FuncRunner.java @@ -2,9 +2,14 @@ import static java.util.Objects.isNull; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.utils.Utils; @@ -52,7 +57,7 @@ public FuncRunner build() { funcExecutable = "func"; } catch (Exception e) { - log.info(e.getMessage()); + log.error(e.getMessage()); throw new Error("Cannot execute simple Func command.\n" + errorMsg); } } else { @@ -66,7 +71,7 @@ public FuncRunner build() { } public String run(String workdir, String... params) { - Pair result = Executor.execute(funcExecutable, workdir, params); + Pair result = execute(funcExecutable, workdir, params); if (result != null && result.getRight() != null) { return result.getRight(); @@ -78,4 +83,45 @@ public String run(String workdir, String... params) { public String getFuncPath() { return Utils.detectAbsolutePath("func", false); } + + public Pair execute(String pathToBinary, String workDir, String... command) { + + String[] withBinaryCommand = new String[] {pathToBinary}; + + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + + try { + + if (printInfo) { + log.info("execute: " + String.join(" ", withBinaryCommand)); + } + + final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); + + pb.directory(new File(workDir)); + Process p = pb.start(); + + p.waitFor(1, TimeUnit.SECONDS); + + String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + if (p.exitValue() == 2 || p.exitValue() == 0) { + return Pair.of(p, resultInput); + } else { + log.error("exit value {}", p.exitValue()); + log.error(resultInput); + throw new Exception("Error running " + withBinaryCommand); + } + + } catch (final IOException e) { + log.info(e.getMessage()); + return null; + } catch (Exception e) { + log.info(e.getMessage()); + throw new RuntimeException(e); + } + } } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java b/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java index 7978d89f..14e3fee0 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/SmartContractCompiler.java @@ -113,7 +113,7 @@ public String compile() { } if (printFiftAsmOutput) { - System.out.println(outputFiftAsmFile); + log.info(outputFiftAsmFile); } return fiftRunner.runStdIn(new File(contractPath).getParent(), outputFiftAsmFile); } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/faucet/TestnetFaucet.java b/smartcontract/src/main/java/org/ton/java/smartcontract/faucet/TestnetFaucet.java index 450e46e7..deead2ed 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/faucet/TestnetFaucet.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/faucet/TestnetFaucet.java @@ -7,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import org.ton.java.address.Address; import org.ton.java.smartcontract.types.WalletV1R3Config; -import org.ton.java.smartcontract.wallet.ContractUtils; import org.ton.java.smartcontract.wallet.v1.WalletV1R3; import org.ton.java.tonlib.Tonlib; import org.ton.java.tonlib.types.ExtMessageInfo; @@ -79,7 +78,7 @@ public static BigInteger topUpContract( throw new Error(extMessageInfo.getError().getMessage()); } - ContractUtils.waitForBalanceChange(tonlib, destinationAddress, 60); + tonlib.waitForBalanceChange(destinationAddress, 60); return tonlib.getAccountBalance(destinationAddress); } diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java index 66e9d57d..9da84cf3 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/Contract.java @@ -112,7 +112,16 @@ default void waitForDeployment(int timeoutSeconds) { } default void waitForBalanceChange(int timeoutSeconds) { - System.out.println("waiting for balance change (up to " + timeoutSeconds + "s)"); + System.out.println( + "waiting for balance change (up to " + + timeoutSeconds + + "s) - " + + (getTonlib().isTestnet() + ? getAddress().toBounceableTestnet() + : getAddress().toBounceable()) + + " - (" + + getAddress().toRaw() + + ")"); BigInteger initialBalance = getBalance(); int i = 0; do { diff --git a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java index 4a120150..aaaa1fdf 100644 --- a/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java +++ b/smartcontract/src/main/java/org/ton/java/smartcontract/wallet/ContractUtils.java @@ -20,8 +20,9 @@ public static boolean isDeployed(Tonlib tonlib, Address address) { return StringUtils.isNotEmpty(tonlib.getRawAccountState(address).getCode()); } + @Deprecated public static void waitForDeployment(Tonlib tonlib, Address address, int timeoutSeconds) { - log.info("waiting for deployment (up to " + timeoutSeconds + "s)"); + log.info("Waiting for deployment (up to {}s) - {}", timeoutSeconds, address.toRaw()); int i = 0; do { if (++i * 2 >= timeoutSeconds) { @@ -31,8 +32,9 @@ public static void waitForDeployment(Tonlib tonlib, Address address, int timeout } while (!isDeployed(tonlib, address)); } + @Deprecated public static void waitForBalanceChange(Tonlib tonlib, Address address, int timeoutSeconds) { - log.info("waiting for balance change (up to " + timeoutSeconds + "s)"); + log.info("Waiting for balance change (up to {}s) - {}", timeoutSeconds, address.toRaw()); BigInteger initialBalance = tonlib.getAccountBalance(address); int i = 0; do { @@ -45,12 +47,13 @@ public static void waitForBalanceChange(Tonlib tonlib, Address address, int time public static void waitForJettonBalanceChange( Tonlib tonlib, Address jettonMinter, Address address, int timeoutSeconds) { - log.info("waiting for jetton balance change (up to " + timeoutSeconds + "s)"); + log.info("Waiting for jetton balance change (up to {}s) - {}", timeoutSeconds, address.toRaw()); BigInteger initialBalance = getJettonBalance(tonlib, jettonMinter, address); int i = 0; do { if (++i * 2 >= timeoutSeconds) { - throw new Error("Balance was not changed within specified timeout."); + throw new Error( + "Balance of " + address.toRaw() + " was not changed within specified timeout."); } Utils.sleep(2); } while (initialBalance.equals(getJettonBalance(tonlib, jettonMinter, address))); diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestDns.java b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestDns.java index b8d3b49f..f85cdce8 100644 --- a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestDns.java +++ b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestDns.java @@ -1,5 +1,13 @@ package org.ton.java.smartcontract.integrationtests; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.ton.java.smartcontract.dns.Dns.DNS_CATEGORY_NEXT_RESOLVER; +import static org.ton.java.smartcontract.dns.Dns.DNS_CATEGORY_WALLET; + +import java.math.BigInteger; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; @@ -17,420 +25,482 @@ import org.ton.java.smartcontract.types.CollectionData; import org.ton.java.smartcontract.types.ItemData; import org.ton.java.smartcontract.types.WalletV3Config; -import org.ton.java.smartcontract.wallet.ContractUtils; import org.ton.java.smartcontract.wallet.v3.WalletV3R1; import org.ton.java.tonlib.Tonlib; import org.ton.java.tonlib.types.ExtMessageInfo; import org.ton.java.utils.Utils; -import java.math.BigInteger; -import java.util.UUID; - -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.ton.java.smartcontract.dns.Dns.DNS_CATEGORY_NEXT_RESOLVER; -import static org.ton.java.smartcontract.dns.Dns.DNS_CATEGORY_WALLET; - @Slf4j @RunWith(JUnit4.class) public class TestDns extends CommonTest { - static WalletV3R1 adminWallet; - static WalletV3R1 buyerWallet; - - @Test - public void testDnsResolveTestnet() { - Dns dns = Dns.builder() - .tonlib(tonlib) - .build(); - log.info("root DNS address = {}", dns.getRootDnsAddress()); - - Object result = dns.resolve("apple.ton", DNS_CATEGORY_NEXT_RESOLVER, true); - String resolvedAddress = ((Address) result).toBounceable(); - log.info("apple.ton resolved to {}", resolvedAddress); - assertThat(resolvedAddress).isNotEmpty(); - - // item EQD9YWaIR_M_FIDQbNP6S8miv-3FU7kIHMRqg_S6bH_bDowf - // owner EQAsEbAKNuRFDkoB6PjYP2dPTdHgt1rX2szkFFHahuDOEkbB - // new owner EQBCMRzsJBTMDqF5JW8Mbq9Ap7b88qKxkwktlZEChtLbiFIH - Address addr = (Address) dns.resolve("alice-alice-alice-9.ton", DNS_CATEGORY_NEXT_RESOLVER, true); - log.info("alice-alice-alice-9 resolved to {}", addr.toString(true, true, true)); - } - - @Test - public void testDnsResolveMainnet() { - Tonlib tonlib = Tonlib.builder().build(); - Dns dns = Dns.builder() - .tonlib(tonlib) - .build(); - Address rootAddress = dns.getRootDnsAddress(); - log.info("root DNS address = {}", rootAddress.toString(true, true, true)); - - Object result = dns.resolve("apple.ton", DNS_CATEGORY_NEXT_RESOLVER, true); - assertThat(result).isNotNull(); - log.info("apple.ton resolved to {}", ((Address) result).toString(true, true, true)); - - Address addr = (Address) dns.getWalletAddress("foundation.ton"); - log.info("foundation.ton resolved to {}", addr.toString(true, true, true)); - assertThat(addr).isNotNull(); - } - - @Test - public void testDnsRootDeploy() throws InterruptedException { - - adminWallet = GenerateWallet.randomV3R1(tonlib, 1); - - DnsRoot dnsRootContract = DnsRoot.builder() - .tonlib(tonlib) - .wc(0) - .keyPair(adminWallet.getKeyPair()) - .address1(Address.of("EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz")) - .address2(Address.of("EQCA14o1-VWhS2efqoh_9M1b_A9DtKTuoqfmkn83AbJzwnPi")) - .address3(Address.of("kQAs9VlT6S776tq3unJcP5Ogsj-ELLunLXuOb1EKcOQi47nL")) - .build(); - log.info("new root DNS address {}", dnsRootContract.getAddress()); - - WalletV3Config adminWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(1) - .destination(dnsRootContract.getAddress()) - .amount(Utils.toNano(0.12)) - .stateInit(dnsRootContract.getStateInit()) - .build(); - - ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); - assertThat(extMessageInfo.getError().getCode()).isZero(); - - dnsRootContract.waitForDeployment(45); - - assertThat(dnsRootContract.isDeployed()).isTrue(); - } - - @Test - public void testDnsCollectionItemDeploy() throws InterruptedException { - - adminWallet = GenerateWallet.randomV3R1(tonlib, 20); - buyerWallet = GenerateWallet.randomV3R1(tonlib, 20); - - log.info("admin wallet address {}", adminWallet.getAddress()); -// log.info("buyer wallet address {}", buyerWallet.getAddress().toString(true, true, true)); - - String dnsItemCodeHex = "B5EE9C7241022801000698000114FF00F4A413F4BCF2C80B0102016202030202CC04050201201E1F02012006070201481819020120080902015816170201200A0B000D470C8CB01C9D0801F73E09DBC400B434C0C05C6C2497C1383E903E900C7E800C5C75C87E800C7E800C3C0289ECE39397C15B088D148CB1C17CB865407E90350C1B5C3232C1FD00327E08E08418B9101A68608209E3402A4108308324CC200337A0404B20403C162A20032A41287E08C0683C00911DFC02440D7E08FC02F814D671C1462C200C00113E910C1C2EBCB8536003F88E34109B5F0BFA40307020F8256D8040708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00E029C70091709509D31F50AAE221F008F82321BC24C0008E9E343A3A3B8E1636363737375135C705F2E196102510241023F823F00BE30EE0310DD33F256EB31FB0926C21E30D0D0E0F00FE302680698064A98452B0BEF2E19782103B9ACA0052A0A15270BC993682103B9ACA0019A193390805E220C2008E328210557CEA20F82510396D71708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00923036E2803C23F823A1A120C2009313A0029130E24474F0091024F823F00B00D2343653CDA182103B9ACA005210A15270BC993682103B9ACA0016A1923005E220C2008E378210370FEC516D72295134544743708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB001CA10B9130E26D5477655477632EF00B0204C882105FCC3D145220BA8E9531373B5372C705F2E191109A104910384706401504E082101A0B9D515220BA8E195B32353537375135C705F2E19A03D4304015045033F823F00BE02182104EB1F0F9BAE3023B20821044BEAE41BAE302382782104ED14B65BA1310111200885B363638385147C705F2E19B04D3FF20D74AC20007D0D30701C000F2E19CF404300798D43040168307F417983050058307F45B30E270C8CB07F400C910354014F823F00B01FE30363A246EF2E19D8050F833D0F4043052408307F40E6FA1F2E19FD30721C00022C001B1F2E1A021C0008E9124109B1068517A10571046105C43144CDD9630103A395F07E201C0018E32708210370FEC51586D8100A0708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00915BE21301FE8E7A37F8235006A1810258BC066E16B0F2E19E23D0D749F823F0075290BEF2E1975178A182103B9ACA00A120C2008E32102782104ED14B6558076D72708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB0093303535E2F82381012CA0F0024477F0091045103412F823F00BE05F041501F03502FA4021F001FA40D20031FA0082103B9ACA001DA121945314A0A1DE22D70B01C300209205A19135E220C2FFF2E192218E3E821005138D91C8500BCF16500DCF1671244B145448C0708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00106994102C395BE20114008A8E3528F0018210D53276DB103946096D71708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB0093383430E21045103412F823F00B009A32353582102FCB26A2BA8E3A7082108B77173504C8CBFF5005CF161443308040708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00E05F04840FF2F00093083001258C2040FA201938083001658C20407D200CB8083001A58C204064200A38083001E58C20404B2007B8083002258C204032200538083002650C20191EB83002A4E00C9D781E9C600069006AC0BC018060840EE6B2802A0060840EE6B2802A00A08418B9101A68608209E3402A410830856456F81B04A5A9D6A0192A4139200201201A1B0201201C1D0021081BA50C1B5C0838343E903E8034CFCC200017321400F3C5807E80B2CFF26000513B513434FFFE900835D2708027DFC07E9035353D0134CFCC0415C415B80C1C1B5B5B5B490415C415A0002B01B232FFD40173C59400F3C5B3333D0032CFF27B5520020120202102012024250013BBB39F00A175F07F008802027422230010A874F00A10475F07000CA959F00A6C71000DB8FCFF00A5F03802012026270013B64A5E014204EBE0FA1000C7B461843AE9240F152118001E5C08DE014206EBE0FA1A60E038001E5C339E8086007AE140F8001E5C33B84111C466105E033E04883DCB11FB64DDC4964AD1BA06B879240DC23572F37CC5CAAAB143A2FFFBC4180012660F003C003060FE81EDF4260F00306EB1583C"; - String dnsCollectionCodeHex = "B5EE9C7241021D010002C7000114FF00F4A413F4BCF2C80B0102016202030202CC040502012017180201200607020120131402012008090201200D0E016D420C70094840FF2F0DE01D0D3030171B0925F03E0FA403001D31FED44D0D4D4303122C000E30210245F048210370FEC51BADC840FF2F080A0201200B0C00D032F82320821062E44069BCF2E0C701F00420D74920C218F2E0C8208103F0BBF2E0C92078A908C000F2E0CA21F005F2E0CB58F00714BEF2E0CC22F9018050F833206EB38E10D0F4043052108307F40E6FA131F2D0CD9130E2C85004CF16C9C85003CF1612CCC9F00C000D1C3232C072742000331C27C074C1C07000082CE500A98200B784B98C4830003CB432600201200F100201201112004F3223880875D244B5C61673C58875D2883000082CE6C070007CB83280B50C3400A44C78B98C727420007F1C0875D2638D572E882CE38B8C00B4C1C8700B48F0802C0929BE14902E6C08B08BC8F04EAC2C48B09800F05EC4EC04AC6CC82CE500A98200B784F7B99B04AEA00093083001258C2040FA201938083001658C20407D200CB8083001A58C204064200A38083001E58C20404B2007B8083002258C204032200538083002650C20191EB83002A4E00C9D781E9C600069006AC0BC018060840EE6B2802A0060840EE6B2802A00A08418B9101A68608209E3402A410830856456F81B04A5A9D6A0192A41392002015815160039D2CF8053810F805BBC00C646582AC678B387D0165B5E66664C0207D804002D007232FFFE0A33C5B25C083232C044FD003D0032C03260001B3E401D3232C084B281F2FFF27420020120191A0201201B1C0007B8B5D318001FBA7A3ED44D0D4D43031F00A7001F00B8001BB905BED44D0D4D430307FF002128009DBA30C3020D74978A908C000F2E04620D70A07C00021D749C0085210B0935B786DE0209501D3073101DE21F0035122D71830F9018200BA93C8CB0F01820167A3ED43D8CF16C90191789170E212A0018F83DF327"; - - DnsCollection dnsCollection = DnsCollection.builder() - .tonlib(tonlib) - .keyPair(adminWallet.getKeyPair()) - .collectionContent(NftUtils.createOffChainUriCell( - UUID.randomUUID().toString()) // unique collection's http address each time for testing) + static WalletV3R1 adminWallet; + static WalletV3R1 buyerWallet; + + @Test + public void testDnsResolveTestnet() { + Dns dns = Dns.builder().tonlib(tonlib).build(); + log.info("root DNS address = {}", dns.getRootDnsAddress()); + + Object result = dns.resolve("apple.ton", DNS_CATEGORY_NEXT_RESOLVER, true); + String resolvedAddress = ((Address) result).toBounceable(); + log.info("apple.ton resolved to {}", resolvedAddress); + assertThat(resolvedAddress).isNotEmpty(); + + // item EQD9YWaIR_M_FIDQbNP6S8miv-3FU7kIHMRqg_S6bH_bDowf + // owner EQAsEbAKNuRFDkoB6PjYP2dPTdHgt1rX2szkFFHahuDOEkbB + // new owner EQBCMRzsJBTMDqF5JW8Mbq9Ap7b88qKxkwktlZEChtLbiFIH + Address addr = + (Address) dns.resolve("alice-alice-alice-9.ton", DNS_CATEGORY_NEXT_RESOLVER, true); + log.info("alice-alice-alice-9 resolved to {}", addr.toString(true, true, true)); + } + + @Test + public void testDnsResolveMainnet() { + Tonlib tonlib = Tonlib.builder().build(); + Dns dns = Dns.builder().tonlib(tonlib).build(); + Address rootAddress = dns.getRootDnsAddress(); + log.info("root DNS address = {}", rootAddress.toString(true, true, true)); + + Object result = dns.resolve("apple.ton", DNS_CATEGORY_NEXT_RESOLVER, true); + assertThat(result).isNotNull(); + log.info("apple.ton resolved to {}", ((Address) result).toString(true, true, true)); + + Address addr = (Address) dns.getWalletAddress("foundation.ton"); + log.info("foundation.ton resolved to {}", addr.toString(true, true, true)); + assertThat(addr).isNotNull(); + } + + @Test + public void testDnsRootDeploy() throws InterruptedException { + + adminWallet = GenerateWallet.randomV3R1(tonlib, 1); + + DnsRoot dnsRootContract = + DnsRoot.builder() + .tonlib(tonlib) + .wc(0) + .keyPair(adminWallet.getKeyPair()) + .address1(Address.of("EQC3dNlesgVD8YbAazcauIrXBPfiVhMMr5YYk2in0Mtsz0Bz")) + .address2(Address.of("EQCA14o1-VWhS2efqoh_9M1b_A9DtKTuoqfmkn83AbJzwnPi")) + .address3(Address.of("kQAs9VlT6S776tq3unJcP5Ogsj-ELLunLXuOb1EKcOQi47nL")) + .build(); + log.info("new root DNS address {}", dnsRootContract.getAddress()); + + WalletV3Config adminWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(1) + .destination(dnsRootContract.getAddress()) + .amount(Utils.toNano(0.12)) + .stateInit(dnsRootContract.getStateInit()) + .build(); + + ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); + assertThat(extMessageInfo.getError().getCode()).isZero(); + + dnsRootContract.waitForDeployment(45); + + assertThat(dnsRootContract.isDeployed()).isTrue(); + } + + @Test + public void testDnsCollectionItemDeploy() throws InterruptedException { + + adminWallet = GenerateWallet.randomV3R1(tonlib, 20); + buyerWallet = GenerateWallet.randomV3R1(tonlib, 20); + + log.info("admin wallet address {}", adminWallet.getAddress()); + // log.info("buyer wallet address {}", buyerWallet.getAddress().toString(true, true, + // true)); + + String dnsItemCodeHex = + "B5EE9C7241022801000698000114FF00F4A413F4BCF2C80B0102016202030202CC04050201201E1F02012006070201481819020120080902015816170201200A0B000D470C8CB01C9D0801F73E09DBC400B434C0C05C6C2497C1383E903E900C7E800C5C75C87E800C7E800C3C0289ECE39397C15B088D148CB1C17CB865407E90350C1B5C3232C1FD00327E08E08418B9101A68608209E3402A4108308324CC200337A0404B20403C162A20032A41287E08C0683C00911DFC02440D7E08FC02F814D671C1462C200C00113E910C1C2EBCB8536003F88E34109B5F0BFA40307020F8256D8040708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00E029C70091709509D31F50AAE221F008F82321BC24C0008E9E343A3A3B8E1636363737375135C705F2E196102510241023F823F00BE30EE0310DD33F256EB31FB0926C21E30D0D0E0F00FE302680698064A98452B0BEF2E19782103B9ACA0052A0A15270BC993682103B9ACA0019A193390805E220C2008E328210557CEA20F82510396D71708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00923036E2803C23F823A1A120C2009313A0029130E24474F0091024F823F00B00D2343653CDA182103B9ACA005210A15270BC993682103B9ACA0016A1923005E220C2008E378210370FEC516D72295134544743708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB001CA10B9130E26D5477655477632EF00B0204C882105FCC3D145220BA8E9531373B5372C705F2E191109A104910384706401504E082101A0B9D515220BA8E195B32353537375135C705F2E19A03D4304015045033F823F00BE02182104EB1F0F9BAE3023B20821044BEAE41BAE302382782104ED14B65BA1310111200885B363638385147C705F2E19B04D3FF20D74AC20007D0D30701C000F2E19CF404300798D43040168307F417983050058307F45B30E270C8CB07F400C910354014F823F00B01FE30363A246EF2E19D8050F833D0F4043052408307F40E6FA1F2E19FD30721C00022C001B1F2E1A021C0008E9124109B1068517A10571046105C43144CDD9630103A395F07E201C0018E32708210370FEC51586D8100A0708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00915BE21301FE8E7A37F8235006A1810258BC066E16B0F2E19E23D0D749F823F0075290BEF2E1975178A182103B9ACA00A120C2008E32102782104ED14B6558076D72708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB0093303535E2F82381012CA0F0024477F0091045103412F823F00BE05F041501F03502FA4021F001FA40D20031FA0082103B9ACA001DA121945314A0A1DE22D70B01C300209205A19135E220C2FFF2E192218E3E821005138D91C8500BCF16500DCF1671244B145448C0708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00106994102C395BE20114008A8E3528F0018210D53276DB103946096D71708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB0093383430E21045103412F823F00B009A32353582102FCB26A2BA8E3A7082108B77173504C8CBFF5005CF161443308040708010C8CB055007CF165005FA0215CB6A12CB1FCB3F226EB39458CF17019132E201C901FB00E05F04840FF2F00093083001258C2040FA201938083001658C20407D200CB8083001A58C204064200A38083001E58C20404B2007B8083002258C204032200538083002650C20191EB83002A4E00C9D781E9C600069006AC0BC018060840EE6B2802A0060840EE6B2802A00A08418B9101A68608209E3402A410830856456F81B04A5A9D6A0192A4139200201201A1B0201201C1D0021081BA50C1B5C0838343E903E8034CFCC200017321400F3C5807E80B2CFF26000513B513434FFFE900835D2708027DFC07E9035353D0134CFCC0415C415B80C1C1B5B5B5B490415C415A0002B01B232FFD40173C59400F3C5B3333D0032CFF27B5520020120202102012024250013BBB39F00A175F07F008802027422230010A874F00A10475F07000CA959F00A6C71000DB8FCFF00A5F03802012026270013B64A5E014204EBE0FA1000C7B461843AE9240F152118001E5C08DE014206EBE0FA1A60E038001E5C339E8086007AE140F8001E5C33B84111C466105E033E04883DCB11FB64DDC4964AD1BA06B879240DC23572F37CC5CAAAB143A2FFFBC4180012660F003C003060FE81EDF4260F00306EB1583C"; + String dnsCollectionCodeHex = + "B5EE9C7241021D010002C7000114FF00F4A413F4BCF2C80B0102016202030202CC040502012017180201200607020120131402012008090201200D0E016D420C70094840FF2F0DE01D0D3030171B0925F03E0FA403001D31FED44D0D4D4303122C000E30210245F048210370FEC51BADC840FF2F080A0201200B0C00D032F82320821062E44069BCF2E0C701F00420D74920C218F2E0C8208103F0BBF2E0C92078A908C000F2E0CA21F005F2E0CB58F00714BEF2E0CC22F9018050F833206EB38E10D0F4043052108307F40E6FA131F2D0CD9130E2C85004CF16C9C85003CF1612CCC9F00C000D1C3232C072742000331C27C074C1C07000082CE500A98200B784B98C4830003CB432600201200F100201201112004F3223880875D244B5C61673C58875D2883000082CE6C070007CB83280B50C3400A44C78B98C727420007F1C0875D2638D572E882CE38B8C00B4C1C8700B48F0802C0929BE14902E6C08B08BC8F04EAC2C48B09800F05EC4EC04AC6CC82CE500A98200B784F7B99B04AEA00093083001258C2040FA201938083001658C20407D200CB8083001A58C204064200A38083001E58C20404B2007B8083002258C204032200538083002650C20191EB83002A4E00C9D781E9C600069006AC0BC018060840EE6B2802A0060840EE6B2802A00A08418B9101A68608209E3402A410830856456F81B04A5A9D6A0192A41392002015815160039D2CF8053810F805BBC00C646582AC678B387D0165B5E66664C0207D804002D007232FFFE0A33C5B25C083232C044FD003D0032C03260001B3E401D3232C084B281F2FFF27420020120191A0201201B1C0007B8B5D318001FBA7A3ED44D0D4D43031F00A7001F00B8001BB905BED44D0D4D430307FF002128009DBA30C3020D74978A908C000F2E04620D70A07C00021D749C0085210B0935B786DE0209501D3073101DE21F0035122D71830F9018200BA93C8CB0F01820167A3ED43D8CF16C90191789170E212A0018F83DF327"; + + DnsCollection dnsCollection = + DnsCollection.builder() + .tonlib(tonlib) + .keyPair(adminWallet.getKeyPair()) + .collectionContent( + NftUtils.createOffChainUriCell( + UUID.randomUUID() + .toString()) // unique collection's http address each time for testing) ) - .dnsItemCodeHex(dnsItemCodeHex) - .code(CellBuilder.beginCell().fromBoc(dnsCollectionCodeHex).endCell()) - .build(); - log.info("DNS collection address {}", dnsCollection.getAddress()); - - WalletV3Config adminWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(1) - .destination(dnsCollection.getAddress()) - .amount(Utils.toNano(1)) - .body(CellBuilder.beginCell() - .storeUint(0x370fec51, 32) // OP deploy new nft - .storeRef(CellBuilder.beginCell().storeString("deploy nft collection").endCell()) - .endCell()) - .stateInit(dnsCollection.getStateInit()) - .build(); - - //deploy - ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); - assertThat(extMessageInfo.getError().getCode()).isZero(); - dnsCollection.waitForDeployment(60); - - getDnsCollectionInfo(tonlib, dnsCollection.getAddress()); - - String dnsItem1DomainName = "alice-alice-alice-9"; // do not add .ton, it will fail with error code 203 - - // create and deploy DNS Item - adminWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(adminWallet.getSeqno()) - .source(adminWallet.getAddress()) - .destination(dnsCollection.getAddress()) - .amount(Utils.toNano(11)) // mind min auction price, which is 10 tons - .body(CellBuilder.beginCell() - .storeUint(0, 32) // OP deploy new nft - .storeRef(CellBuilder.beginCell().storeString(dnsItem1DomainName).endCell()) - .endCell()) - .build(); - extMessageInfo = adminWallet.send(adminWalletConfig); - - - assertThat(extMessageInfo.getError().getCode()).isZero(); - - adminWallet.waitForBalanceChange(45); - - Address dnsItem1Address = DnsCollection.getNftItemAddressByDomain(tonlib, dnsCollection.getAddress(), dnsItem1DomainName); - log.info("dnsItem1 address {}", dnsItem1Address); - - ContractUtils.waitForDeployment(tonlib, dnsItem1Address, 60); - - getDnsItemInfo(tonlib, dnsItem1Address); - - //make a bid - WalletV3Config buyerConfig = WalletV3Config.builder() - .walletId(42) - .seqno(buyerWallet.getSeqno()) - .destination(dnsItem1Address) - .amount(Utils.toNano(13)) - .build(); - - extMessageInfo = buyerWallet.send(buyerConfig); - assertThat(extMessageInfo.getError().getCode()).isZero(); - buyerWallet.waitForBalanceChange(45); - - Address dnsItem1Editor = DnsItem.getEditor(tonlib, dnsItem1Address); - log.info("dnsItem1 editor {}", nonNull(dnsItem1Editor) ? dnsItem1Editor : null); - - Utils.sleep(60 * 4, "wait till auction is finished"); - - getDnsItemInfo(tonlib, dnsItem1Address); - - // claim your domain by doing any action with it - extMessageInfo = getStaticData(adminWallet, dnsItem1Address); - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30, "Claim DNS item " + dnsItem1DomainName); - - //or assign your wallet to it, so it could resolve your wallet address to your-domain.ton - extMessageInfo = changeDnsRecord(buyerWallet, dnsItem1Address, buyerWallet.getAddress()); - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30, "Assign wallet to domain " + dnsItem1DomainName); - - getDnsItemInfo(tonlib, dnsItem1Address); - dnsItem1Editor = DnsItem.getEditor(tonlib, dnsItem1Address); - log.info("dnsItem1 editor {}", nonNull(dnsItem1Editor) ? dnsItem1Editor.toString(true, true, true) : null); - - extMessageInfo = governDnsItem(buyerWallet, dnsItem1Address); - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30, "govern dns item"); - - extMessageInfo = transferDnsItem(buyerWallet, dnsItem1Address, "EQBCMRzsJBTMDqF5JW8Mbq9Ap7b88qKxkwktlZEChtLbiFIH"); - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30, "transferring domain to other user"); - - // release of domain is possible if either one year has passed and auction has ended; - // also msg_value >= min_price and obviously only the owner can release domain name; - // once it is released the owner changed to null and auction starts again - extMessageInfo = releaseDnsItem(buyerWallet, dnsItem1Address, Utils.toNano(15)); // will fail with error code 414 - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30); - - getDnsItemInfo(tonlib, dnsItem1Address); + .dnsItemCodeHex(dnsItemCodeHex) + .code(CellBuilder.beginCell().fromBoc(dnsCollectionCodeHex).endCell()) + .build(); + log.info("DNS collection address {}", dnsCollection.getAddress()); + + WalletV3Config adminWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(1) + .destination(dnsCollection.getAddress()) + .amount(Utils.toNano(1)) + .body( + CellBuilder.beginCell() + .storeUint(0x370fec51, 32) // OP deploy new nft + .storeRef( + CellBuilder.beginCell().storeString("deploy nft collection").endCell()) + .endCell()) + .stateInit(dnsCollection.getStateInit()) + .build(); + + // deploy + ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); + assertThat(extMessageInfo.getError().getCode()).isZero(); + dnsCollection.waitForDeployment(60); + + getDnsCollectionInfo(tonlib, dnsCollection.getAddress()); + + String dnsItem1DomainName = + "alice-alice-alice-9"; // do not add .ton, it will fail with error code 203 + + // create and deploy DNS Item + adminWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(adminWallet.getSeqno()) + .source(adminWallet.getAddress()) + .destination(dnsCollection.getAddress()) + .amount(Utils.toNano(11)) // mind min auction price, which is 10 tons + .body( + CellBuilder.beginCell() + .storeUint(0, 32) // OP deploy new nft + .storeRef(CellBuilder.beginCell().storeString(dnsItem1DomainName).endCell()) + .endCell()) + .build(); + extMessageInfo = adminWallet.send(adminWalletConfig); + + assertThat(extMessageInfo.getError().getCode()).isZero(); + + adminWallet.waitForBalanceChange(45); + + Address dnsItem1Address = + DnsCollection.getNftItemAddressByDomain( + tonlib, dnsCollection.getAddress(), dnsItem1DomainName); + log.info("dnsItem1 address {}", dnsItem1Address); + + tonlib.waitForDeployment(dnsItem1Address, 60); + // ContractUtils.waitForDeployment(tonlib, dnsItem1Address, 60); + + getDnsItemInfo(tonlib, dnsItem1Address); + + // make a bid + WalletV3Config buyerConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(buyerWallet.getSeqno()) + .destination(dnsItem1Address) + .amount(Utils.toNano(13)) + .build(); + + extMessageInfo = buyerWallet.send(buyerConfig); + assertThat(extMessageInfo.getError().getCode()).isZero(); + buyerWallet.waitForBalanceChange(45); + + Address dnsItem1Editor = DnsItem.getEditor(tonlib, dnsItem1Address); + log.info("dnsItem1 editor {}", nonNull(dnsItem1Editor) ? dnsItem1Editor : null); + + Utils.sleep(60 * 4, "wait till auction is finished"); + + getDnsItemInfo(tonlib, dnsItem1Address); + + // claim your domain by doing any action with it + extMessageInfo = getStaticData(adminWallet, dnsItem1Address); + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30, "Claim DNS item " + dnsItem1DomainName); + + // or assign your wallet to it, so it could resolve your wallet address to your-domain.ton + extMessageInfo = changeDnsRecord(buyerWallet, dnsItem1Address, buyerWallet.getAddress()); + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30, "Assign wallet to domain " + dnsItem1DomainName); + + getDnsItemInfo(tonlib, dnsItem1Address); + dnsItem1Editor = DnsItem.getEditor(tonlib, dnsItem1Address); + log.info( + "dnsItem1 editor {}", + nonNull(dnsItem1Editor) ? dnsItem1Editor.toString(true, true, true) : null); + + extMessageInfo = governDnsItem(buyerWallet, dnsItem1Address); + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30, "govern dns item"); + + extMessageInfo = + transferDnsItem( + buyerWallet, dnsItem1Address, "EQBCMRzsJBTMDqF5JW8Mbq9Ap7b88qKxkwktlZEChtLbiFIH"); + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30, "transferring domain to other user"); + + // release of domain is possible if either one year has passed and auction has ended; + // also msg_value >= min_price and obviously only the owner can release domain name; + // once it is released the owner changed to null and auction starts again + extMessageInfo = + releaseDnsItem( + buyerWallet, dnsItem1Address, Utils.toNano(15)); // will fail with error code 414 + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30); + + getDnsItemInfo(tonlib, dnsItem1Address); + } + + @Test + public void testDnsItemDeployAtGlobalCollection() throws InterruptedException { + + adminWallet = GenerateWallet.randomV3R1(tonlib, 5); + log.info("admin wallet address {}", adminWallet.getAddress()); + + Address dnsCollectionAddress = Address.of("EQDjPtM6QusgMgWfl9kMcG-EALslbTITnKcH8VZK1pnH3UZA"); + log.info("DNS collection address {}", dnsCollectionAddress.toBounceable()); + + getDnsCollectionInfo(tonlib, dnsCollectionAddress); + + resolveDnsCollectionItems(dnsCollectionAddress); + + String dnsItem1DomainName = "alice-alice-alice-12"; + + // create and deploy DNS Item + WalletV3Config adminWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(adminWallet.getSeqno()) + .destination(dnsCollectionAddress) + .amount(Utils.toNano(15)) + .body( + CellBuilder.beginCell() + .storeUint(0, 32) // OP deploy new nft + .storeRef(CellBuilder.beginCell().storeString(dnsItem1DomainName).endCell()) + .endCell()) + .build(); + ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); + assertThat(extMessageInfo.getError().getCode()).isZero(); + Utils.sleep(30, "deploying DNS item " + dnsItem1DomainName); + + Address dnsItem1Address = + DnsCollection.getNftItemAddressByDomain(tonlib, dnsCollectionAddress, dnsItem1DomainName); + log.info("dnsItem1 address {}", dnsItem1Address.toString(true, true, true)); + + getDnsItemInfo(tonlib, dnsItem1Address); + + Utils.sleep(60 * 4, "wait till auction is finished"); + + getDnsItemInfo(tonlib, dnsItem1Address); + + // claim your domain by doing any action with it + extMessageInfo = getStaticData(adminWallet, dnsItem1Address); + assertThat(extMessageInfo.getError().getCode()).isZero(); + + // assign your wallet to domain name + extMessageInfo = changeDnsRecord(adminWallet, dnsItem1Address, adminWallet.getAddress()); + assertThat(extMessageInfo.getError().getCode()).isZero(); + + Utils.sleep(25); + + getDnsItemInfo(tonlib, dnsItem1Address); + + Dns dns = Dns.builder().tonlib(tonlib).build(); + Address dnsRootAddress = dns.getRootDnsAddress(); + log.info("root DNS address = {}", dnsRootAddress.toString(true, true, true)); + + Address addrDomain = + (Address) dns.resolve(dnsItem1DomainName + ".ton", DNS_CATEGORY_NEXT_RESOLVER, true); + log.info("{} resolved to {}", dnsItem1DomainName + ".ton", addrDomain); + + Address addrWallet = (Address) dns.getWalletAddress(dnsItem1DomainName + ".ton"); + log.info("{} resolved to {}", dnsItem1DomainName + ".ton", addrWallet); + + Address siteAddress = (Address) dns.getSiteAddress(dnsItem1DomainName + ".ton"); + log.info("{} resolved to {}", dnsItem1DomainName + ".ton", siteAddress); + } + + private void getDnsCollectionInfo(Tonlib tonlib, Address dnsCollectionAddres) { + CollectionData data = DnsCollection.getCollectionData(tonlib, dnsCollectionAddres); + log.info("dns collection info {}", data); + } + + private static void resolveDnsCollectionItems(Address dnsCollectionAddress) { + CellBuilder cellApple = CellBuilder.beginCell(); + cellApple.storeString("apple"); + String hashApple = Utils.bytesToHex(cellApple.endCell().hash()); + log.info("apple hash {}", hashApple); + + CellBuilder cell3Alices = CellBuilder.beginCell(); + cell3Alices.storeString("alice-alice-alice"); + String hash3Alices = Utils.bytesToHex(cell3Alices.endCell().hash()); + log.info("alice-alice-alice hash {}", hash3Alices); + + CellBuilder cellAlices = CellBuilder.beginCell(); + cellAlices.storeString( + "alicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicea"); + String hashAlices = Utils.bytesToHex(cellAlices.endCell().hash()); + log.info( + "alices hash {}", + hashAlices); // 8b98cad1bf9de7e1bd830ba3fba9608e6190825dddcf7edac7851ee16a692e81 + + Address apple = + DnsCollection.getNftItemAddressByIndex( + tonlib, dnsCollectionAddress, new BigInteger(hashApple, 16)); + Address alice3 = + DnsCollection.getNftItemAddressByIndex( + tonlib, dnsCollectionAddress, new BigInteger(hash3Alices, 16)); + Address aliceX = + DnsCollection.getNftItemAddressByIndex( + tonlib, dnsCollectionAddress, new BigInteger(hashAlices, 16)); + + log.info( + "address at index hash(apple) {} = {}", + hashApple, + apple.toString(true, true, true)); + log.info( + "address at index hash(alice-alice-alice) {} = {}", + hash3Alices, + alice3.toString(true, true, true)); + log.info( + "address at index hash(alice...) {} = {}", + hashAlices, + aliceX.toString(true, true, true)); + + Address appleAddress = + (Address) + DnsCollection.resolve( + tonlib, dnsCollectionAddress, "apple", DNS_CATEGORY_NEXT_RESOLVER, true); + log.info("apple resolved to {}", appleAddress.toString(true, true, true)); + + Address alice3Resolved = + (Address) + DnsCollection.resolve( + tonlib, + dnsCollectionAddress, + "alice-alice-alice", + DNS_CATEGORY_NEXT_RESOLVER, + true); + log.info("alice-alice-alice resolved to {}", alice3Resolved.toString(true, true, true)); + + Address alices = + (Address) + DnsCollection.resolve( + tonlib, + dnsCollectionAddress, + "alicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicea", + DNS_CATEGORY_NEXT_RESOLVER, + true); + log.info("alice... resolved to {}", alices.toString(true, true, true)); + } + + private void getDnsItemInfo(Tonlib tonlib, Address dnsItemAddress) { + ItemData data = DnsCollection.getNftItemContent(tonlib, dnsItemAddress); + log.info("dns item data {}", data); + log.info( + "dns item collection address {}", data.getCollectionAddress().toString(true, true, true)); + if (nonNull(data.getOwnerAddress())) { + log.info("dns item owner address {}", data.getOwnerAddress().toString(true, true, true)); } - @Test - public void testDnsItemDeployAtGlobalCollection() throws InterruptedException { - - adminWallet = GenerateWallet.randomV3R1(tonlib, 5); - log.info("admin wallet address {}", adminWallet.getAddress()); - - Address dnsCollectionAddress = Address.of("EQDjPtM6QusgMgWfl9kMcG-EALslbTITnKcH8VZK1pnH3UZA"); - log.info("DNS collection address {}", dnsCollectionAddress.toBounceable()); - - getDnsCollectionInfo(tonlib, dnsCollectionAddress); - - resolveDnsCollectionItems(dnsCollectionAddress); - - String dnsItem1DomainName = "alice-alice-alice-12"; - - // create and deploy DNS Item - WalletV3Config adminWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(adminWallet.getSeqno()) - .destination(dnsCollectionAddress) - .amount(Utils.toNano(15)) - .body(CellBuilder.beginCell() - .storeUint(0, 32) // OP deploy new nft - .storeRef(CellBuilder.beginCell().storeString(dnsItem1DomainName).endCell()) - .endCell()) - .build(); - ExtMessageInfo extMessageInfo = adminWallet.send(adminWalletConfig); - assertThat(extMessageInfo.getError().getCode()).isZero(); - Utils.sleep(30, "deploying DNS item " + dnsItem1DomainName); - - Address dnsItem1Address = DnsCollection.getNftItemAddressByDomain(tonlib, dnsCollectionAddress, dnsItem1DomainName); - log.info("dnsItem1 address {}", dnsItem1Address.toString(true, true, true)); - - getDnsItemInfo(tonlib, dnsItem1Address); - - Utils.sleep(60 * 4, "wait till auction is finished"); - - getDnsItemInfo(tonlib, dnsItem1Address); - - // claim your domain by doing any action with it - extMessageInfo = getStaticData(adminWallet, dnsItem1Address); - assertThat(extMessageInfo.getError().getCode()).isZero(); - - //assign your wallet to domain name - extMessageInfo = changeDnsRecord(adminWallet, dnsItem1Address, adminWallet.getAddress()); - assertThat(extMessageInfo.getError().getCode()).isZero(); - - Utils.sleep(25); - - getDnsItemInfo(tonlib, dnsItem1Address); - - Dns dns = Dns.builder().tonlib(tonlib).build(); - Address dnsRootAddress = dns.getRootDnsAddress(); - log.info("root DNS address = {}", dnsRootAddress.toString(true, true, true)); - - Address addrDomain = (Address) dns.resolve(dnsItem1DomainName + ".ton", DNS_CATEGORY_NEXT_RESOLVER, true); - log.info("{} resolved to {}", dnsItem1DomainName + ".ton", addrDomain); - - Address addrWallet = (Address) dns.getWalletAddress(dnsItem1DomainName + ".ton"); - log.info("{} resolved to {}", dnsItem1DomainName + ".ton", addrWallet); - - Address siteAddress = (Address) dns.getSiteAddress(dnsItem1DomainName + ".ton"); - log.info("{} resolved to {}", dnsItem1DomainName + ".ton", siteAddress); - } - - private void getDnsCollectionInfo(Tonlib tonlib, Address dnsCollectionAddres) { - CollectionData data = DnsCollection.getCollectionData(tonlib, dnsCollectionAddres); - log.info("dns collection info {}", data); - } - - private static void resolveDnsCollectionItems(Address dnsCollectionAddress) { - CellBuilder cellApple = CellBuilder.beginCell(); - cellApple.storeString("apple"); - String hashApple = Utils.bytesToHex(cellApple.endCell().hash()); - log.info("apple hash {}", hashApple); - - CellBuilder cell3Alices = CellBuilder.beginCell(); - cell3Alices.storeString("alice-alice-alice"); - String hash3Alices = Utils.bytesToHex(cell3Alices.endCell().hash()); - log.info("alice-alice-alice hash {}", hash3Alices); - - CellBuilder cellAlices = CellBuilder.beginCell(); - cellAlices.storeString("alicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicea"); - String hashAlices = Utils.bytesToHex(cellAlices.endCell().hash()); - log.info("alices hash {}", hashAlices); // 8b98cad1bf9de7e1bd830ba3fba9608e6190825dddcf7edac7851ee16a692e81 - - Address apple = DnsCollection.getNftItemAddressByIndex(tonlib, dnsCollectionAddress, new BigInteger(hashApple, 16)); - Address alice3 = DnsCollection.getNftItemAddressByIndex(tonlib, dnsCollectionAddress, new BigInteger(hash3Alices, 16)); - Address aliceX = DnsCollection.getNftItemAddressByIndex(tonlib, dnsCollectionAddress, new BigInteger(hashAlices, 16)); - - log.info("address at index hash(apple) {} = {}", hashApple, apple.toString(true, true, true)); - log.info("address at index hash(alice-alice-alice) {} = {}", hash3Alices, alice3.toString(true, true, true)); - log.info("address at index hash(alice...) {} = {}", hashAlices, aliceX.toString(true, true, true)); - - Address appleAddress = (Address) DnsCollection.resolve(tonlib, dnsCollectionAddress, "apple", DNS_CATEGORY_NEXT_RESOLVER, true); - log.info("apple resolved to {}", appleAddress.toString(true, true, true)); - - Address alice3Resolved = (Address) DnsCollection.resolve(tonlib, dnsCollectionAddress, "alice-alice-alice", DNS_CATEGORY_NEXT_RESOLVER, true); - log.info("alice-alice-alice resolved to {}", alice3Resolved.toString(true, true, true)); - - Address alices = (Address) DnsCollection.resolve(tonlib, dnsCollectionAddress, "alicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicealicea", DNS_CATEGORY_NEXT_RESOLVER, true); - log.info("alice... resolved to {}", alices.toString(true, true, true)); - } - - private void getDnsItemInfo(Tonlib tonlib, Address dnsItemAddress) { - ItemData data = DnsCollection.getNftItemContent(tonlib, dnsItemAddress); - log.info("dns item data {}", data); - log.info("dns item collection address {}", data.getCollectionAddress().toString(true, true, true)); - if (nonNull(data.getOwnerAddress())) { - log.info("dns item owner address {}", data.getOwnerAddress().toString(true, true, true)); - } - - if (isNull(data.getOwnerAddress())) { - AuctionInfo auctionInfo = DnsItem.getAuctionInfo(tonlib, dnsItemAddress); - Address maxBidAddress = auctionInfo.getMaxBidAddress(); - BigInteger maxBidAmount = auctionInfo.getMaxBidAmount(); - log.info("AUCTION: maxBid {}, maxBidAddress {}, endTime {}", Utils.formatNanoValue(maxBidAmount), maxBidAddress.toString(true, true, true), Utils.toUTC(auctionInfo.getAuctionEndTime())); - } else { - log.info("SOLD to {}", data.getOwnerAddress().toString(true, true, true)); - } - String domain = DnsItem.getDomain(tonlib, dnsItemAddress); - log.info("domain {}", domain); - - long lastFillUpTime = DnsItem.getLastFillUpTime(tonlib, dnsItemAddress); - log.info("lastFillUpTime {}, {}", lastFillUpTime, Utils.toUTC(lastFillUpTime)); - } - - private ExtMessageInfo changeDnsRecord(WalletV3R1 ownerWallet, Address dnsItemAddress, - Address newSmartContract) { - Cell body = DnsItem.createChangeContentEntryBody(DNS_CATEGORY_WALLET, - DnsUtils.createSmartContractAddressRecord(newSmartContract), - 0); - - WalletV3Config ownerWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(ownerWallet.getSeqno()) - .destination(dnsItemAddress) - .amount(Utils.toNano(0.07)) - .body(body) - .build(); - - return ownerWallet.send(ownerWalletConfig); - } - - private ExtMessageInfo transferDnsItem(WalletV3R1 ownerWallet, Address dnsItemAddress, String newOwner) { - WalletV3Config ownerWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(ownerWallet.getSeqno()) - .destination(dnsItemAddress) - .amount(Utils.toNano(0.07)) - .body( - DnsItem.createTransferBody( - 0, - Address.of(newOwner), - Utils.toNano(0.02), - "gift".getBytes(), - ownerWallet.getAddress()) - ) - .build(); - - return ownerWallet.send(ownerWalletConfig); - } - - private ExtMessageInfo releaseDnsItem(WalletV3R1 ownerWallet, Address dnsItemAddress, BigInteger amount) { - Cell body = CellBuilder.beginCell() - .storeUint(0x4ed14b65, 32) // op::dns_balance_release = 0x4ed14b65; - .storeUint(123, 64) - .endCell(); - WalletV3Config ownerWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(ownerWallet.getSeqno()) - .destination(dnsItemAddress) - .amount(amount) - .body(body) - .build(); - - return ownerWallet.send(ownerWalletConfig); - } - - private static ExtMessageInfo governDnsItem(WalletV3R1 ownerWallet, Address dnsItemAddress) { - Cell body = CellBuilder.beginCell() - .storeUint(0x44beae41, 32) // op::process_governance_decision = 0x44beae41; - .storeUint(123, 64) - .endCell(); - WalletV3Config ownerWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(ownerWallet.getSeqno()) - .destination(dnsItemAddress) - .amount(Utils.toNano(1)) - .body(body) - .build(); - - return ownerWallet.send(ownerWalletConfig); - } - - private static ExtMessageInfo getStaticData(WalletV3R1 ownerWallet, Address dnsItem1Address) { - WalletV3Config ownerWalletConfig = WalletV3Config.builder() - .walletId(42) - .seqno(ownerWallet.getSeqno()) - .destination(dnsItem1Address) - .amount(Utils.toNano(0.05)) - .body(DnsItem.createStaticDataBody(661)) - .build(); - - return ownerWallet.send(ownerWalletConfig); + if (isNull(data.getOwnerAddress())) { + AuctionInfo auctionInfo = DnsItem.getAuctionInfo(tonlib, dnsItemAddress); + Address maxBidAddress = auctionInfo.getMaxBidAddress(); + BigInteger maxBidAmount = auctionInfo.getMaxBidAmount(); + log.info( + "AUCTION: maxBid {}, maxBidAddress {}, endTime {}", + Utils.formatNanoValue(maxBidAmount), + maxBidAddress.toString(true, true, true), + Utils.toUTC(auctionInfo.getAuctionEndTime())); + } else { + log.info("SOLD to {}", data.getOwnerAddress().toString(true, true, true)); } + String domain = DnsItem.getDomain(tonlib, dnsItemAddress); + log.info("domain {}", domain); + + long lastFillUpTime = DnsItem.getLastFillUpTime(tonlib, dnsItemAddress); + log.info("lastFillUpTime {}, {}", lastFillUpTime, Utils.toUTC(lastFillUpTime)); + } + + private ExtMessageInfo changeDnsRecord( + WalletV3R1 ownerWallet, Address dnsItemAddress, Address newSmartContract) { + Cell body = + DnsItem.createChangeContentEntryBody( + DNS_CATEGORY_WALLET, DnsUtils.createSmartContractAddressRecord(newSmartContract), 0); + + WalletV3Config ownerWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(ownerWallet.getSeqno()) + .destination(dnsItemAddress) + .amount(Utils.toNano(0.07)) + .body(body) + .build(); + + return ownerWallet.send(ownerWalletConfig); + } + + private ExtMessageInfo transferDnsItem( + WalletV3R1 ownerWallet, Address dnsItemAddress, String newOwner) { + WalletV3Config ownerWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(ownerWallet.getSeqno()) + .destination(dnsItemAddress) + .amount(Utils.toNano(0.07)) + .body( + DnsItem.createTransferBody( + 0, + Address.of(newOwner), + Utils.toNano(0.02), + "gift".getBytes(), + ownerWallet.getAddress())) + .build(); + + return ownerWallet.send(ownerWalletConfig); + } + + private ExtMessageInfo releaseDnsItem( + WalletV3R1 ownerWallet, Address dnsItemAddress, BigInteger amount) { + Cell body = + CellBuilder.beginCell() + .storeUint(0x4ed14b65, 32) // op::dns_balance_release = 0x4ed14b65; + .storeUint(123, 64) + .endCell(); + WalletV3Config ownerWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(ownerWallet.getSeqno()) + .destination(dnsItemAddress) + .amount(amount) + .body(body) + .build(); + + return ownerWallet.send(ownerWalletConfig); + } + + private static ExtMessageInfo governDnsItem(WalletV3R1 ownerWallet, Address dnsItemAddress) { + Cell body = + CellBuilder.beginCell() + .storeUint(0x44beae41, 32) // op::process_governance_decision = 0x44beae41; + .storeUint(123, 64) + .endCell(); + WalletV3Config ownerWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(ownerWallet.getSeqno()) + .destination(dnsItemAddress) + .amount(Utils.toNano(1)) + .body(body) + .build(); + + return ownerWallet.send(ownerWalletConfig); + } + + private static ExtMessageInfo getStaticData(WalletV3R1 ownerWallet, Address dnsItem1Address) { + WalletV3Config ownerWalletConfig = + WalletV3Config.builder() + .walletId(42) + .seqno(ownerWallet.getSeqno()) + .destination(dnsItem1Address) + .amount(Utils.toNano(0.05)) + .body(DnsItem.createStaticDataBody(661)) + .build(); + + return ownerWallet.send(ownerWalletConfig); + } } diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV4R2Plugins.java b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV4R2Plugins.java index f4ffa7c1..ba18cddc 100644 --- a/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV4R2Plugins.java +++ b/smartcontract/src/test/java/org/ton/java/smartcontract/integrationtests/TestWalletV4R2Plugins.java @@ -16,7 +16,6 @@ import org.ton.java.smartcontract.types.NewPlugin; import org.ton.java.smartcontract.types.WalletV4R2Config; import org.ton.java.smartcontract.utils.MsgUtils; -import org.ton.java.smartcontract.wallet.ContractUtils; import org.ton.java.smartcontract.wallet.v4.SubscriptionInfo; import org.ton.java.smartcontract.wallet.v4.WalletV4R2; import org.ton.java.tonlib.types.ExtMessageInfo; @@ -167,7 +166,8 @@ public void testPlugins() throws InterruptedException { log.info("extMessageInfo {}", extMessageInfo); assertThat(extMessageInfo.getError().getCode()).isZero(); - ContractUtils.waitForDeployment(tonlib, beneficiaryAddress, 90); // no need? + tonlib.waitForDeployment(beneficiaryAddress, 90); + // ContractUtils.waitForDeployment(tonlib, beneficiaryAddress, 90); // no need? log.info( "beneficiaryWallet balance {}", @@ -197,7 +197,7 @@ public void testPlugins() throws InterruptedException { log.info("extMessageInfo {}", extMessageInfo); assertThat(extMessageInfo.getError().getCode()).isZero(); - ContractUtils.waitForDeployment(tonlib, subscriptionInfo.getBeneficiary(), 90); + tonlib.waitForDeployment(subscriptionInfo.getBeneficiary(), 90); log.info( "beneficiaryWallet balance {}", diff --git a/smartcontract/src/test/java/org/ton/java/smartcontract/unittests/TestSmartContractCompiler.java b/smartcontract/src/test/java/org/ton/java/smartcontract/unittests/TestSmartContractCompiler.java index 5bed149d..77098afc 100644 --- a/smartcontract/src/test/java/org/ton/java/smartcontract/unittests/TestSmartContractCompiler.java +++ b/smartcontract/src/test/java/org/ton/java/smartcontract/unittests/TestSmartContractCompiler.java @@ -23,7 +23,7 @@ public class TestSmartContractCompiler { public void testSmartContractCompiler() { SmartContractCompiler smcFunc = SmartContractCompiler.builder() - .contractAsResource("/contracts/wallets/new-wallet-v4r2.fc") + .contractAsResource("contracts/wallets/new-wallet-v4r2.fc") .build(); String codeCellHex = smcFunc.compile(); @@ -79,7 +79,7 @@ public void testSmartContractCompiler() { public void testWalletV5Compiler() { SmartContractCompiler smcFunc = SmartContractCompiler.builder() - .contractAsResource("/contracts/wallets/new-wallet-v5.fc") + .contractAsResource("contracts/wallets/new-wallet-v5.fc") .build(); String codeCellHex = smcFunc.compile(); @@ -91,7 +91,7 @@ public void testWalletV5Compiler() { public void testLibraryDeployerCompiler() { SmartContractCompiler smcFunc = SmartContractCompiler.builder() - .contractAsResource("/contracts/wallets/library-deployer.fc") + .contractAsResource("contracts/wallets/library-deployer.fc") .build(); String codeCellHex = smcFunc.compile(); diff --git a/smartcontract/src/test/resources/contracts/wallets/library-deployer.fc b/smartcontract/src/test/resources/contracts/wallets/library-deployer.fc new file mode 100644 index 00000000..f53c89d0 --- /dev/null +++ b/smartcontract/src/test/resources/contracts/wallets/library-deployer.fc @@ -0,0 +1,20 @@ +#pragma version =0.4.4; +#include "stdlib.fc"; + +() set_lib_code(cell code, int mode) impure asm "SETLIBCODE"; + +() deploy_lib() impure { + set_lib_code(get_data(), 2); + cell empty = begin_cell().end_cell(); + dump_stack(); + set_code(empty); + set_data(empty); +} + +() recv_internal() impure { + deploy_lib(); +} + +() recv_external() impure { + deploy_lib(); +} diff --git a/smartcontract/src/test/resources/contracts/wallets/new-wallet-v4r2.fc b/smartcontract/src/test/resources/contracts/wallets/new-wallet-v4r2.fc new file mode 100644 index 00000000..de132e6a --- /dev/null +++ b/smartcontract/src/test/resources/contracts/wallets/new-wallet-v4r2.fc @@ -0,0 +1,207 @@ +;; Wallet smart contract with plugins +#pragma version >=0.4.3; + +#include "stdlib.fc"; + +(slice, int) dict_get?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT"; +(cell, int) dict_add_builder?(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTADDB"; +(cell, int) dict_delete?(cell dict, int key_len, slice index) asm(index dict key_len) "DICTDEL"; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + var cs = in_msg_cell.begin_parse(); + var flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + if (in_msg.slice_bits() < 32) { + ;; ignore simple transfers + return (); + } + int op = in_msg~load_uint(32); + if (op != 0x706c7567) & (op != 0x64737472) { + ;; "plug" & "dstr" + ;; ignore all messages not related to plugins + return (); + } + slice s_addr = cs~load_msg_addr(); + (int wc, int addr_hash) = parse_std_addr(s_addr); + slice wc_n_address = begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, wc_n_address); + if ~ (success?) { + ;; it may be a transfer + return (); + } + int query_id = in_msg~load_uint(64); + var msg = begin_cell(); + if (op == 0x706c7567) { + ;; request funds + + (int r_toncoins, cell r_extra) = (in_msg~load_grams(), in_msg~load_dict()); + + [int my_balance, _] = get_balance(); + throw_unless(80, my_balance - msg_value >= r_toncoins); + + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(r_toncoins) + .store_dict(r_extra) + .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x706c7567 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + + } + + if (op == 0x64737472) { + ;; remove plugin by its request + + plugins~dict_delete?(8 + 256, wc_n_address); + var ds = get_data().begin_parse().first_bits(32 + 32 + 256); + set_data(begin_cell().store_slice(ds).store_dict(plugins).end_cell()); + ;; return coins only if bounce expected + if (flags & 2) { + msg = msg.store_uint(0x18, 6) + .store_slice(s_addr) + .store_grams(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472 | 0x80000000, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 64); + } + } +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32)); + throw_if(36, valid_until <= now()); + var ds = get_data().begin_parse(); + var (stored_seqno, stored_subwallet, public_key, plugins) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256), ds~load_dict()); + ds.end_parse(); + throw_unless(33, msg_seqno == stored_seqno); + throw_unless(34, subwallet_id == stored_subwallet); + throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); + commit(); + cs~touch(); + int op = cs~load_uint(8); + + if (op == 0) { + ;; simple send + while (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + return (); ;; have already saved the storage + } + + if (op == 1) { + ;; deploy and install plugin + int plugin_workchain = cs~load_int(8); + int plugin_balance = cs~load_grams(); + (cell state_init, cell body) = (cs~load_ref(), cs~load_ref()); + int plugin_address = cell_hash(state_init); + slice wc_n_address = begin_cell().store_int(plugin_workchain, 8).store_uint(plugin_address, 256).end_cell().begin_parse(); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(plugin_balance) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(body); + send_raw_message(msg.end_cell(), 3); + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + } + + if (op == 2) { + ;; install plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_add_builder?(8 + 256, wc_n_address, begin_cell()); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x6e6f7465, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + if (op == 3) { + ;; remove plugin + slice wc_n_address = cs~load_bits(8 + 256); + int amount = cs~load_grams(); + int query_id = cs~load_uint(64); + + (plugins, int success?) = plugins.dict_delete?(8 + 256, wc_n_address); + throw_unless(39, success?); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(4, 3).store_slice(wc_n_address) + .store_grams(amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(0x64737472, 32) ;; op + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 3); + } + + set_data(begin_cell() + .store_uint(stored_seqno + 1, 32) + .store_uint(stored_subwallet, 32) + .store_uint(public_key, 256) + .store_dict(plugins) + .end_cell()); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +int get_subwallet_id() method_id { + return get_data().begin_parse().skip_bits(32).preload_uint(32); +} + +int get_public_key() method_id { + var cs = get_data().begin_parse().skip_bits(64); + return cs.preload_uint(256); +} + +int is_plugin_installed(int wc, int addr_hash) method_id { + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + var (_, success?) = plugins.dict_get?(8 + 256, begin_cell().store_int(wc, 8).store_uint(addr_hash, 256).end_cell().begin_parse()); + return success?; +} + +tuple get_plugin_list() method_id { + var list = null(); + var ds = get_data().begin_parse().skip_bits(32 + 32 + 256); + var plugins = ds~load_dict(); + do { + var (wc_n_address, _, f) = plugins~dict::delete_get_min(8 + 256); + if (f) { + (int wc, int addr) = (wc_n_address~load_int(8), wc_n_address~load_uint(256)); + list = cons(pair(wc, addr), list); + } + } until (~ f); + return list; +} \ No newline at end of file diff --git a/smartcontract/src/test/resources/contracts/wallets/new-wallet-v5.fc b/smartcontract/src/test/resources/contracts/wallets/new-wallet-v5.fc new file mode 100644 index 00000000..a9407462 --- /dev/null +++ b/smartcontract/src/test/resources/contracts/wallets/new-wallet-v5.fc @@ -0,0 +1,301 @@ +#pragma version =0.4.4; + +#include "stdlib.fc"; + +const int error::signature_disabled = 132; +const int error::invalid_seqno = 133; +const int error::invalid_wallet_id = 134; +const int error::invalid_signature = 135; +const int error::expired = 136; +const int error::external_send_message_must_have_ignore_errors_send_mode = 137; +const int error::invalid_message_operation = 138; +const int error::add_extension = 139; +const int error::remove_extension = 140; +const int error::unsupported_action = 141; +const int error::disable_signature_when_extensions_is_empty = 142; +const int error::this_signature_mode_already_set = 143; +const int error::remove_last_extension_when_signature_disabled = 144; +const int error::extension_wrong_workchain = 145; +const int error::only_extension_can_change_signature_mode = 146; +const int error::invalid_c5 = 147; + +const int size::bool = 1; +const int size::seqno = 32; +const int size::wallet_id = 32; +const int size::public_key = 256; +const int size::valid_until = 32; +const int size::message_flags = 4; +const int size::signature = 512; +const int size::message_operation_prefix = 32; +const int size::address_hash_size = 256; +const int size::query_id = 64; + +const int prefix::signed_external = 0x7369676E; +const int prefix::signed_internal = 0x73696E74; +const int prefix::extension_action = 0x6578746E; + +(slice, int) check_and_remove_add_extension_prefix(slice body) impure asm "x{02} SDBEGINSQ"; +(slice, int) check_and_remove_remove_extension_prefix(slice body) impure asm "x{03} SDBEGINSQ"; +(slice, int) check_and_remove_set_signature_allowed_prefix(slice body) impure asm "x{04} SDBEGINSQ"; + +;;; returns the number of trailing zeroes in slice s. +int count_trailing_zeroes(slice s) asm "SDCNTTRAIL0"; + +;;; returns the last 0 ≤ l ≤ 1023 bits of s. +slice get_last_bits(slice s, int l) asm "SDCUTLAST"; +;;; returns all but the last 0 ≤ l ≤ 1023 bits of s. +slice remove_last_bits(slice s, int l) asm "SDSKIPLAST"; + +;; `action_send_msg` has 0x0ec3c86d prefix +;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 +slice enforce_and_remove_action_send_msg_prefix(slice body) impure asm "x{0ec3c86d} SDBEGINS"; + +;;; put raw list of OutActions to C5 register. +;;; OutList TLB-schema - https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L378 +;;; C5 register - https://docs.ton.org/tvm.pdf, page 11 +() set_c5_actions(cell action_list) impure asm "c5 POP"; + +;;; transforms an ordinary or exotic cell into a Slice, as if it were an ordinary cell. A flag is returned indicating whether c is exotic. If that be the case, its type can later be deserialized from the first eight bits of s. +(slice, int) begin_parse_raw(cell c) asm "XCTOS"; + +cell verify_c5_actions(cell c5, int is_external) inline { + ;; XCTOS doesn't automatically load exotic cells (unlike CTOS `begin_parse`). + ;; we use it in `verify_c5_actions` because during action phase processing exotic cells in c5 won't be unfolded too. + ;; exotic cell starts with 0x02, 0x03 or 0x04 so it will not pass action_send_msg prefix check + (slice cs, _) = c5.begin_parse_raw(); + + int count = 0; + + while (~ cs.slice_empty?()) { + ;; only `action_send_msg` is allowed; `action_set_code`, `action_reserve_currency` or `action_change_library` are not. + cs = cs.enforce_and_remove_action_send_msg_prefix(); + + throw_unless(error::invalid_c5, cs.slice_bits() == 8); ;; send_mode uint8 + throw_unless(error::invalid_c5, cs.slice_refs() == 2); ;; next-action-ref and MessageRelaxed ref + + ;; enforce that send_mode has +2 bit (ignore errors) set for external message. + ;; if such send_mode is not set and sending fails at the action phase (for example due to insufficient balance) then the seqno will not be increased and the external message will be processed again and again. + + ;; action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction; + ;; https://github.com/ton-blockchain/ton/blob/5c392e0f2d946877bb79a09ed35068f7b0bd333a/crypto/block/block.tlb#L380 + ;; load 7 bits and make sure that they end with 1 + throw_if(error::external_send_message_must_have_ignore_errors_send_mode, is_external & (count_trailing_zeroes(cs.preload_bits(7)) > 0)); + + (cs, _) = cs.preload_ref().begin_parse_raw(); + count += 1; + } + throw_unless(error::invalid_c5, count <= 255); + throw_unless(error::invalid_c5, cs.slice_refs() == 0); + + return c5; +} + +() process_actions(slice cs, int is_external, int is_extension) impure inline_ref { + cell c5_actions = cs~load_maybe_ref(); + ifnot (cell_null?(c5_actions)) { + ;; Simply set the C5 register with all pre-computed actions after verification: + set_c5_actions(c5_actions.verify_c5_actions(is_external)); + } + if (cs~load_int(1) == 0) { ;; has_other_actions + return (); + } + + ;; Loop extended actions + while (true) { + int is_add_extension = cs~check_and_remove_add_extension_prefix(); + int is_remove_extension = is_add_extension ? 0 : cs~check_and_remove_remove_extension_prefix(); + ;; Add/remove extensions + if (is_add_extension | is_remove_extension) { + (int address_wc, int address_hash) = parse_std_addr(cs~load_msg_addr()); + (int my_address_wc, _) = parse_std_addr(my_address()); + + throw_unless(error::extension_wrong_workchain, my_address_wc == address_wc); ;; the extension must be in the same workchain as the wallet. + + slice data_slice = get_data().begin_parse(); + slice data_slice_before_extensions = data_slice~load_bits(size::bool + size::seqno + size::wallet_id + size::public_key); + cell extensions = data_slice.preload_dict(); + + ;; Add extension + if (is_add_extension) { + (extensions, int is_success) = extensions.udict_add_builder?(size::address_hash_size, address_hash, begin_cell().store_int(-1, 1)); + throw_unless(error::add_extension, is_success); + } else { ;; Remove extension + (extensions, int is_success) = extensions.udict_delete?(size::address_hash_size, address_hash); + throw_unless(error::remove_extension, is_success); + int is_signature_allowed = data_slice_before_extensions.preload_int(size::bool); + throw_if(error::remove_last_extension_when_signature_disabled, null?(extensions) & (~ is_signature_allowed)); + } + + set_data(begin_cell() + .store_slice(data_slice_before_extensions) + .store_dict(extensions) + .end_cell()); + + } elseif (cs~check_and_remove_set_signature_allowed_prefix()) { ;; allow/disallow signature + throw_unless(error::only_extension_can_change_signature_mode, is_extension); + int allow_signature = cs~load_int(1); + slice data_slice = get_data().begin_parse(); + int is_signature_allowed = data_slice~load_int(size::bool); + throw_if(error::this_signature_mode_already_set, is_signature_allowed == allow_signature); + is_signature_allowed = allow_signature; + + slice data_tail = data_slice; ;; seqno, wallet_id, public_key, extensions + ifnot (allow_signature) { ;; disallow + int is_extensions_not_empty = data_slice.skip_bits(size::seqno + size::wallet_id + size::public_key).preload_int(1); + throw_unless(error::disable_signature_when_extensions_is_empty, is_extensions_not_empty); + } + + set_data(begin_cell() + .store_int(is_signature_allowed, size::bool) + .store_slice(data_tail) ;; seqno, wallet_id, public_key, extensions + .end_cell()); + } else { + throw(error::unsupported_action); + } + ifnot (cs.slice_refs()) { + return (); + } + cs = cs.preload_ref().begin_parse(); + } +} + +;; ------------------------------------------------------------------------------------------------ + +() process_signed_request(slice in_msg_body, int is_external) impure inline { + slice signature = in_msg_body.get_last_bits(size::signature); + slice signed_slice = in_msg_body.remove_last_bits(size::signature); + + slice cs = signed_slice.skip_bits(size::message_operation_prefix); ;; skip signed_internal or signed_external prefix + (int wallet_id, int valid_until, int seqno) = (cs~load_uint(size::wallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::seqno)); + + slice data_slice = get_data().begin_parse(); + int is_signature_allowed = data_slice~load_int(size::bool); + int stored_seqno = data_slice~load_uint(size::seqno); + slice data_tail = data_slice; ;; wallet_id, public_key, extensions + int stored_wallet_id = data_slice~load_uint(size::wallet_id); + int public_key = data_slice~load_uint(size::public_key); + int is_extensions_not_empty = data_slice.preload_int(1); + + int is_signature_valid = check_signature(slice_hash(signed_slice), signature, public_key); + ifnot (is_signature_valid) { + if (is_external) { + throw(error::invalid_signature); + } else { + return (); + } + } + ;; In case the wallet application has initially, by mistake, deployed a contract with the wrong bit (signature is forbidden and extensions are empty) - we allow such a contract to work. + throw_if(error::signature_disabled, (~ is_signature_allowed) & is_extensions_not_empty); + throw_unless(error::invalid_seqno, seqno == stored_seqno); + throw_unless(error::invalid_wallet_id, wallet_id == stored_wallet_id); + throw_if(error::expired, valid_until <= now()); + + if (is_external) { + accept_message(); + } + + stored_seqno = stored_seqno + 1; + set_data(begin_cell() + .store_int(true, size::bool) ;; is_signature_allowed + .store_uint(stored_seqno, size::seqno) + .store_slice(data_tail) ;; wallet_id, public_key, extensions + .end_cell()); + + if (is_external) { + ;; For external messages we commit seqno changes, so that even if an exception occurs further on, the reply-protection will still work. + commit(); + } + + process_actions(cs, is_external, false); +} + +() recv_external(slice in_msg_body) impure inline { + throw_unless(error::invalid_message_operation, in_msg_body.preload_uint(size::message_operation_prefix) == prefix::signed_external); + process_signed_request(in_msg_body, true); +} + +;; ------------------------------------------------------------------------------------------------ + +() recv_internal(cell in_msg_full, slice in_msg_body) impure inline { + if (in_msg_body.slice_bits() < size::message_operation_prefix) { + return (); ;; just receive Toncoins + } + int op = in_msg_body.preload_uint(size::message_operation_prefix); + if ((op != prefix::extension_action) & (op != prefix::signed_internal)) { + return (); ;; just receive Toncoins + } + + ;; bounced messages has 0xffffffff prefix and skipped by op check + + if (op == prefix::extension_action) { + in_msg_body~skip_bits(size::message_operation_prefix); + + slice in_msg_full_slice = in_msg_full.begin_parse(); + in_msg_full_slice~skip_bits(size::message_flags); + ;; Authenticate extension by its address. + (int sender_address_wc, int sender_address_hash) = parse_std_addr(in_msg_full_slice~load_msg_addr()); + (int my_address_wc, _) = parse_std_addr(my_address()); + + if (my_address_wc != sender_address_wc) { + return (); + } + + cell extensions = get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) + .preload_dict(); + + ;; Note that some random contract may have deposited funds with this prefix, + ;; so we accept the funds silently instead of throwing an error (wallet v4 does the same). + (_, int extension_found) = extensions.udict_get?(size::address_hash_size, sender_address_hash); + ifnot (extension_found) { + return (); + } + + in_msg_body~skip_bits(size::query_id); ;; skip query_id + + process_actions(in_msg_body, false, true); + return (); + + } + + ;; Before signature checking we handle errors silently (return), after signature checking we throw exceptions. + + ;; Check to make sure that there are enough bits for reading before signature check + if (in_msg_body.slice_bits() < size::message_operation_prefix + size::wallet_id + size::valid_until + size::seqno + size::signature) { + return (); + } + process_signed_request(in_msg_body, false); +} + +;; ------------------------------------------------------------------------------------------------ +;; Get methods + +int is_signature_allowed() method_id { + return get_data().begin_parse() + .preload_int(size::bool); +} + +int seqno() method_id { + return get_data().begin_parse() + .skip_bits(size::bool) + .preload_uint(size::seqno); +} + +int get_subwallet_id() method_id { + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno) + .preload_uint(size::wallet_id); +} + +int get_public_key() method_id { + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id) + .preload_uint(size::public_key); +} + +;; Returns raw dictionary (or null if empty) where keys are address hashes. Workchains of extensions are same with wallet smart contract workchain. +cell get_extensions() method_id { + return get_data().begin_parse() + .skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key) + .preload_dict(); +} \ No newline at end of file diff --git a/smartcontract/src/test/resources/contracts/wallets/stdlib.fc b/smartcontract/src/test/resources/contracts/wallets/stdlib.fc new file mode 100644 index 00000000..978b9473 --- /dev/null +++ b/smartcontract/src/test/resources/contracts/wallets/stdlib.fc @@ -0,0 +1,639 @@ +;; Standard library for funC +;; + +{- + This file is part of TON FunC Standard Library. + + FunC Standard Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + FunC Standard Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + +-} + +{- + # Tuple manipulation primitives + The names and the types are mostly self-explaining. + See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall) + for more info on the polymorphic functions. + + Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`) + and vise versa. +-} + +{- + # Lisp-style lists + + Lists can be represented as nested 2-elements tuples. + Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]). + For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types. +-} + +;;; Adds an element to the beginning of lisp-style list. +forall X -> tuple cons(X head, tuple tail) asm "CONS"; + +;;; Extracts the head and the tail of lisp-style list. +forall X -> (X, tuple) uncons(tuple list) asm "UNCONS"; + +;;; Extracts the tail and the head of lisp-style list. +forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS"; + +;;; Returns the head of lisp-style list. +forall X -> X car(tuple list) asm "CAR"; + +;;; Returns the tail of lisp-style list. +tuple cdr(tuple list) asm "CDR"; + +;;; Creates tuple with zero elements. +tuple empty_tuple() asm "NIL"; + +;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)` +;;; is of length at most 255. Otherwise throws a type check exception. +forall X -> tuple tpush(tuple t, X value) asm "TPUSH"; +forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH"; + +;;; Creates a tuple of length one with given argument as element. +forall X -> [X] single(X x) asm "SINGLE"; + +;;; Unpacks a tuple of length one +forall X -> X unsingle([X] t) asm "UNSINGLE"; + +;;; Creates a tuple of length two with given arguments as elements. +forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR"; + +;;; Unpacks a tuple of length two +forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR"; + +;;; Creates a tuple of length three with given arguments as elements. +forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE"; + +;;; Unpacks a tuple of length three +forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE"; + +;;; Creates a tuple of length four with given arguments as elements. +forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE"; + +;;; Unpacks a tuple of length four +forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE"; + +;;; Returns the first element of a tuple (with unknown element types). +forall X -> X first(tuple t) asm "FIRST"; + +;;; Returns the second element of a tuple (with unknown element types). +forall X -> X second(tuple t) asm "SECOND"; + +;;; Returns the third element of a tuple (with unknown element types). +forall X -> X third(tuple t) asm "THIRD"; + +;;; Returns the fourth element of a tuple (with unknown element types). +forall X -> X fourth(tuple t) asm "3 INDEX"; + +;;; Returns the first element of a pair tuple. +forall X, Y -> X pair_first([X, Y] p) asm "FIRST"; + +;;; Returns the second element of a pair tuple. +forall X, Y -> Y pair_second([X, Y] p) asm "SECOND"; + +;;; Returns the first element of a triple tuple. +forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST"; + +;;; Returns the second element of a triple tuple. +forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND"; + +;;; Returns the third element of a triple tuple. +forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD"; + + +;;; Push null element (casted to given type) +;;; By the TVM type `Null` FunC represents absence of a value of some atomic type. +;;; So `null` can actually have any atomic type. +forall X -> X null() asm "PUSHNULL"; + +;;; Moves a variable [x] to the top of the stack +forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP"; + + + +;;; Returns the current Unix time as an Integer +int now() asm "NOW"; + +;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`. +;;; If necessary, it can be parsed further using primitives such as [parse_std_addr]. +slice my_address() asm "MYADDR"; + +;;; Returns the balance of the smart contract as a tuple consisting of an int +;;; (balance in nanotoncoins) and a `cell` +;;; (a dictionary with 32-bit keys representing the balance of "extra currencies") +;;; at the start of Computation Phase. +;;; Note that RAW primitives such as [send_raw_message] do not update this field. +[int, cell] get_balance() asm "BALANCE"; + +;;; Returns the logical time of the current transaction. +int cur_lt() asm "LTIME"; + +;;; Returns the starting logical time of the current block. +int block_lt() asm "BLOCKLT"; + +;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`. +;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells. +int cell_hash(cell c) asm "HASHCU"; + +;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`. +;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created +;;; and its hash computed by [cell_hash]. +int slice_hash(slice s) asm "HASHSU"; + +;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight, +;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`. +int string_hash(slice s) asm "SHA256U"; + +{- + # Signature checks +-} + +;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data) +;;; using [public_key] (also represented by a 256-bit unsigned integer). +;;; The signature must contain at least 512 data bits; only the first 512 bits are used. +;;; The result is `−1` if the signature is valid, `0` otherwise. +;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`. +;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice, +;;; the second hashing occurring inside `CHKSIGNS`. +int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; + +;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`, +;;; similarly to [check_signature]. +;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception. +;;; The verification of Ed25519 signatures is the standard one, +;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed. +int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; + +{--- + # Computation of boc size + The primitives below may be useful for computing storage fees of user-provided data. +-} + +;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`. +;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z` +;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account +;;; the identification of equal cells. +;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG, +;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells. +;;; The total count of visited cells `x` cannot exceed non-negative [max_cells]; +;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and +;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`. +(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE"; + +;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`. +;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself; +;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`. +(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE"; + +;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure. +(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure. +(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT"; + +;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator) +;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; + +{-- + # Debug primitives + Only works for local TVM execution with debug level verbosity +-} +;;; Dumps the stack (at most the top 255 values) and shows the total stack depth. +() dump_stack() impure asm "DUMPSTK"; + +{- + # Persistent storage save and load +-} + +;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later. +cell get_data() asm "c4 PUSH"; + +;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive. +() set_data(cell c) impure asm "c4 POP"; + +{- + # Continuation primitives +-} +;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls. +;;; The primitive returns the current value of `c3`. +cont get_c3() impure asm "c3 PUSH"; + +;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time. +;;; Note that after execution of this primitive the current code +;;; (and the stack of recursive function calls) won't change, +;;; but any other function call will use a function from the new code. +() set_c3(cont c) impure asm "c3 POP"; + +;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist. +cont bless(slice s) impure asm "BLESS"; + +{--- + # Gas related primitives +-} + +;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero, +;;; decreasing the value of `gr` by `gc` in the process. +;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction. +;;; This action is required to process external messages, which bring no value (hence no gas) with themselves. +;;; +;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept). +() accept_message() impure asm "ACCEPT"; + +;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero. +;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`, +;;; an (unhandled) out of gas exception is thrown before setting new gas limits. +;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message]. +() set_gas_limit(int limit) impure asm "SETGASLIMIT"; + +;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”) +;;; so that the current execution is considered “successful” with the saved values even if an exception +;;; in Computation Phase is thrown later. +() commit() impure asm "COMMIT"; + +;;; Not implemented +;;() buy_gas(int gram) impure asm "BUYGAS"; + +;;; Computes the amount of gas that can be bought for `amount` nanoTONs, +;;; and sets `gl` accordingly in the same way as [set_gas_limit]. +() buy_gas(int amount) impure asm "BUYGAS"; + +;;; Computes the minimum of two integers [x] and [y]. +int min(int x, int y) asm "MIN"; + +;;; Computes the maximum of two integers [x] and [y]. +int max(int x, int y) asm "MAX"; + +;;; Sorts two integers. +(int, int) minmax(int x, int y) asm "MINMAX"; + +;;; Computes the absolute value of an integer [x]. +int abs(int x) asm "ABS"; + +{- + # Slice primitives + + It is said that a primitive _loads_ some data, + if it returns the data and the remainder of the slice + (so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)). + + It is said that a primitive _preloads_ some data, if it returns only the data + (it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)). + + Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice. +-} + + +;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell, +;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2) +;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards. +slice begin_parse(cell c) asm "CTOS"; + +;;; Checks if [s] is empty. If not, throws an exception. +() end_parse(slice s) impure asm "ENDS"; + +;;; Loads the first reference from the slice. +(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF"; + +;;; Preloads the first reference from the slice. +cell preload_ref(slice s) asm "PLDREF"; + + {- Functions below are commented because are implemented on compilator level for optimisation -} + +;;; Loads a signed [len]-bit integer from a slice [s]. +;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX"; + +;;; Loads an unsigned [len]-bit integer from a slice [s]. +;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX"; + +;;; Preloads a signed [len]-bit integer from a slice [s]. +;; int preload_int(slice s, int len) asm "PLDIX"; + +;;; Preloads an unsigned [len]-bit integer from a slice [s]. +;; int preload_uint(slice s, int len) asm "PLDUX"; + +;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX"; + +;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`. +;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; + +;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^120 - 1`). +(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; +(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS"; + +;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; + +;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice first_bits(slice s, int len) asm "SDCUTFIRST"; + +;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; + +;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s]. +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;;; Loads a dictionary `D` (HashMapE) from `slice` [s]. +;;; (returns `null` if `nothing` constructor is used). +(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; + +;;; Preloads a dictionary `D` from `slice` [s]. +cell preload_dict(slice s) asm "PLDDICT"; + +;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice. +slice skip_dict(slice s) asm "SKIPDICT"; + +;;; Loads (Maybe ^Cell) from `slice` [s]. +;;; In other words loads 1 bit and if it is true +;;; loads first ref and return it with slice remainder +;;; otherwise returns `null` and slice remainder +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; + +;;; Preloads (Maybe ^Cell) from `slice` [s]. +cell preload_maybe_ref(slice s) asm "PLDOPTREF"; + + +;;; Returns the depth of `cell` [c]. +;;; If [c] has no references, then return `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c]. +;;; If [c] is a `null` instead of a cell, returns zero. +int cell_depth(cell c) asm "CDEPTH"; + + +{- + # Slice size primitives +-} + +;;; Returns the number of references in `slice` [s]. +int slice_refs(slice s) asm "SREFS"; + +;;; Returns the number of data bits in `slice` [s]. +int slice_bits(slice s) asm "SBITS"; + +;;; Returns both the number of data bits and the number of references in `slice` [s]. +(int, int) slice_bits_refs(slice s) asm "SBITREFS"; + +;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references). +int slice_empty?(slice s) asm "SEMPTY"; + +;;; Checks whether `slice` [s] has no bits of data. +int slice_data_empty?(slice s) asm "SDEMPTY"; + +;;; Checks whether `slice` [s] has no references. +int slice_refs_empty?(slice s) asm "SREMPTY"; + +;;; Returns the depth of `slice` [s]. +;;; If [s] has no references, then returns `0`; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s]. +int slice_depth(slice s) asm "SDEPTH"; + +{- + # Builder size primitives +-} + +;;; Returns the number of cell references already stored in `builder` [b] +int builder_refs(builder b) asm "BREFS"; + +;;; Returns the number of data bits already stored in `builder` [b]. +int builder_bits(builder b) asm "BBITS"; + +;;; Returns the depth of `builder` [b]. +;;; If no cell references are stored in [b], then returns 0; +;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b]. +int builder_depth(builder b) asm "BDEPTH"; + +{- + # Builder primitives + It is said that a primitive _stores_ a value `x` into a builder `b` + if it returns a modified version of the builder `b'` with the value `x` stored at the end of it. + It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods). + + All the primitives below first check whether there is enough space in the `builder`, + and only then check the range of the value being serialized. +-} + +;;; Creates a new empty `builder`. +builder begin_cell() asm "NEWC"; + +;;; Converts a `builder` into an ordinary `cell`. +cell end_cell(builder b) asm "ENDC"; + +;;; Stores a reference to `cell` [c] into `builder` [b]. +builder store_ref(builder b, cell c) asm(c b) "STREF"; + +;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`. +;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX"; + +;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`. +;; builder store_int(builder b, int x, int len) asm(x b len) "STIX"; + + +;;; Stores `slice` [s] into `builder` [b] +builder store_slice(builder b, slice s) asm "STSLICER"; + +;;; Stores (serializes) an integer [x] in the range `0..2^120 − 1` into `builder` [b]. +;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`, +;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`, +;;; followed by an `8l`-bit unsigned big-endian representation of [x]. +;;; If [x] does not belong to the supported range, a range check exception is thrown. +;;; +;;; Store amounts of TonCoins to the builder as VarUInteger 16 +builder store_grams(builder b, int x) asm "STGRAMS"; +builder store_coins(builder b, int x) asm "STGRAMS"; + +;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b]. +;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise. +builder store_dict(builder b, cell c) asm(c b) "STDICT"; + +;;; Stores (Maybe ^Cell) to builder: +;;; if cell is null store 1 zero bit +;;; otherwise store 1 true bit and ref to cell +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + + +{- + # Address manipulation primitives + The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme: + ```TL-B + addr_none$00 = MsgAddressExt; + addr_extern$01 len:(## 8) external_address:(bits len) + = MsgAddressExt; + anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; + addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + workchain_id:int32 address:(bits addr_len) = MsgAddressInt; + _ _:MsgAddressInt = MsgAddress; + _ _:MsgAddressExt = MsgAddress; + + int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddress dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt + created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed; + ``` + A deserialized `MsgAddress` is represented by a tuple `t` as follows: + + - `addr_none` is represented by `t = (0)`, + i.e., a tuple containing exactly one integer equal to zero. + - `addr_extern` is represented by `t = (1, s)`, + where slice `s` contains the field `external_address`. In other words, ` + t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`. + - `addr_std` is represented by `t = (2, u, x, s)`, + where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present). + Next, integer `x` is the `workchain_id`, and slice `s` contains the address. + - `addr_var` is represented by `t = (3, u, x, s)`, + where `u`, `x`, and `s` have the same meaning as for `addr_std`. +-} + +;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`, +;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices. +(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR"; + +;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`. +;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown. +tuple parse_addr(slice s) asm "PARSEMSGADDR"; + +;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`), +;;; applies rewriting from the anycast (if present) to the same-length prefix of the address, +;;; and returns both the workchain and the 256-bit address as integers. +;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`, +;;; throws a cell deserialization exception. +(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR"; + +;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s], +;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`). +(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR"; + +{- + # Dictionary primitives +-} + + +;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; +(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF"; + +;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell), +;;; and returns the resulting dictionary. +cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; +(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF"; + +cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF"; +(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT"; +(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT"; +(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF"; +(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF"; +(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL"; +(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL"; +(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT"; +(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT"; +(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; +(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; +cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; +cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; +cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB"; +(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB"; +(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB"; +(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB"; +(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB"; +(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2"; +(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2"; +(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2"; +(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2"; +(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2"; +(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2"; + +;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL +cell new_dict() asm "NEWDICT"; +;;; Checks whether a dictionary is empty. Equivalent to cell_null?. +int dict_empty?(cell c) asm "DICTEMPTY"; + + +{- Prefix dictionary primitives -} +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2"; +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET"; +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL"; + +;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value. +cell config_param(int x) asm "CONFIGOPTPARAM"; +;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in. +int cell_null?(cell c) asm "ISNULL"; + +;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15. +() raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved. +() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX"; +;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128. +() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG"; +;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract +() set_code(cell new_code) impure asm "SETCODE"; + +;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x. +int random() impure asm "RANDU256"; +;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed. +int rand(int range) impure asm "RAND"; +;;; Returns the current random seed as an unsigned 256-bit Integer. +int get_seed() impure asm "RANDSEED"; +;;; Sets the random seed to unsigned 256-bit seed. +() set_seed(int) impure asm "SETRAND"; +;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x. +() randomize(int x) impure asm "ADDRAND"; +;;; Equivalent to randomize(cur_lt());. +() randomize_lt() impure asm "LTIME" "ADDRAND"; + +;;; Checks whether the data parts of two slices coinside +int equal_slice_bits (slice a, slice b) asm "SDEQ"; + +;;; Concatenates two builders +builder store_builder(builder to, builder from) asm "STBR"; diff --git a/tolk/src/main/java/org/ton/java/tolk/Executor.java b/tolk/src/main/java/org/ton/java/tolk/Executor.java deleted file mode 100644 index bff9a141..00000000 --- a/tolk/src/main/java/org/ton/java/tolk/Executor.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.ton.java.tolk; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.concurrent.TimeUnit; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.tuple.Pair; - -@Slf4j -public class Executor { - - public static Pair execute( - String pathToBinary, String workDir, String... command) { - - String[] withBinaryCommand = new String[] {pathToBinary}; - - withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); - - try { - log.info("execute: " + String.join(" ", withBinaryCommand)); - - final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); - - pb.directory(new File(workDir)); - Process p = pb.start(); - - p.waitFor(1, TimeUnit.SECONDS); - - String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); - - p.getInputStream().close(); - p.getErrorStream().close(); - p.getOutputStream().close(); - if (p.exitValue() == 2 || p.exitValue() == 0) { - return Pair.of(p, resultInput); - } else { - log.info("exit value {}", p.exitValue()); - log.info(resultInput); - throw new Exception("Error running " + withBinaryCommand); - } - - } catch (final IOException e) { - log.info(e.getMessage()); - return null; - } catch (Exception e) { - log.info(e.getMessage()); - throw new RuntimeException(e); - } - } -} diff --git a/tolk/src/main/java/org/ton/java/tolk/TolkRunner.java b/tolk/src/main/java/org/ton/java/tolk/TolkRunner.java index baf92de4..a911bc96 100644 --- a/tolk/src/main/java/org/ton/java/tolk/TolkRunner.java +++ b/tolk/src/main/java/org/ton/java/tolk/TolkRunner.java @@ -2,9 +2,14 @@ import static java.util.Objects.isNull; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.ton.java.utils.Utils; @@ -69,7 +74,7 @@ public TolkRunner build() { } public String run(String workdir, String... params) { - Pair result = Executor.execute(tolkExecutable, workdir, params); + Pair result = execute(tolkExecutable, workdir, params); if (result != null && result.getRight() != null) { return result.getRight(); @@ -81,4 +86,44 @@ public String run(String workdir, String... params) { public String getTolkPath() { return Utils.detectAbsolutePath("tolk", false); } + + public Pair execute(String pathToBinary, String workDir, String... command) { + + String[] withBinaryCommand = new String[] {pathToBinary}; + + withBinaryCommand = ArrayUtils.addAll(withBinaryCommand, command); + + try { + if (printInfo) { + log.info("execute: " + String.join(" ", withBinaryCommand)); + } + + final ProcessBuilder pb = new ProcessBuilder(withBinaryCommand).redirectErrorStream(true); + + pb.directory(new File(workDir)); + Process p = pb.start(); + + p.waitFor(1, TimeUnit.SECONDS); + + String resultInput = IOUtils.toString(p.getInputStream(), Charset.defaultCharset()); + + p.getInputStream().close(); + p.getErrorStream().close(); + p.getOutputStream().close(); + if (p.exitValue() == 2 || p.exitValue() == 0) { + return Pair.of(p, resultInput); + } else { + log.error("exit value {}", p.exitValue()); + log.error(resultInput); + throw new Exception("Error running " + withBinaryCommand); + } + + } catch (final IOException e) { + log.info(e.getMessage()); + return null; + } catch (Exception e) { + log.info(e.getMessage()); + throw new RuntimeException(e); + } + } } diff --git a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java index 1618cb2b..a72ce037 100644 --- a/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java +++ b/tonlib/src/main/java/org/ton/java/tonlib/Tonlib.java @@ -459,7 +459,9 @@ && nonNull(sync.getSync_state()) (sync.getSync_state().getCurrent_seqno() * 100) / (double) sync.getSync_state().getTo_seqno(); } - log.info("Synchronized: " + String.format("%.2f%%", pct)); + if (pct < 99.5) { + log.info("Synchronized: " + String.format("%.2f%%", pct)); + } } if (isNull(response)) { throw new RuntimeException("Error in waitForSyncDone(), response is null."); @@ -478,7 +480,6 @@ && nonNull(sync.getSync_state()) "Error in tonlib.receive(), " + receiveRetryTimes + " times was not able retrieve result from lite-server."); - // break outterloop; } tonlibJson.tonlib_client_json_send(tonlib, query); @@ -1583,7 +1584,11 @@ public boolean isDeployed(Address address) { } public void waitForDeployment(Address address, int timeoutSeconds) { - log.info("waiting for deployment (up to {}s)", timeoutSeconds); + log.info( + "Waiting for deployment (up to {}s) - {} - ({})", + timeoutSeconds, + testnet ? address.toBounceableTestnet() : address.toBounceable(), + address.toRaw()); int i = 0; do { if (++i * 2 >= timeoutSeconds) { @@ -1594,12 +1599,17 @@ public void waitForDeployment(Address address, int timeoutSeconds) { } public void waitForBalanceChange(Address address, int timeoutSeconds) { - log.info("waiting for balance change up to {}s", timeoutSeconds); + log.info( + "Waiting for balance change up to {}s - {} - ({})", + timeoutSeconds, + testnet ? address.toBounceableTestnet() : address.toBounceable(), + address.toRaw()); BigInteger initialBalance = getAccountBalance(address); int i = 0; do { if (++i * 2 >= timeoutSeconds) { - throw new Error("Balance was not changed within specified timeout."); + throw new Error( + "Balance of " + address.toRaw() + "was not changed within specified timeout."); } Utils.sleep(2); } while (initialBalance.equals(getAccountBalance(address))); @@ -1634,4 +1644,8 @@ private static void redirectNativeOutput() { // Kernel32.INSTANCE.SetStdHandle(Kernel32.STD_ERROR_HANDLE, originalErr); // } } + + public boolean isTestnet() { + return testnet; + } }