CityOfZion / neo-boa

Python compiler for the Neo2 Virtual Machine, see neo3-boa for NEO3
MIT License
69 stars 55 forks source link

[Feature] Use Python assert in smart contract #113

Closed hal0x2328 closed 5 years ago

hal0x2328 commented 5 years ago

It would be neat if we could replace something like:

if not CheckWitness(Owner):
    print("Unauthorized user")
    return False

with:

assert CheckWitness(Owner), "Unauthorized user"

I'm moving to just implement this in the following way in my smart contracts:

OnError = RegisterAction('error', 'message')
...
sc_assert(CheckWitness(Owner), "Unauthorized user")

def sc_assert(condition, msg):
    if not condition:
        print(msg) # for neo-python console
        OnError(msg) # for neo-cli ApplicationLog
        throw_if_null(0)

but it would be cool if it were just natively supported with the existing assert keyword in Python.

(I noticed that the neo-cli implementation ofgetapplicationlog returns the notification events that triggered before the contract faulted, so the sc_assert function I wrote takes advantage of that - I see this as being a huge help for people trying to debug contract failures after-the-fact. So if this suggestion were to be implemented as a built-in it would be nice to replicate that feature too.)

ixje commented 5 years ago

I have not looked at what getapplicationlog does, but notification events that triggered before the contract faulted can be logged/displayed with neo-python via config sc-debug-notify. Does that help?

sc-debug-notify - toggle printing smart contract Notify events on execution failure
hal0x2328 commented 5 years ago

notification events that triggered before the contract faulted can be logged/displayed with neo-python via config sc-debug-notify. Does that help?

Yes, that could eliminate the need for the extra print statement.

hal0x2328 commented 5 years ago

One other issue I am having with my bespoke assert implementation is that it appears to be impossible to have a void return type for a function. So unless I use something like

ret = sc_assert(something_is_true(), "Not true!")

I end up with an extra value on the evaluation stack at the end of execution, which is undesirable. But adding the unused variable assignment is also kind of inelegant.

hal0x2328 commented 5 years ago

I think I found the solution and it doesn't require any changes to neo-boa, just a little knowledge of Python internals:

OnError = RegisterAction('error', 'message')

def Main(operation, args):
    assert args[0] == True, 'Got false argument!'
    return True

def AssertionError(msg):
    OnError(msg)
    throw_if_null(0)