uuosio / ascdk

MIT License
12 stars 9 forks source link

Optional<u64> does not work #64

Closed jafri closed 2 years ago

jafri commented 2 years ago

Optional u64 types are supported in normal eosio code and ABI, but not possible in ascdk

learnforpractice commented 2 years ago

Optional only supports classes that extend Packer currently. Wrapping u64 in a class can solve this kind of problem.

Here is an alternative that can support Optional<u64> but for some reasons, I don't think it's a better solution.

import { Packer, Encoder, Decoder } from "./serializer"
import { Utils } from "./utils"

export class OptionalValue<T> {
    value: T
    constructor(
        value: T
    ) {
        this.value = value;
    }
}

export class Optional<T> implements Packer {
    _value: usize
    constructor(
        value: T | null = null
    ) {
        if (value) {
            this._value = changetype<usize>(new OptionalValue<T>(value));
        } else {
            this._value = 0;
        }
    }

    get value(): T | null {
        if (this._value) {
            let value = changetype<OptionalValue<T>>(this._value);
            return value.value;    
        } else {
            return null;
        }
    }

    pack(): u8[] {
        let enc = new Encoder(this.getSize())
        if (this._value) {
            enc.packNumber<u8>(1);
            let value = changetype<OptionalValue<T>>(this._value);
            if (isInteger<T>()) {
                enc.packNumber<T>(value.value);
            } else if (isString<T>()) {
                enc.packString(changetype<string>(value.value));
            } else {
                enc.pack(changetype<Packer>(value.value));
            }
            return enc.getBytes();
        } else {
            enc.packNumber<u8>(0);
            return enc.getBytes();
        }
    }

    unpack(data: u8[]): usize {
        let dec = new Decoder(data)
        let hasValue = dec.unpackNumber<u8>();
        if (!hasValue) {
            this._value = 0;
            return 1;
        }

        if (isInteger<T>()) {
            let obj = dec.unpackNumber<T>();
            this._value = changetype<usize>(new OptionalValue<T>(obj));
        } else if (isString<T>()) {
            let obj = dec.unpackString();
            this._value = changetype<usize>(new OptionalValue<string>(obj));    
        } else {
            let obj = instantiate<T>();
            dec.unpack(obj);
            this._value = changetype<usize>(new OptionalValue<T>(obj));    
        }
        return dec.getPos();
    }

    getSize(): usize {
        if (!this._value) {
            return 1;
        }
        if (isInteger<T>()) {
            return 1 + sizeof<T>();
        } else if (isString<T>()) {
            let value = changetype<OptionalValue<string>>(this._value);
            return 1 + Utils.calcPackedStringLength(value.value);
        } else if (isReference<T>()) {
            let value = changetype<OptionalValue<T>>(this._value);
            return 1 + value.value.getSize();
        }
        return 1 + this._value.getSize();
    }
}
learnforpractice commented 2 years ago

Should be fixed by fc91b75d5e3142de33ce37208ca38e131e4a44ba by introducing two new classes OptionalNumber and OptionalString.

jafri commented 2 years ago

@learnforpractice I don't think this optional follows correctly

class MyClass {
    a1: Optional<Asset>;
    a2: OptionalNumber<u64>;
    a3: OptionalString;
    constructor(a1: Asset | null = null, a2: u64 = 0, a3: string="") {
        this.a1 = new Optional<Asset>(a1);
        this.a2 = new OptionalNumber<u64>(a2);
        this.a3 = new OptionalString(a3);
    }
}

Example code

const item = new MyClass(null)
print(item.a2.hasValue) // print true ??
print(item.a3.hasValue) // print true ??

We should also add get hasValue to Optional for consistent interface

learnforpractice commented 2 years ago

That's how it suppose to work, see the constructor in OptionalNumber:

export class OptionalNumber<T> implements Packer {
    value: T;
    hasValue: bool;
    constructor(
        value: T = 0, hasValue: bool = true
    )

the default value of hasValue is true.