Open aslesarenko opened 3 months ago
What is the motivation for those constructors? Any example ? Why BigInt is in the list?
You already can do bigInt("")
For compile-time creation of objects of (AvlTree, Header, Box, BigInt, GroupElement) you can use deserializeRaw from hardcoded bytes. Creating header from fields in runtime ? Any useful example ?
What is the motivation for those constructors? Any example ? Why BigInt is in the list?
The motivation is to have single ErgoTree encoding for different use cases.
This is actually pretty standard way of encoding new ObjectType(...)
operations on languages.
We could have it from network launch along with MethodCall, but we didn't have time for this.
CreateAvlTree is one example. Another is val h = Header(...)
, as well as basically any other type.
BigInt maybe not the best example, as there is already byteArrayToBigInt
method, but why not.
If any type can have a constructor, why not BigInt?
You already can do bigInt("")
This only allow constant argument, where as NewObject will allow any expressions in arguments.
For compile-time creation of objects of (AvlTree, Header, Box, BigInt, GroupElement) you can use deserializeRaw from hardcoded bytes.
yes, key words are hardcoded bytes
, this may be both advantage and disadvantage, depending on use case.
Creating header from fields in runtime ? Any useful example ?
I guess these constructors can be done on the compiler level anyway, so under the hood the compiler will replace a constructor with expression which is building a bytestring and then do deserializeRaw
or deserializeTyped
for it
Thus I am moving it to 6.x
expression which is building a bytestring
So, of some arguments come from GetVar, then they should be serialized and then concatenated to the rest of the bytes? And this will happen during contract execution? Isn't it too much efforts to avoid having normal operation?
It depends on usage, I haven't seen any use-case still. Especially considering that in practice you can go the other way, so instead of constructing e.g. a box with certain fields, expect for a box being provided and then calculate predicates over its fields (which is what most of apps are doing btw)
I haven't seen any use-case still.
I described the usecase where parameters can come from different sources. And the most natural way is to have constructor call. Any work around looks ugly at use, and maybe more difficult in implementation. The only thing we save here is time spent on v6.0. We can move this to v7.0, but implementing it via pure compilation+serialization is even more complex.
But where do you need for constructing e.g. box from multiple sources instead of checking a box of interest ?
Even smaller chance to find an use case for a header.
I have wanted to make a reusable function with an option as parameter, which works fine when feeding it the result of an option producing function, but sometimes it is an actual value and creating a Some out of that would be nice.
Even smaller chance to find an use case for a header.
cmon, what kind of argument it is? Users found use cases for almost all language features I've put into the language. Even Box in a register, which was added to DataSerializer just because it was easy to do and because I wanted to have generic serializer which works for any type with descriptor, and Box was such type.
The same is here, NewObject is generic feature which will work for any type. I'm pretty sure people will find use cases.
As no use-cases provided, I can move it to 7.0 or leave for 6.x. Frontend only implementation looks promising, as it is better not to touch consensus-critical level when possible
As no use-cases provided
No use case for Header, but what about AvlTree, which we started from. Why lack of usecase for Header is so critical, what about other types?
For AvlTree byte machinery is not hard:
val digest = SELF.R4[Coll[Byte]].get
val flag = Coll(0.toByte) // read-only
val keyLength = Coll(32.toByte)
val valueLength = Coll(0.toByte) // arb length
val treeBytes = digest ++ flag ++ keyLength ++ valueLength
deserialize[AvlTree](treeBytes).digest == digest
so here you have fields coming from different sources etc
the example is artificial though, as in practice (keyLength, valueLength) are coming from the same source as digest
(keyLength, valueLength) are coming from the same source as digest
To your point, if they come say from a register as AvlTree, then
@contract MyContract(flags: Byte) = {
val tree = SELF.R4[AvlTree].get
AvlTree(tree.digest, flags, tree.keyLength, tree.valueLength).digest == digest
}
The this examples not only much easier to understand/read, but also always works, while your code is only good while values fit into single bytes, user need to take care about format, and also know how to encode.
But there is simpler solution:
@contract MyContract(flags: Byte) = {
val tree = SELF.R4[AvlTree].get
tree.updateOperations(flags).digest == digest
}
But there is simpler solution:
Yes, that is because AvlTree have only fields.
In case you compare not digest
but some computable property or the result of a method call, then you have to construct an object.
Yes, that is because AvlTree have only fields. In case you compare not
digest
but some computable property or the result of a method call, then you have to construct an object.
updateOperations(flags) is constructing an object, and then you can call any tree method on it
Problem
Currently there is no way in ErgoScript to create objects of pre-defined types such as AvlTree, Header, Box, etc. It is possible to introduce new objects only by deserializing from context var or from register. But this is very limited approach and more like a workaround.
Solution
NewObject[T <: SType](args: Seq[SValue], tpe: T) extends Value[T]
operation to ErgoTree which constructs a new object from args by calling the default constructor.val tree = AvlTree(digest, flags, keyLength, valueLength)
i.e. the same way constructors are called in Scala3 by using type name.