Contracts written in Kotlin don't work

Hey everyone.
I am currently trying to develop a smart contract on Ardor. Since Ardor supports "any other language which uses the Java JVM as its runtime environment" and I'm more comfortable with Kotlin than with Java (Kotlin compiles to JVM bytecode), I started developing it in Kotlin.
Everything went pretty smoothly, unit tests were successful etc. After reaching a state I was happy with, I wanted to deploy my contract to the testnet.
The contract deployed without any problems. Now I tried to tigger the contract with a transaction. No response.
I look into the logs and discover an error:

2021-09-03 17:30:06 INFO: pool-2-thread-30 Invoking processTransaction on contract com.jelurida.ardor.contracts.HelloWorldKotlin
2021-09-03 17:30:06 INFO: pool-2-thread-30 Error running contract com.jelurida.ardor.contracts.HelloWorldKotlin
java.lang.NullPointerException: Cannot read the array length because "<local4>" is null
	at nxt.addons.CloudDataClassLoader.findClass(CloudDataClassLoader.java:91)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
	at nxt.addons.CloudDataClassLoader.loadClass(CloudDataClassLoader.java:106)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	at com.jelurida.ardor.contracts.HelloWorldKotlin.processTransaction(HelloWorldKotlin.kt)
	at nxt.addons.ContractRunner$INVOCATION_TYPE$1.invokeMethod(ContractRunner.java:91)
	at nxt.addons.ContractRunner.invokeContract(ContractRunner.java:433)
	at nxt.addons.ContractRunner.processImpl(ContractRunner.java:426)
	at nxt.addons.ContractRunner.process(ContractRunner.java:362)
	at nxt.addons.ContractRunner.processTransaction(ContractRunner.java:702)
	at nxt.addons.ContractRunner.processConfirmed(ContractRunner.java:469)
	at nxt.util.Listeners.notify(Listeners.java:48)
	at nxt.blockchain.TransactionProcessorImpl.notifyListeners(TransactionProcessorImpl.java:315)
	at nxt.blockchain.BlockchainProcessorImpl.accept(BlockchainProcessorImpl.java:1729)
	at nxt.blockchain.BlockchainProcessorImpl.pushBlock(BlockchainProcessorImpl.java:1467)
	at nxt.blockchain.BlockchainProcessorImpl.processPeerBlock(BlockchainProcessorImpl.java:1175)
	at nxt.peer.BlockInventory.lambda$processRequest$1(BlockInventory.java:141)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:832)
2021-09-03 17:30:06 SEVERE: pool-2-thread-30 {"errorDescription":"java.lang.NullPointerException: Cannot read the array length because \"<local4>\" is null","errorCode":11002}

The same error occurs when I deploy the default HelloWorld contract converted into Kotlin, so it seems like Kotlin contracts don't work in general?
I have also tried deploying the contract manually using the contractManager CLI in case the IntelliJ plugin doesn't like Kotlin, but still no luck.

Can anyone tell me if Ardor supports contracts written in Kotlin? If so, how can I develop/deploy one without getting this error?
Any help will be greatly appreciated!

Thanks for the report. We'll looking into it.

Frankly we didn't try Kotlin earlier. I tried it now and it seems to work on my side with 2 problems:

  1. I had to add kotlin-stdlib ( kotlin-stdlib-1.5.10.jar in my case) to the classpath of the contract runner node, and while executing the ContractManager. Without it I was getting same exception as you. So try to add that jar to the lib directory of the contract runner node.

  2. After converting the HelloWorld contract I was getting this exception

2021-09-07 13:43:38 INFO: pool-2-thread-10 Error running contract com.jelurida.test.HelloWorldCont
java.lang.UnsupportedOperationException
	at java.util.AbstractMap.put(AbstractMap.java:209)
	at com.jelurida.test.HelloWorldCont.processTransaction(HelloWorldCont.kt:15)
	at nxt.addons.ContractRunner$INVOCATION_TYPE$1.invokeMethod(ContractRunner.java:91)
	at nxt.addons.ContractRunner.invokeContract(ContractRunner.java:433)
	at nxt.addons.ContractRunner.processImpl(ContractRunner.java:426)
	at nxt.addons.ContractRunner.process(ContractRunner.java:362)
	at nxt.addons.ContractRunner.processTransaction(ContractRunner.java:702)
	at nxt.addons.ContractRunner.processConfirmed(ContractRunner.java:469)
	at nxt.util.Listeners.notify(Listeners.java:48)
	at nxt.blockchain.TransactionProcessorImpl.notifyListeners(TransactionProcessorImpl.java:315)
	at nxt.blockchain.BlockchainProcessorImpl.accept(BlockchainProcessorImpl.java:1729)
	at nxt.blockchain.BlockchainProcessorImpl.pushBlock(BlockchainProcessorImpl.java:1467)
	at nxt.blockchain.BlockchainProcessorImpl.processPeerBlock(BlockchainProcessorImpl.java:1175)
	at nxt.peer.BlockInventory.lambda$processRequest$1(BlockInventory.java:141)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

The problem here is that the automatic conversion to Kotlin transforms the line

message.put("text", "Hello " + triggerTransaction.getSenderRs());

to

message["text"] = "Hello " + triggerTransaction.senderRs

Which then compiles to a call to AbstractMap.put(Object, Object) instead of JO.put(String, Object). So I edited the Kotlin contract to explicitly call put:

message.put("text", "Hello " + triggerTransaction.senderRs)

... and it seems to work. But I think we should fix the JO class to implement AbstractMap.put(Object, Object).

2 Likes

I have now tried to add the kotlin-stdlib.jar that's bundeled with IntelliJ (in my case located in C:\Users\username\AppData\Roaming\JetBrains\IdeaIC2021.1\plugins\Kotlin\kotlinc\lib) to the lib directory of my Ardor installation and my contract development installation. Restarted the node, redeployed the contract, sent trigger transaction, same error.
After this I have also tried to reference each of the last 3 HelloWorldCont contracts uploaded by ARDOR-J24K-QWD8-XHEX-36P7X (looking at your error message it seems to be the contracts you have used for testing), also getting the same error.
Since those exact contracts seem to work for you I guess I have simply done something wrong with adding the kotlin-stdlib jar to the lib directory?
Do you have any other suggestions on how I can successfully run the HelloWorld Kotlin contract or maybe go into more detail with your 1. suggestion in case I have misunderstood something?

I'm not sure where this jar comes from. I got it from the libs directory when I created a new Kotlin project. Now I see that on Linux it is in /home/<user>/.local/share/JetBrains/IdeaIC2021.2/Kotlin/lib
I thought that you will know better than me the "official" way to get the jar. To make sure you have the right jar, open it with as zip archive and check that in the kotlin package there is a Metadata.class.

Also make sure that the jar is indeed added to the classpath during the execution of the Ardor process with the ContractRunner addon. If you are starting Ardor from intellij, it's not enough to place the jar in the lib directory - you will have to list that jar in Project Setting / Libraries.

My kotlin-stdlib.jar indeed got a Metadata.class in it's kotlin package. Since the path of it's location also more or less lines up with yours I woudl say that I'm indeed usign the right jar.
What i have done: shut down my Ardor node, put the jar into the lib directory of my Ardor installation, start the ardor node by executing the ardor.exe next to the lib directory, reference the latest HelloWorldCont you have uplaoded (full hash 7f1dcf2aa45f26605ef971464b75d56bd1c7812156114da61e2fa274d1dda621), start the Contract Runner from the Processes menu, send a trigger transation, still same error.
Do I need to do something additional for Ardor to pick up the new jar from the lib directory? Can I somehow check which jars Ardor actually loads?

I'm not sure what that exe does on Windows. Try to run with run.bat. The classpath should be printed in the logs:

java.class.path = ...

You said that the contract upload was successful:

The contract deployed without any problems

Weird that it is working - on my side if I don't have that jar, the upload is failing too. How do you run the contract manager in order to upload the contract?

1 Like

I can't believe it, using the exe was the issue! It works fine when I use the run.bat!
The exe apparently just loads the ardor.jar and that's it.

I thank you very much for helping me solve this problem and answering my questions so quickly! Really appreciated!

Both the IntelliJ Plugin and manually uploading the compiled .class file with the GUI and then referencing it wiht the contractManager CLI work just fine. I am developing my contract right next to the example contracts in a full Ardor installation, so maybe it works because of that?

Btw: can you tell me how I can develop a contract in it's own seperate project instead of right next to the example contracts, since I the docs only talk about developing right next to the example contracts?

I am developing my contract right next to the example contracts in a full Ardor installation, so maybe it works because of that?

I guess you converted the contracts project to Kotlin project and this added the kotlin jars to the classpath?

Btw: can you tell me how I can develop a contract in it's own seperate project instead of right next to the example contracts, since I the docs only talk about developing right next to the example contracts?

What I did is to create a new Kotrlin project, add the ardor.jar to the classpath, then build a jar artifact and uploaded it by using a

contract.HelloWorldCont.filePath=/path/to/jar

property in the Ardor installation where I run the contractManager CLI. Then I run the contractManager with parameters

-u -p com.jelurida.test -n HelloWorldCont 

I'm not sure this is the best way to do things, but I was anticipating some problems with missing classes which I hoped Kotlin will add them to the jar for me. Now it seems that uploading a single class should work too.