1

I have a set of smart contracts coded in SmartPy. They are heavily interdependent and some of them also interact with cTez contracts.

I need to write SmartPy tests to check if everything is working fine. But, I don't see any way to deploy LIGO contracts in the SmartPy scenario. One idea I can think of is if there is any way to deploy raw Michelson contracts in SmartPy test scenario. That way I can compile the cTez LIGO contracts into Michelson and then deploy those in my scenario.

Or any other way to write tests, where I can deploy contracts that are coded in different languages?

1 Answer 1

0

You can use raw Michelson as you were expecting. Here is a generic contract for that:

import smartpy as sp

class ProxyInterface(sp.Contract):
    def __init__(self, initial_storage, storage_type, code, entrypoints):
        self.init(initial_storage)
        self.init_type(storage_type)
        self.parameter_type = sp.TVariant(**entrypoints).right_comb()
        self.storage_type = storage_type
        self.code = code

        def create_ep(ep_name, params_type):
            def ep(self, params):
                sp.set_type(params, params_type)
                self.main_computation(
                    sp.set_type_expr(
                        sp.variant(ep_name, params),
                        self.parameter_type))
            return ep
        
        for ep_name, params_type in entrypoints.items():
            ep = create_ep(ep_name, params_type)
            ep.__name__ = ep_name
            setattr(self, ep_name, sp.entrypoint(ep))

    @sp.private_lambda(with_storage="read-write", with_operations=True, wrap_call=True)
    def main_computation(self, params):
        sp.set_type(params, self.parameter_type)
        f = sp.lambda_michelson(
            self.code,
            source=sp.TPair(self.parameter_type, self.storage_type),
            target=sp.TPair(sp.TList(sp.TOperation), self.storage_type)
        )
        r = sp.compute(f(sp.pair(params, self.data)))
        o, d = sp.match_pair(r)
        self.data = d
        sp.add_operations(o)

You can instantiate it in a scenario. It will init the storage, create the entrypoints and a michelson lambda based on the parameters you gave.

You may want to use the SmartPy explorer to get the SmartPy types of the contract or at least something similar to what you'll have to use.

Here is an example using it with the store value contract.

Original contract and tests:

import smartpy as sp

class StoreValue(sp.Contract):
    def __init__(self, value):
        self.init(storedValue = value)

    @sp.entrypoint(new_syntax = True)
    def replace(self, params):
        self.data.storedValue = params.value

    @sp.entrypoint(new_syntax = True)
    def double(self):
        self.data.storedValue *= 2

    @sp.entrypoint(new_syntax = True)
    def divide(self, params):
        assert params.divisor > 5
        self.data.storedValue /= params.divisor

if "templates" not in __name__:
    @sp.add_test(name = "StoreValue")
    def test():
        c1 = StoreValue(12)
        scenario = sp.test_scenario()
        scenario.h1("Store Value")
        scenario += c1
        c1.replace(value = 15)
        scenario.p("Some computation").show(c1.data.storedValue * 12)
        c1.replace(value = 25)
        c1.double()
        c1.divide(divisor = 2).run(valid = False, exception="WrongCondition: params.divisor > 5")
        scenario.verify(c1.data.storedValue == 50)
        c1.divide(divisor = 6)
        scenario.verify(c1.data.storedValue == 8)

    sp.add_compilation_target("storeValue", StoreValue(12))

Equivalent with the Michelson proxy:

if "templates" not in __name__:
    @sp.add_test(name = "ProxyInterface")
    def test():
        sc = sp.test_scenario()

        c1 = ProxyInterface(
            initial_storage=42,
            storage_type=sp.TInt,
            code="""UNPAIR; IF_LEFT { PUSH int 5; DUP 2; COMPARE; GT; IF {} { PUSH int 19; FAILWITH; }; SWAP; EDIV; IF_NONE { PUSH int 20; FAILWITH; } { CAR; }; } { IF_LEFT { DROP; PUSH int 2; SWAP; MUL; } { SWAP; DROP; }; }; NIL operation; PAIR; """,
            entrypoints= {
                "divide": sp.TInt,
                "double": sp.TUnit,
                "replace": sp.TInt,
            },
        )
        sc.h1("Store Value")
        sc += c1
        c1.replace(15)
        sc.p("Some computation").show(c1.data * 12)
        c1.replace(25)
        c1.double()
        c1.divide(2).run(valid = False, exception=19)
        sc.verify(c1.data == 50)
        c1.divide(6)
        sc.verify(c1.data == 8)

Notice that I got a strange error so I had to replace the string error message by an int. If you experience the same issue, you can do the same replacement or open another question with your problem.

2
  • Thanks for this! This is mostly what I need. But in my case, I cannot modify the generic contract because it is internally called by more library contracts (using sp.transfer inside their entry points). So, it should be callable exactly like a normal contract. Like c1.replace(value = 25) instead of c1.default(sp.variant("replace", 25)). Mar 31, 2023 at 17:10
  • I edited my answer with a better solution that respects the interface.
    – Jordan_tez
    Apr 1, 2023 at 6:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.