diff --git a/CHANGELOG.md b/CHANGELOG.md index 97524ceed..964081899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +## Added +- Smart contract: loading contract to JVM +- Smart contract: simple contact validation + ## Changed --Core: chain synchronization improved (request for chain of blocks instead of last block only) --Core: time synchronization improved (based on time-synchronized nodes) +- Core: chain synchronization improved (request for chain of blocks instead of last block only) +- Core: time synchronization improved (based on time-synchronized nodes) + ## [1.3.0] - 2018-11-16 ## Added diff --git a/build.gradle b/build.gradle index 6d81aa36a..307be67fb 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,10 @@ dependencies { //JAXB compile("javax.xml.bind:jaxb-api:2.3.0") + // Byte code + compile("org.ow2.asm:asm:7.0") + compile("org.ow2.asm:asm-commons:7.0") + // Tests testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('io.projectreactor:reactor-test') @@ -83,7 +87,7 @@ compileTestKotlin { } noArg { annotations( - "javax.persistence.MappedSuperclass", + "javax.persistence.MappedSuperclass", "javax.persistence.Entity", "io.openfuture.chain.core.annotation.NoArgConstructor" ) diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractConstruct.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractConstruct.kt similarity index 54% rename from src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractConstruct.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractConstruct.kt index ebf571431..c2cec4796 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractConstruct.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractConstruct.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.annotation +package io.openfuture.chain.smartcontract.core.annotation @Target(AnnotationTarget.FUNCTION) annotation class ContractConstruct \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractMethod.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractMethod.kt similarity index 53% rename from src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractMethod.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractMethod.kt index 725d5658f..1d3500c39 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/annotation/ContractMethod.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/annotation/ContractMethod.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.annotation +package io.openfuture.chain.smartcontract.core.annotation @Target(AnnotationTarget.FUNCTION) annotation class ContractMethod \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/exception/RequiredException.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/RequiredException.kt similarity index 63% rename from src/main/kotlin/io/openfuture/chain/smartcontract/exception/RequiredException.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/RequiredException.kt index 446f3ad12..b93a2e3e7 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/exception/RequiredException.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/RequiredException.kt @@ -1,3 +1,3 @@ -package io.openfuture.chain.smartcontract.exception +package io.openfuture.chain.smartcontract.core.exception class RequiredException(message: String?) : SmartContractException(message ?: "Required Exception") \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/exception/SmartContractException.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/SmartContractException.kt similarity index 66% rename from src/main/kotlin/io/openfuture/chain/smartcontract/exception/SmartContractException.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/SmartContractException.kt index abafe7df4..edaceb65c 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/exception/SmartContractException.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/exception/SmartContractException.kt @@ -1,3 +1,3 @@ -package io.openfuture.chain.smartcontract.exception +package io.openfuture.chain.smartcontract.core.exception abstract class SmartContractException(message: String) : RuntimeException("Smart contract exception: $message") \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Address.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Address.kt similarity index 74% rename from src/main/kotlin/io/openfuture/chain/smartcontract/model/Address.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Address.kt index d7e3eea40..55cd5ff80 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Address.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Address.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.model +package io.openfuture.chain.smartcontract.core.model open class Address(private val address: String) { diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Event.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Event.kt similarity index 73% rename from src/main/kotlin/io/openfuture/chain/smartcontract/model/Event.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Event.kt index 107b3e980..177b11a1a 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Event.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Event.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.model +package io.openfuture.chain.smartcontract.core.model abstract class Event { diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Message.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Message.kt similarity index 57% rename from src/main/kotlin/io/openfuture/chain/smartcontract/model/Message.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Message.kt index c3eab3605..7ca746ca0 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/model/Message.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/Message.kt @@ -1,3 +1,3 @@ -package io.openfuture.chain.smartcontract.model +package io.openfuture.chain.smartcontract.core.model class Message(val data: ByteArray, val value: Long, val sender: Address) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/model/SmartContract.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/SmartContract.kt similarity index 55% rename from src/main/kotlin/io/openfuture/chain/smartcontract/model/SmartContract.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/model/SmartContract.kt index f26e4c329..1d5cbee5e 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/model/SmartContract.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/model/SmartContract.kt @@ -1,7 +1,7 @@ -package io.openfuture.chain.smartcontract.model +package io.openfuture.chain.smartcontract.core.model -import io.openfuture.chain.smartcontract.exception.RequiredException -import io.openfuture.chain.smartcontract.utils.AddressUtils +import io.openfuture.chain.smartcontract.core.exception.RequiredException +import io.openfuture.chain.smartcontract.core.utils.AddressUtils abstract class SmartContract(ownerAddress: String) { @@ -10,14 +10,6 @@ abstract class SmartContract(ownerAddress: String) { protected var address: Address = Address(AddressUtils.generateContractAddress(ownerAddress, "0")) - protected fun transfer(address: String, amount: Long) { - transfer(Address(address), amount) - } - - protected fun transfer(address: Address, amount: Long) { - TODO() - } - protected fun required(value: Boolean, message: String? = null) { if (!value) { throw RequiredException(message) diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/core/service/Services.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/service/Services.kt new file mode 100644 index 000000000..e31bfcd6c --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/service/Services.kt @@ -0,0 +1,23 @@ +package io.test.io.openfuture.chain.smartcontract.core.service + +import io.openfuture.chain.smartcontract.core.model.SmartContract + +interface ContractService { + + fun get(address: String): SmartContract + +} + +interface TransactionService { + + fun transfer(senderAddress: String, recipientAddress: String, amount: Long) + +} + +interface BlockService { + + fun blockHash(height: Long): String + + fun blockTimestamp(height: Long): String + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/AddressUtils.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/AddressUtils.kt similarity index 84% rename from src/main/kotlin/io/openfuture/chain/smartcontract/utils/AddressUtils.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/AddressUtils.kt index 959815de9..6e6f8afab 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/AddressUtils.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/AddressUtils.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.utils +package io.openfuture.chain.smartcontract.core.utils import org.bouncycastle.pqc.math.linearalgebra.ByteUtils import kotlin.text.Charsets.UTF_8 diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/ByteUtils.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/ByteUtils.kt new file mode 100644 index 000000000..65df1e97f --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/ByteUtils.kt @@ -0,0 +1,11 @@ +package io.openfuture.chain.smartcontract.core.utils + +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils + +object ByteUtils { + + fun toHexString(bytes: ByteArray): String = ByteUtils.toHexString(bytes) + + fun fromHexString(hex: String): ByteArray = ByteUtils.fromHexString(hex) + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/HashUtils.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/HashUtils.kt similarity index 93% rename from src/main/kotlin/io/openfuture/chain/smartcontract/utils/HashUtils.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/HashUtils.kt index 6aa6b4fee..2cc1f8f8d 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/HashUtils.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/core/utils/HashUtils.kt @@ -1,4 +1,4 @@ -package io.openfuture.chain.smartcontract.utils +package io.openfuture.chain.smartcontract.core.utils import org.bouncycastle.crypto.digests.RIPEMD160Digest import org.bouncycastle.jcajce.provider.digest.Keccak diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/ClassSource.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/ClassSource.kt new file mode 100644 index 000000000..93958c4a1 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/ClassSource.kt @@ -0,0 +1,28 @@ +package io.openfuture.chain.smartcontract.deploy.domain + +import io.openfuture.chain.smartcontract.deploy.utils.asPackagePath +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes.ASM6 +import java.nio.file.Path + + +class ClassSource( + val bytes: ByteArray +) { + + companion object { + fun isClass(path: Path): Boolean = path.fileName.toString().endsWith(".class", true) + } + + val reader = ClassReader(bytes) + val writer = ClassWriter(reader, ASM6) + + /** + * Fully qualified class name, e.g. io.openfuture.chain.HelloWorld + */ + val qualifiedName + get() = reader.className.asPackagePath + + +} diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/LoadedClass.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/LoadedClass.kt new file mode 100644 index 000000000..004391600 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/domain/LoadedClass.kt @@ -0,0 +1,6 @@ +package io.openfuture.chain.smartcontract.deploy.domain + +class LoadedClass( + val clazz: Class<*>, + val byteCode: ByteArray +) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/exception/ClassLoadingException.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/exception/ClassLoadingException.kt new file mode 100644 index 000000000..1960a05fa --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/exception/ClassLoadingException.kt @@ -0,0 +1,6 @@ +package io.openfuture.chain.smartcontract.deploy.exception + +class ClassLoadingException( + message: String? = "Class loading failed", + cause: Throwable? = null +) : RuntimeException(message, cause) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/load/SourceClassLoader.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/load/SourceClassLoader.kt new file mode 100644 index 000000000..a12646db1 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/load/SourceClassLoader.kt @@ -0,0 +1,90 @@ +package io.openfuture.chain.smartcontract.deploy.load + +import io.openfuture.chain.smartcontract.deploy.domain.ClassSource.Companion.isClass +import io.openfuture.chain.smartcontract.deploy.domain.LoadedClass +import io.openfuture.chain.smartcontract.deploy.exception.ClassLoadingException +import io.openfuture.chain.smartcontract.deploy.utils.asResourcePath +import io.openfuture.chain.smartcontract.deploy.utils.toURL +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.FileNotFoundException +import java.io.IOException +import java.net.URL +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + + +class SourceClassLoader( + paths: List = emptyList() +) : URLClassLoader(resolvePaths(paths), getSystemClassLoader()) { + + companion object { + private val log: Logger = LoggerFactory.getLogger(SourceClassLoader::class.java) + } + + private val classes: MutableMap = mutableMapOf() + + + override fun loadClass(name: String): Class<*> = loadClass(name, false) + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + try { + //todo validate + return super.loadClass(name, resolve) + } catch (ex: Throwable) { + throw ClassLoadingException(ex.message, ex) + } + } + + override fun findClass(name: String): Class<*> { + val loadedClass = classes[name] + if (null != loadedClass) { + log.trace("Class $name already loaded") + return loadedClass.clazz + } + + val bytes = readClassBytes(name) + val clazz = loadBytes(name, bytes).clazz + classes[name] = LoadedClass(clazz, bytes) + resolveClass(clazz) + return clazz + } + + fun loadBytes(className: String, bytes: ByteArray): LoadedClass { + try { + return LoadedClass(defineClass(className, bytes, 0, bytes.size), bytes) + } catch (ex: Throwable) { + throw ClassLoadingException(ex.message, ex) + } + } + + private fun readClassBytes(fullyQualifiedClassName: String): ByteArray { + try { + return (getResourceAsStream("${fullyQualifiedClassName.asResourcePath}.class") + ?: throw ClassLoadingException("Class not found $fullyQualifiedClassName")).readBytes() + } catch (e: IOException) { + throw ClassLoadingException("Error reading bytecode", e) + } + } + +} + + +private val homeDirectory: Path + get() = Paths.get(System.getProperty("user.home")) + + +private fun resolvePaths(paths: List): Array = paths.map { expandPath(it) }.flatMap { path -> + when { + !Files.exists(path) -> throw FileNotFoundException("File not found; $path") + isClass(path) || Files.isDirectory(path) -> listOf(path.toURL()) + else -> throw IllegalArgumentException("Expected a class file, but found $path") + } +}.toTypedArray() + +private fun expandPath(path: Path): Path = if (path.toString().startsWith("~/")) { + homeDirectory.resolve(path.toString().removePrefix("~/")) +} else path + diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/DefaultContractService.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/DefaultContractService.kt new file mode 100644 index 000000000..643e7fab1 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/DefaultContractService.kt @@ -0,0 +1,24 @@ +package io.openfuture.chain.smartcontract.deploy.service + +import io.openfuture.chain.smartcontract.deploy.domain.ClassSource +import io.openfuture.chain.smartcontract.deploy.load.SourceClassLoader +import org.springframework.stereotype.Service + +@Service +class DefaultContractService : ContractService { + + private val classLoader = SourceClassLoader() + + + override fun deploy(bytes: ByteArray) { + // simple deploy method + val source = ClassSource(bytes) + classLoader.loadBytes(source.qualifiedName, bytes) + } + + override fun run(className: String, method: String, vararg params: Any) { + //run a method of a contract in separate thread + TODO("not implemented") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/Services.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/Services.kt new file mode 100644 index 000000000..17c4edf4c --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/service/Services.kt @@ -0,0 +1,9 @@ +package io.openfuture.chain.smartcontract.deploy.service + +interface ContractService { + + fun deploy(bytes: ByteArray) + + fun run(className: String, method: String, vararg params: Any) + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/utils/Extentions.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/utils/Extentions.kt new file mode 100644 index 000000000..8ca55f886 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/utils/Extentions.kt @@ -0,0 +1,9 @@ +package io.openfuture.chain.smartcontract.deploy.utils + +import java.net.URL +import java.nio.file.Path + +fun Path.toURL(): URL = this.toUri().toURL() + +val String.asPackagePath: String get() = this.replace('/', '.') +val String.asResourcePath: String get() = this.replace('.', '/') \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/BlackList.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/BlackList.kt new file mode 100644 index 000000000..024d3f7bb --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/BlackList.kt @@ -0,0 +1,31 @@ +package io.test.io.openfuture.chain.smartcontract.deploy.validation + +object BlackList { + + private val entries = setOf( + "java/awt/.*", + "java/beans/.*", + "java/lang/invoke/.*", + "java/lang/.*Thread.*", + "java/lang/Shutdown.*", + "java/lang/ref/.*", + "java/lang/reflect/InvocationHandler.*", + "java/lang/reflect/Proxy.*", + "java/lang/reflect/Weak.*", + "java/io/.*File.*", + "java/net/.*Content.*", + "java/net/Host.*", + "java/net/Inet.*", + "java/nio/file/Path.*", + "java/nio/file/attribute/.*", + "java/util/SplittableRandom.*", + "java/util/Random.*", + "java/util/WeakHashMap.*", + "java/util/concurrent/.*", + "java/util/concurrent/locks/.*", + "javax/activation/.*" + ).map { "[\\(\\)L]?$it".toRegex() } + + fun matches(className: String): Boolean = entries.any { it.matches(className) } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/SourceValidator.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/SourceValidator.kt new file mode 100644 index 000000000..c3ab3c6b8 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/SourceValidator.kt @@ -0,0 +1,59 @@ +package io.test.io.openfuture.chain.smartcontract.deploy.validation + +import io.openfuture.chain.smartcontract.deploy.utils.asPackagePath +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.FieldVisitor +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.ASM6 +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +class SourceValidator(val result: ValidationResult) : ClassVisitor(ASM6) { + + companion object { + private const val superContractName = "io/openfuture/chain/smartcontract/core/model/SmartContract" + private val log: Logger = LoggerFactory.getLogger(SourceValidator::class.java) + } + + + override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array?) { + if (null == superName || superName != superContractName) { + result.addError("Class is not a smart contract. Should inherit an ${superContractName.asPackagePath} class") + } + + log.debug("CLASS: name-$name, interfaces-$interfaces, signature-$signature, version-$version") + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitField(access: Int, name: String?, descriptor: String?, signature: String?, value: Any?): FieldVisitor? { + if (null != descriptor && BlackList.matches(descriptor)) { + result.addError("$descriptor is forbidden to use in a contract") + } + + log.debug("FIELD: name-$name, descriptor-$descriptor, signature-$signature") + return super.visitField(access, name, descriptor, signature, value) + } + + override fun visitMethod(access: Int, name: String?, descriptor: String?, signature: String?, exceptions: Array?): MethodVisitor? { + if (null != descriptor && BlackList.matches(descriptor)) { + result.addError("$descriptor is forbidden to use in a contract") + } + + log.debug("METHOD: name-$name, descriptor-$descriptor, signature-$signature, exceptions-$exceptions") + return SourceMethodVisitor() + } + + + private inner class SourceMethodVisitor : MethodVisitor(ASM6) { + + override fun visitLocalVariable(name: String?, descriptor: String?, signature: String?, start: Label?, end: Label?, index: Int) { + if (null != descriptor && BlackList.matches(descriptor)) { + result.addError("$descriptor is forbidden to use in a contract's method") + } + + super.visitLocalVariable(name, descriptor, signature, start, end, index) + } + + } +} diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/ValidationResult.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/ValidationResult.kt new file mode 100644 index 000000000..8d798cd36 --- /dev/null +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/deploy/validation/ValidationResult.kt @@ -0,0 +1,15 @@ +package io.test.io.openfuture.chain.smartcontract.deploy.validation + +class ValidationResult( + val errors: MutableSet = mutableSetOf() +) { + + fun addError(error: String) { + errors.add(error) + } + + fun hasErrors(): Boolean = errors.isNotEmpty() + + override fun toString(): String = errors.joinToString(separator = "\n", prefix = "[", postfix = "]") + +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/service/Services.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/service/Services.kt deleted file mode 100644 index d968c4ca7..000000000 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/service/Services.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.openfuture.chain.smartcontract.service - -import io.openfuture.chain.smartcontract.model.SmartContract - -interface ContractService { - - fun deploy(contract: SmartContract) - - fun get(address: String): SmartContract - -} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/AssetsSellerContract.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/AssetsSellerContract.kt similarity index 69% rename from src/main/kotlin/io/openfuture/chain/smartcontract/AssetsSellerContract.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/templates/AssetsSellerContract.kt index 8ba193e0a..8eed2bae5 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/AssetsSellerContract.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/AssetsSellerContract.kt @@ -1,10 +1,10 @@ -package io.openfuture.chain.smartcontract +package io.openfuture.chain.smartcontract.templates -import io.openfuture.chain.smartcontract.annotation.ContractMethod -import io.openfuture.chain.smartcontract.model.Address -import io.openfuture.chain.smartcontract.model.Event -import io.openfuture.chain.smartcontract.model.Message -import io.openfuture.chain.smartcontract.model.SmartContract +import io.openfuture.chain.smartcontract.core.annotation.ContractMethod +import io.openfuture.chain.smartcontract.core.model.Address +import io.openfuture.chain.smartcontract.core.model.Event +import io.openfuture.chain.smartcontract.core.model.Message +import io.openfuture.chain.smartcontract.core.model.SmartContract class AssetsSellerContract : SmartContract("") { diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/CalculatorContract.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/CalculatorContract.kt similarity index 75% rename from src/main/kotlin/io/openfuture/chain/smartcontract/CalculatorContract.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/templates/CalculatorContract.kt index c0b6da8cf..caa3bb74f 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/CalculatorContract.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/CalculatorContract.kt @@ -1,7 +1,7 @@ -package io.openfuture.chain.smartcontract +package io.openfuture.chain.smartcontract.templates -import io.openfuture.chain.smartcontract.annotation.ContractMethod -import io.openfuture.chain.smartcontract.model.SmartContract +import io.openfuture.chain.smartcontract.core.annotation.ContractMethod +import io.openfuture.chain.smartcontract.core.model.SmartContract class CalculatorContract() : SmartContract("") { diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/MyTokenSmartContract.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/MyTokenSmartContract.kt similarity index 52% rename from src/main/kotlin/io/openfuture/chain/smartcontract/MyTokenSmartContract.kt rename to src/main/kotlin/io/openfuture/chain/smartcontract/templates/MyTokenSmartContract.kt index 0540ecfd9..5895c3817 100644 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/MyTokenSmartContract.kt +++ b/src/main/kotlin/io/openfuture/chain/smartcontract/templates/MyTokenSmartContract.kt @@ -1,9 +1,9 @@ -package io.openfuture.chain.smartcontract +package io.openfuture.chain.smartcontract.templates -import io.openfuture.chain.smartcontract.annotation.ContractConstruct -import io.openfuture.chain.smartcontract.annotation.ContractMethod -import io.openfuture.chain.smartcontract.model.Address -import io.openfuture.chain.smartcontract.model.SmartContract +import io.openfuture.chain.smartcontract.core.annotation.ContractConstruct +import io.openfuture.chain.smartcontract.core.annotation.ContractMethod +import io.openfuture.chain.smartcontract.core.model.Address +import io.openfuture.chain.smartcontract.core.model.SmartContract class MyTokenSmartContract : SmartContract("") { diff --git a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/BlocksUtils.kt b/src/main/kotlin/io/openfuture/chain/smartcontract/utils/BlocksUtils.kt deleted file mode 100644 index e52306362..000000000 --- a/src/main/kotlin/io/openfuture/chain/smartcontract/utils/BlocksUtils.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.openfuture.chain.smartcontract.utils - -object BlocksUtils { - - fun blockHash(blockNumber: Long): String { - TODO() - } - - fun blockTimestamp(blockNumber: Long): Long { - TODO() - } - -} \ No newline at end of file diff --git a/src/test/kotlin/io/openfuture/chain/smartcontract/deploy/ClassLoadingTests.kt b/src/test/kotlin/io/openfuture/chain/smartcontract/deploy/ClassLoadingTests.kt new file mode 100644 index 000000000..fef9b1cd0 --- /dev/null +++ b/src/test/kotlin/io/openfuture/chain/smartcontract/deploy/ClassLoadingTests.kt @@ -0,0 +1,76 @@ +package io.openfuture.chain.smartcontract.deploy + +import io.openfuture.chain.smartcontract.deploy.load.SourceClassLoader +import org.assertj.core.api.Assertions.assertThat +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils +import org.junit.Test +import java.nio.file.Path +import java.nio.file.Paths + +class ClassLoadingTests { + + @Test + fun loadClassFromFile() { + val path = "/classes/CalculatorContract.class" + + val loader = SourceClassLoader(listOf(getResource(path))) + + val clazz = loader.loadClass("io.openfuture.chain.smartcontract.templates.CalculatorContract") + val contract = clazz.newInstance() + + assertThat(contract).isNotNull + assertThat(clazz.getDeclaredMethod("result").invoke(contract)).isEqualTo(0L) + clazz.getDeclaredMethod("add", Long::class.java).invoke(contract, 10L) + assertThat(clazz.getDeclaredMethod("result").invoke(contract)).isEqualTo(10L) + } + + + @Test + fun loadBytesWhenJavaClass() { + val javaBytes = """ + cafebabe0000003400140a000400100800110700120700130100063c696e69743e010003282956010004436f646501000f4c696 + e654e756d6265725461626c650100124c6f63616c5661726961626c655461626c65010004746869730100134c696f2f74657374 + 2f48656c6c6f4a6176613b01000568656c6c6f01001428294c6a6176612f6c616e672f537472696e673b01000a536f757263654 + 6696c6501000e48656c6c6f4a6176612e6a6176610c0005000601000d48656c6c6f2c20776f726c6421010011696f2f74657374 + 2f48656c6c6f4a6176610100106a6176612f6c616e672f4f626a656374002100030004000000000002000100050006000100070 + 000002f00010001000000052ab70001b10000000200080000000600010000000300090000000c000100000005000a000b000000 + 01000c000d000100070000002d00010001000000031202b00000000200080000000600010000000600090000000c00010000000 + 3000a000b00000001000e00000002000f + """ + + val loader = SourceClassLoader() + val clazz = loader.loadBytes("io.test.HelloJava", ByteUtils.fromHexString(javaBytes)).clazz + val result = clazz.getDeclaredMethod("hello").invoke(clazz.newInstance()) + + assertThat(result).isEqualTo("Hello, world!") + } + + @Test + fun loadBytesWhenKotlinClass() { + val kotlinBytes = """ + cafebabe000000340024010013696f2f746573742f48656c6c6f4b6f746c696e0700010100106a6176612f6c616e672f4f626a65 + 637407000301000568656c6c6f01001428294c6a6176612f6c616e672f537472696e673b0100234c6f72672f6a6574627261696e + 732f616e6e6f746174696f6e732f4e6f744e756c6c3b01000d48656c6c6f2c20776f726c6421080008010004746869730100154c + 696f2f746573742f48656c6c6f4b6f746c696e3b0100063c696e69743e0100032829560c000c000d0a0004000e0100114c6b6f74 + 6c696e2f4d657461646174613b0100026d760300000001030000000d0100026276030000000003000000030100016b0100026431 + 010032c080120a0218020a0210c0800a0208020a02100e0ac08018c080320230014205c2a2060210024a0610031a023004c2a806 + 05010002643201000001000f6f70656e2d636861696e5f6d61696e01000e48656c6c6f4b6f746c696e2e6b74010004436f646501 + 00124c6f63616c5661726961626c655461626c6501000f4c696e654e756d6265725461626c6501001b52756e74696d65496e7669 + 7369626c65416e6e6f746174696f6e7301000a536f7572636546696c6501001952756e74696d6556697369626c65416e6e6f7461 + 74696f6e730031000200040000000000020011000500060002001e0000002d00010001000000031209b000000002001f0000000c + 000100000003000a000b00000020000000060001000000050021000000060001000700000001000c000d0001001e0000002f0001 + 0001000000052ab7000fb100000002001f0000000c000100000005000a000b000000200000000600010000000300020022000000 + 02001d00230000004600010010000500115b000349001249001249001300145b0003490012490015490016001749001200185b00 + 01730019001a5b000673000b73001b73000d73000573001b73001c + """ + + val loader = SourceClassLoader() + val clazz = loader.loadBytes("io.test.HelloKotlin", ByteUtils.fromHexString(kotlinBytes)).clazz + val result = clazz.getDeclaredMethod("hello").invoke(clazz.newInstance()) + + assertThat(result).isEqualTo("Hello, world!") + } + + private fun getResource(path: String): Path = Paths.get(javaClass.getResource(path).toURI()) + +} \ No newline at end of file diff --git a/src/test/resources/classes/CalculatorContract.class b/src/test/resources/classes/CalculatorContract.class new file mode 100644 index 000000000..651a4e099 Binary files /dev/null and b/src/test/resources/classes/CalculatorContract.class differ