Updating old syntax

I am trying to update a contract with the old syntax to the new one. Can someone give a rundown of what steps I take to do that? Wrap as sp.module and remove the sp.Contract, ???

Or maybe there is a converter I couldn’t find?

https://github.com/chasdabigone/Custody-Free-Quipuswap-Wrapper/blob/main/quipuswap_maker_ceiling.py

Hello, from what I see your contracts are not using deep property of the old SmartPy syntax so they should be not so difficult to convert. There is no converter but an llm can now do a lot of things.

Here I converted your main contract (without the tests):

addresses.py:

import smartpy as sp

# This file contains addresses for tests which are named and ensure uniqueness across the test suite.

# The address which acts as the Governor
GOVERNOR_ADDRESS = sp.address("tz1YYnf7vGXqCmB1shNg4rHiTz5gwWTDYceB")

# The address which acts as the addLiquidity Executor
EXECUTOR_ADDRESS = sp.address("tz1YYnf7vGXqCmB1shNg4rHiTz5gwWTDYceB")

# The address that acts as the token contract.
TOKEN_ADDRESS = sp.address("KT1RBR9i6R7T56DJbaUtzDNuCt9KHLM8bVpW")

# The address of a XTZ/kUSD Quipuswap contract
QUIPUSWAP_ADDRESS = sp.address("KT1VVYfncoCWrwG6Bwd4MFuq3Xj8c4ndW5qF")

# The address of the Harbinger Normalizer (views)
HARBINGER_VWAP_ADDRESS = sp.address("KT1ENe4jbDE1QVG1euryp23GsAeWuEwJutQX")

# The address of the Harbinger Spot Price (views)
HARBINGER_SPOT_ADDRESS = sp.address("KT1UcwQtaztLSq8oufdXAtWpRTfFySCj7gFM")

# An address which is never used. This is a `null` value for addresses.
NULL_ADDRESS = sp.address("tz1bTpviNnyx2PXsNmGpCQTMQsGoYordkUoA")

# An address which can be rotated.
ROTATED_ADDRESS = sp.address("tz1UMCB2AHSTwG7YcGNr31CqYCtGN873royv")

# An address which acts as a Liquidity Fund
LIQUIDITY_FUND_ADDRESS = sp.address("tz1R6Ej25VSerE3MkSoEEeBjKHCDTFbpKuSX")

# An address which acts as a pause guardian
PAUSE_GUARDIAN_ADDRESS = sp.address("tz1YYnf7vGXqCmB1shNg4rHiTz5gwWTDYceB")

# An address which will receive the swapped tokens
RECEIVER_ADDRESS = sp.address("tz1YYnf7vGXqCmB1shNg4rHiTz5gwWTDYceB")

# An address of a Baker
BAKER_PUBLIC_KEY_HASH = "tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9"
BAKER_ADDRESS = sp.address(BAKER_PUBLIC_KEY_HASH)
BAKER_KEY_HASH = sp.key_hash(BAKER_PUBLIC_KEY_HASH)
VOTING_POWERS = {
    BAKER_KEY_HASH: 8000,
}

# An series of named addresses with no particular role.
# These are used for token transfer tests.
ALICE_ADDRESS = sp.address("tz1VQnqCCqX4K5sP3FNkVSNKTdCAMJDd3E1n")
BOB_ADDRESS = sp.address("tz2FCNBrERXtaTtNX6iimR1UJ5JSDxvdHM93")
CHARLIE_ADDRESS = sp.address("tz3S6BBeKgJGXxvLyZ1xzXzMPn11nnFtq5L9")

main.py:

import smartpy as sp

# Import modules from files
import addresses as Addresses

# from test_helpers.addresses import addresses as Addresses
# from common.constants import constants as Constants
# from common.errors import errors as Errors


################################################################
# Constants
################################################################


@sp.module
def Constants():
    # The fixed point number representing 1 in the system, 10^18
    PRECISION = sp.nat(1000000000000000000)

    # The asset pair reported by Harbinger.
    ASSET_CODE = "XTZ-USD"

    # The type of data returned in Harbinger's Normalizer callback.
    HARBINGER_DATA_TYPE: type = sp.pair[sp.string, sp.pair[sp.timestamp, sp.nat]]


################################################################
# Errors
################################################################


@sp.module
def Errors():
    # The sender of a contract invocation was required to be the Governor contract.
    NOT_GOVERNOR = 1

    # The sender of an operation was required to be the Administrator of the contract.
    NOT_EXECUTOR = 2

    # The sender of an operation was required to be the Pause Guardian.
    NOT_PAUSE_GUARDIAN = 3

    # The data provided was too old.
    STALE_DATA = 4

    # The system is paused.
    PAUSED = 5

    # Cannot receive funds.
    CANNOT_RECEIVE_FUNDS = 6

    # The swap was attempted before min delay time
    TRADE_TIME = 7

    # VWAP vs input price difference is too great
    SLIPPAGE = 8

    # Not enough tokens to perform swap
    NOT_ENOUGH_TOKENS = 9

    # The sender was not the expected contract
    BAD_SENDER = 10

    # Error calling view on Harbinger Normalizer
    VWAP_VIEW_ERROR = 11

    # Error calling view on Harbinger spot
    SPOT_VIEW_ERROR = 12

    # Error while interacting with DEX contract
    DEX_CONTRACT_ERROR = 13

    # Wrong state while interacting with function
    BAD_STATE = 14

    # Error while calling approve on token
    APPROVAL = 15

    # Difference between spot and normalizer prices is too great
    VOLATILITY = 16

    # Error while executing the transfer function in token
    TOKEN_TRANSFER = 17

    # Error while retrieving balance of token
    BALANCE_REQUEST = 18

    ## BELOW ARE ONLY USED IN TESTS ##
    # The user did not have a sufficient token balance to complete the operation.
    TOKEN_INSUFFICIENT_BALANCE = 19

    # The allowance change was unsafe. Please reset the allowance to zero before trying to operation again.
    TOKEN_UNSAFE_ALLOWANCE_CHANGE = 20

    # The operation was not performed by the token administrator.
    TOKEN_NOT_ADMINISTRATOR = 21

    # The debt ceiling would be exceeded if the operation were completed.
    DEBT_CEILING = 22

    # The user was not allowed to perform a token transfer.
    TOKEN_NO_TRANSFER_PERMISSION = 23

    # The sender of an operation was required to be Executor or Governor
    NOT_AUTHORIZED = 24


################################################################
# Contract
################################################################


@sp.module
def quipu():
    import Constants
    import Errors

    # State Machine
    IDLE = 0
    WAITING_FOR_TOKEN_BALANCE = 1

    class MakerContract(sp.Contract):
        def __init__(
            self,
            governorContractAddress,
            pauseGuardianContractAddress,
            receiverContractAddress,  # Address to send the output to
            vwapContractAddress,
            spotContractAddress,
            quipuswapContractAddress,
            tokenAddress,
            paused,
            maxDataDelaySec,  # 5 minutes
            minTradeDelaySec,  # Time to wait in seconds between allowing swaps (use 0 to allow batch transactions)
            spreadAmount,  # How far below the oracle price the exchange price must be in percent before allowing a swap
            volatilityTolerance,  # 5%
            tradeAmount,
            tokenBalance,  # this should be 0 when deployed
            lastTradeTime,
            state,
        ):
            self.data.governorContractAddress = governorContractAddress
            self.data.pauseGuardianContractAddress = pauseGuardianContractAddress
            self.data.receiverContractAddress = receiverContractAddress
            self.data.vwapContractAddress = vwapContractAddress
            self.data.spotContractAddress = spotContractAddress
            self.data.quipuswapContractAddress = quipuswapContractAddress
            self.data.tokenAddress = tokenAddress
            self.data.paused = paused
            self.data.maxDataDelaySec = maxDataDelaySec
            self.data.minTradeDelaySec = minTradeDelaySec
            self.data.spreadAmount = spreadAmount
            self.data.volatilityTolerance = volatilityTolerance
            self.data.tradeAmount = tradeAmount
            self.data.tokenBalance = tokenBalance
            self.data.lastTradeTime = lastTradeTime
            self.data.state = state

        ################################################################
        # Quipuswap API
        ################################################################

        @sp.entrypoint
        def tokenToTezPayment(self):
            # Verify the contract isn't paused.
            assert sp.amount == sp.tez(0)
            assert not self.data.paused, Errors.PAUSED

            # Make sure enough time has passed
            timeDeltaSeconds = sp.as_nat(sp.now - self.data.lastTradeTime)
            assert timeDeltaSeconds >= self.data.minTradeDelaySec, Errors.TRADE_TIME

            # Read vwap from Harbinger Normalizer
            harbingerVwap = sp.view(
                "getPrice",
                self.data.vwapContractAddress,
                Constants.ASSET_CODE,
                sp.pair[sp.timestamp, sp.nat],
            ).unwrap_some(error=Errors.VWAP_VIEW_ERROR)

            # Read spot price from Harbinger Spot
            harbingerSpot = sp.view(
                "getPrice",
                self.data.spotContractAddress,
                Constants.ASSET_CODE,
                sp.pair[
                    sp.timestamp,  # Start
                    sp.pair[
                        sp.timestamp,  # End
                        sp.pair[
                            sp.nat,  # Open
                            sp.pair[
                                sp.nat,  # High
                                sp.pair[
                                    sp.nat,  # Low
                                    sp.pair[sp.nat, sp.nat],  # Close  # Volume
                                ],
                            ],
                        ],
                    ],
                ],
            ).unwrap_some(error=Errors.SPOT_VIEW_ERROR)

            # Extract spot price
            spotPrice = sp.fst(sp.snd(sp.snd(sp.snd(sp.snd(sp.snd(harbingerSpot))))))

            # Assert that the Harbinger spot data is newer than max data delay
            dataAge = sp.as_nat(sp.now - sp.fst(sp.snd(harbingerSpot)))
            assert dataAge <= self.data.maxDataDelaySec, Errors.STALE_DATA

            # Assert that latest Harbinger Normalizer update is newer than max data delay
            vwapAge = sp.as_nat(sp.now - sp.fst(harbingerVwap))
            assert vwapAge <= self.data.maxDataDelaySec, Errors.STALE_DATA

            # Upsample price numbers using token precision constant
            harbingerVwapPrice = (
                sp.snd(harbingerVwap) * Constants.PRECISION
            ) / 1_000_000
            harbingerSpotPrice = (spotPrice * Constants.PRECISION) / 1_000_000

            # Check for volatility difference between VWAP and spot
            volatilityDifference = (
                abs(harbingerVwapPrice - harbingerSpotPrice) * 100 / harbingerSpotPrice
            )  # because tolerance is a percent
            assert (
                self.data.volatilityTolerance > volatilityDifference
            ), Errors.VOLATILITY

            # Upsample
            tokensToTrade = self.data.tradeAmount * Constants.PRECISION

            # Calculate the expected XTZ with no slippage.
            # Expected out with no slippage = (number of tokens to trade / mutez Spot price) / 1e6
            neutralOut = (tokensToTrade / spotPrice) / 1_000_000

            # Apply spread multiplier
            # Expected out multiplied by spread = (neutral out from above) * (1 + spread amount)
            percent = sp.nat(100) + self.data.spreadAmount
            requiredOut = (
                neutralOut * percent
            ) / 100  # Note that percent is specified in scale = 100

            # Approve Quipuswap contract to spend on token contract
            approveHandle = sp.contract(
                sp.pair[sp.address, sp.nat], self.data.tokenAddress, "approve"
            ).unwrap_some(error=Errors.APPROVAL)
            approveArg = (self.data.quipuswapContractAddress, tokensToTrade)
            sp.transfer(approveArg, sp.mutez(0), approveHandle)

            # Invoke a quipuswap trade
            tradeHandle = sp.contract(
                sp.pair[sp.pair[sp.nat, sp.nat], sp.address],
                self.data.quipuswapContractAddress,
                "tokenToTezPayment",
            ).unwrap_some(error=Errors.DEX_CONTRACT_ERROR)
            tradeArg = ((tokensToTrade, requiredOut), self.data.receiverContractAddress)
            sp.transfer(tradeArg, sp.mutez(0), tradeHandle)

            # Write last trade timestamp to storage
            self.data.lastTradeTime = sp.now

            # Revoke Quipuswap contract approval on token contract
            approveHandle = sp.contract(
                sp.pair[sp.address, sp.nat], self.data.tokenAddress, "approve"
            ).unwrap_some(error=Errors.APPROVAL)
            approveArg = (self.data.quipuswapContractAddress, 0)
            sp.transfer(approveArg, sp.mutez(0), approveHandle)

        ################################################################
        #  Balance functions
        ################################################################

        # Return FA 1.2 balance to receiverContractAddress
        @sp.entrypoint
        def returnBalance(self):
            assert sp.amount == sp.tez(0)
            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR

            # Verify state is correct.
            assert self.data.state == IDLE, Errors.BAD_STATE

            # Call token contract to update balance.
            param = (
                sp.self_address(),
                sp.self_entrypoint("redeemCallback"),
            )
            contractHandle = sp.contract(
                sp.pair[sp.address, sp.contract[sp.nat]],
                self.data.tokenAddress,
                "getBalance",
            ).unwrap_some()
            sp.transfer(param, sp.mutez(0), contractHandle)

            # Save state to state machine
            self.data.state = WAITING_FOR_TOKEN_BALANCE

        # Private callback for updating Balance.
        @sp.entrypoint
        def redeemCallback(self, updatedBalance):
            assert sp.amount == sp.tez(0)
            updatedBalance = sp.cast(updatedBalance, sp.nat)

            # Validate sender
            assert sp.sender == self.data.tokenAddress, Errors.BAD_SENDER

            # Verify state is correct.
            assert self.data.state == WAITING_FOR_TOKEN_BALANCE, Errors.BAD_STATE

            self.data.tokenBalance = updatedBalance

            # Send balance to Receiver
            sendParam = (
                sp.self_address(),
                self.data.receiverContractAddress,
                self.data.tokenBalance,
            )

            sendHandle = sp.contract(
                sp.tuple[sp.address, sp.address, sp.nat],
                self.data.tokenAddress,
                "transfer",
            ).unwrap_some()
            sp.transfer(sendParam, sp.mutez(0), sendHandle)

            # Reset state
            self.data.state = IDLE

        ################################################################
        # Pause Guardian
        ################################################################

        # Pause the system
        @sp.entrypoint
        def pause(self):
            assert sp.amount == sp.tez(0)
            assert (
                sp.sender == self.data.pauseGuardianContractAddress
            ), Errors.NOT_PAUSE_GUARDIAN
            self.data.paused = True

        ################################################################
        # Governance
        ################################################################

        # Update the max data delay (stale data).
        @sp.entrypoint
        def setMaxDataDelaySec(self, newMaxDataDelaySec):
            assert sp.amount == sp.tez(0)
            newMaxDataDelaySec = sp.cast(newMaxDataDelaySec, sp.nat)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.maxDataDelaySec = newMaxDataDelaySec

        # Update the delay between swaps.
        @sp.entrypoint
        def setMinTradeDelaySec(self, newMinTradeDelaySec):
            assert sp.amount == sp.tez(0)
            newMinTradeDelaySec = sp.cast(newMinTradeDelaySec, sp.nat)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.minTradeDelaySec = newMinTradeDelaySec

        # Set the trade amount (in normalized tokens).
        @sp.entrypoint
        def setTradeAmount(self, newTradeAmount):
            assert sp.amount == sp.tez(0)
            newTradeAmount = sp.cast(newTradeAmount, sp.nat)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.tradeAmount = newTradeAmount

        # Set spread amount (in percent)
        @sp.entrypoint
        def setSpreadAmount(self, newSpreadAmount):
            assert sp.amount == sp.tez(0)
            newSpreadAmount = sp.cast(newSpreadAmount, sp.nat)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.spreadAmount = newSpreadAmount

        # Set volatility tolerance (in percent)
        @sp.entrypoint
        def setVolatilityTolerance(self, newVolatilityTolerance):
            assert sp.amount == sp.tez(0)
            newVolatilityTolerance = sp.cast(newVolatilityTolerance, sp.nat)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.volatilityTolerance = newVolatilityTolerance

        # Unpause the system.
        @sp.entrypoint
        def unpause(self):
            assert sp.amount == sp.tez(0)
            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.paused = False

        # Update the Harbinger normalizer contract.
        @sp.entrypoint
        def setVwapContract(self, newVwapContractAddress):
            assert sp.amount == sp.tez(0)
            newVwapContractAddress = sp.cast(newVwapContractAddress, sp.address)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.vwapContractAddress = newVwapContractAddress

        # Update the Harbinger spot contract.
        @sp.entrypoint
        def setSpotContract(self, newSpotContractAddress):
            assert sp.amount == sp.tez(0)
            newSpotContractAddress = sp.cast(newSpotContractAddress, sp.address)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.spotContractAddress = newSpotContractAddress

        # Update the pause guardian contract.
        @sp.entrypoint
        def setPauseGuardianContract(self, newPauseGuardianContractAddress):
            assert sp.amount == sp.tez(0)
            newPauseGuardianContractAddress = sp.cast(
                newPauseGuardianContractAddress, sp.address
            )

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.pauseGuardianContractAddress = newPauseGuardianContractAddress

        # Update the Quipuswap AMM contract.
        @sp.entrypoint
        def setQuipuswapContract(self, newQuipuswapContractAddress):
            assert sp.amount == sp.tez(0)
            newQuipuswapContractAddress = sp.cast(
                newQuipuswapContractAddress, sp.address
            )

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.quipuswapContractAddress = newQuipuswapContractAddress

        # Update the governor contract.
        @sp.entrypoint
        def setGovernorContract(self, newGovernorContractAddress):
            assert sp.amount == sp.tez(0)
            newGovernorContractAddress = sp.cast(newGovernorContractAddress, sp.address)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.governorContractAddress = newGovernorContractAddress

        # Update the Receiver contract.
        @sp.entrypoint
        def setReceiverContract(self, newReceiverContractAddress):
            assert sp.amount == sp.tez(0)
            newReceiverContractAddress = sp.cast(newReceiverContractAddress, sp.address)

            assert sp.sender == self.data.governorContractAddress, Errors.NOT_GOVERNOR
            self.data.receiverContractAddress = newReceiverContractAddress


# Only run tests if this file is main.
if __name__ == "__main__":
    ################################################################
    ################################################################
    # Tests
    ################################################################
    ################################################################

    # FakeHarbingerVwap = sp.io.import_script_from_url(
    #     "file:test-helpers/fake-harbinger-normalizer.py"
    # )
    # FakeHarbingerSpot = sp.import_script_from_url(
    #     "file:test-helpers/fake-harbinger-spot.py"
    # )
    # FakeQuipuswap = sp.io.import_script_from_url("file:test-helpers/fake-quipuswap.py")

    ################################################################
    # tokenToTezPayment
    ################################################################

    @sp.add_test()
    def test():
        scenario = sp.test_scenario(
            "tokenToTezPayment - correctly calculates amount out with 10 percent spread",
            quipu,
        )

        harbingerVwap = sp.test_account("harbingerVwap")
        harbingerSpot = sp.test_account("harbingerSpot")
        quipuswap = sp.test_account("quipuswap")

        # GIVEN a moment in time.
        currentTime = sp.timestamp(1000)

        # AND fake harbinger normalizer contract
        # harbingerVwap = FakeHarbingerVwap.FakeHarbingerContract(
        #     harbingerValue=sp.nat(1_000_000),  # $1.00
        #     harbingerUpdateTime=currentTime,
        #     harbingerAsset=Constants.ASSET_CODE,
        # )
        # scenario += harbingerVwap

        # # AND fake harbinger spot contract
        # harbingerSpot = FakeHarbingerSpot.FakeHarbingerContract(
        #     harbingerValue=sp.nat(1_000_000),  # $1.00
        #     harbingerUpdateTime=currentTime,
        #     harbingerAsset=Constants.ASSET_CODE,
        #     harbingerVolume=sp.nat(1000),
        # )
        # scenario += harbingerSpot

        # # AND a fake quipuswap contract
        # quipuswap = FakeQuipuswap.FakeQuipuswapContract()
        # scenario += quipuswap

        # AND a Market Making Ceiling contract with 10% spread requirement
        maxDataDelaySec = 60
        minTradeDelaySec = 1
        lastTrade = sp.timestamp(1)
        proxy = quipu.MakerContract(
            governorContractAddress=Addresses.GOVERNOR_ADDRESS,
            pauseGuardianContractAddress=Addresses.PAUSE_GUARDIAN_ADDRESS,
            receiverContractAddress=Addresses.RECEIVER_ADDRESS,
            vwapContractAddress=harbingerVwap.address,
            spotContractAddress=harbingerSpot.address,
            quipuswapContractAddress=quipuswap.address,
            tokenAddress=Addresses.TOKEN_ADDRESS,
            paused=False,
            maxDataDelaySec=maxDataDelaySec,
            minTradeDelaySec=minTradeDelaySec,
            spreadAmount=sp.nat(10),
            volatilityTolerance=sp.nat(5),
            tradeAmount=sp.nat(10),
            tokenBalance=sp.nat(0),
            lastTradeTime=lastTrade,
            state=quipu.IDLE,
        )
        scenario += proxy

        # WHEN a trade is initiated
        param = (sp.nat(10), sp.nat(1), Addresses.RECEIVER_ADDRESS)
        amount = sp.nat(10)
        scenario += proxy.tokenToTezPayment(_now=currentTime)

        # THEN a trade was attempted requiring a 10% spread between harbinger price and quipuswap price
        # Expected Amount = (tokens sent / harbinger price) * (1 + (spread amount // 100))
        #                 = (10 / $1.00) * (1 + .1)
        #                 = 10 * 1.1
        #                 = 11
        # scenario.verify(quipuswap.data.amountOut == 11 * 1_000_000)
        # scenario.verify(quipuswap.data.destination == Addresses.RECEIVER_ADDRESS)

I inlined the Constants and Error files but you can create separate files by copying everything inside the module function into a .spy file. You’ll need to import the files with a Python import and then re-import them within the quipu @sp.module (it’s already done).

Here are the instructions I gave to the llm (claude 3.7 thinking):

All SmartPy contract code, type definition and lambda definition now belongs to modules.

Modules are defined like a python function decorated with `@sp.module`.
For example:

```python
@sp.module
def my_module():
    pass
```

## Types

Types are defined using the type hint `:type`. For example:

```python
def my_module():
    t1: type = sp.unit
    t2: type = sp.nat
    t3: type = sp.int
    t4: type = sp.string
    t5: type = sp.bool
    t6: type = sp.address
    t7: type = sp.bytes
    t8: type = sp.record(field1=t1, field2=t2)
    t9: type = sp.variant(field1=t1, field2=t2)
    t10: type = sp.map[t6, t2]
    t11: type = sp.big_map[t6, t2]
    t12: type = sp.set[t2]
    t13: type = sp.list[t2]
    t14: type = sp.lambda_(t1, t2)
    t15: type = sp.option[t2]
    t16: type = sp.operation
    t17: type = sp.signature
    t18: type = sp.key
    t19: type = sp.key_hash
    t20: type = sp.timestamp
    t21: type = sp.chain_id
    t22: type = sp.bytes
    t23: type = sp.pair[t1, t2]
    t24: type = sp.bool


```

The old syntax was using different typing system. For example, `sp.TRecord(field1=type, field2=type).layout(("field1", "field2"))` was used to define a record type and it's now replaced by `sp.record(field1=t1, field2=t2).layout(("field1", "field2"))`, `sp.TAddress` is now replaced by `sp.address`, etc.

Sometimes it is necessary to cast a variable with sp.cast to specify its type. For example `sp.cast(x, sp.nat)`. It replaces the old `sp.set_type()` and the old `sp.set_type_expr()`

### Creating values

Most types are inferred. For example: `x = ""` will be inferred of being of type `sp.string`. `y = 1` will be inferred of being of type `sp.int_or_nat` and then resolved to `sp.int` if there is no operation with a `sp.nat` otherwise it's `sp.nat`.

For example:

```python
a = 1
b = 1
c = 1
d = 1
e = sp.nat(1)
f = sp.int(1)

x = a + b # type: sp.int later resolved to sp.int if no interaction with sp.nat
y = c + e # type: sp.nat because e is a sp.nat
z = d + f # type: sp.int because f is a sp.int
```

The old syntax allowed to define maps and big maps with both the value and the type. It's not allowed now. We now use `sp.cast()` in conjunction with `{}` and `sp.big_map()` to create maps and big maps with explicit types.

For example:

```python
x = sp.cast({}, sp.map[sp.address, sp.int])
y = sp.cast({}, sp.big_map[sp.address, sp.int])
```

THe replaces the old syntax:

```python
x = sp.map(l={}, tkey = sp.TAddress, tvalue = sp.TInt)
y = sp.big_map(l={}, tkey = sp.TAddress, tvalue = sp.TInt)
```

`sp.some()` is now replaced by `sp.Some()`, `sp.none()` by `None`

## Contract data

Contract data are set field by field by setting `self.data.field = value` in the constructor.

In the old syntax we were doing things like:

```python
self.init(
    field1 = value1,
    field2 = value2,
)
```

Now we can do:

```python
self.data.field1 = value1
self.data.field2 = value2
```

The data type could be made explicit with `self.init_type(sp.TRecord(field1=type, field2=type))` or `self.init_type(field1=type, field2=type)`. Now the equivalent is: `sp.cast(self.data, sp.record(field1=type, field2=type))`. We always use the new types so `sp.nat` is used instead of `sp.TNat`, etc.

## Imports

Imports are done within a module.
For example:

```python
# an inlined SmartPy module
@sp.module
def example():
    def foo():
        pass

# another inlined SmartPy module that uses the previous module
@sp.module
def my_module():
    # Since v0.20.0: to use the `example` module you must import it
    import example
    def bar():
        example.foo()
```

In the old syntax we may do something like `Quorum = sp.io.import_script_from_url("file:contracts/dao/types/quorum.py")`

Now we can doa python import `from dao.types.quorum import quorum` and then in the module: `import quorum as Quorum` as an equivalent.

## Lambda

Private lambdas are lambdas that are private to a contract. They are defined using the `@sp.private` decorator.

For example:

```python
@sp.module
def main():
    class C(sp.Contract):
        @sp.private(with_storage="read-only", with_operations=False)
        def multiply(self, a, b):
            return a * b

        @sp.entrypoint
        def ep(self):
            assert self.multiply(a=4, b=5) == 20
```

Previously we would have done:

```python
class C(sp.Contract):
    @sp.private_lambda(with_storage="read-only", with_operations=False, wrap_call=True)
    def multiply(self, a, b):
        return a * b
```

## Cases

To match over variants, you can use the pattern-matching syntax (`match <expr>`)
over SmartPy variants. The following example creates a variant and uses `match`
branch based on its contents:

```smartpy
v = sp.variant.Circle(2)
match v:
    case Circle(radius):
        assert radius == 2
    case Rectangle(dimensions):
        ...
        # Do something with `dimensions.h` and `dimensions.w`.
```

You can use `case None` to match the `None` case, as in this example:

```smartpy
o = sp.Some(5)
match o:
    case Some(value):
        assert value == 5
    case None:
        ...
```

Note, in SmartPy, you can only pattern-match over options and variants.

In the old syntax we would have done:

```python
v = sp.variant.Circle(2)
with v.match_cases() as my_variant:
    with my_variant.match("Circle") as radius:
        assert radius == 2
    with my_variant.match("Rectangle") as dimensions:
        ...
        # Do something with `dimensions.h` and `dimensions.w`.
```

Or

```python
@sp.entrypoint
def without_native_match(self):
    v = sp.variant.Circle(2)
    with sp.match(v):
        with sp.case.Circle as radius:
            assert radius == 2
        with sp.case.Rectangle as dimensions:
            ...
            # Do something with `dimensions.h` and `dimensions.w`.
```

## Entrypoints

The `check_no_incoming_transfer` parameter is now removed.
We now do `assert sp.amount == sp.tez(0)` in the entrypoint to check that no incoming transfer is done.

We don't create local anymore.

Previously we would have done:

```python
x = sp.local("x", value)
```

Now we do:

```python
x = value
```

## Verify

`sp.verify(x, [error])` is now deprecated. We use the assert statement instead.

Previously we would have done:

```python
sp.verify(x, Errors.ERROR_MESSAGE)
```

Now we do:

```python
assert x, Errors.ERROR_MESSAGE
```

`.open_some(message="message")` is now `.unwrap_some(error="message")`

## Flow control

`with sp.if_(x)` is now `if x:`

Previously we would have done:

```python
with sp.if_(x):
    ...
```

Now we do:

```python
if x:
    ...
```

Same is true for `with sp.else_()` and `else:`, `with sp.for_()` and `for x in y:`, `with sp.while_()` and `while x:`.

For example for was used like that:

```python
with sp.for_('price', response.value.prices) as price:
    ...
```

Now we do:

```python
for price in response.value.prices:
    ...
```

`sp.result(x)` is now `return x`
`sp.failwith(message)` is now `raise message`

## Other changes

`sp.unit` is now `()`.
`sp.pair(x, y)` is no `(x, y)`.

## Best practices

The usage of `utils.nat_to_mutez(x)` often indicates an anti-pattern. It's better to use `sp.mutez` everywhere than `sp.nat` and `utils.nat_to_mutez(x)` to convert it to mutez.

The scenario system has less changes. The main change is the way to define test scenario, the fact that you need to refer to modules to access contract classes and remove the .run(...). The context arguments are now given as parameter calls with underscore. So c.ep().run(now=...) becomes c.ep(_now=...).

I may help you to convert more in the next days.

Please re-read the files and unsure that you understand and agree the conversion.

Wow Jordan, thank you so much this is a huge help. Way more than I expected. I will try to get it working this week, thank you!

1 Like

Hi Jordan, it is all working except for one small bit where the timestamps are compared.

I tried the old one:

            # Assert that the Youves spot data is newer than max data delay
            dataAge = sp.as_nat(sp.now - sp.snd(youvesSpot))
            assert dataAge <= self.data.maxDataDelaySec, Errors.STALE_DATA

And I also tried this one:

            # Assert that the Youves spot data is newer than max data delay
            spotAge = utils.seconds_of_timestamp(sp.snd(youvesSpot))
            dataAge = utils.seconds_of_timestamp(sp.now) - spotAge
            assert sp.as_nat(dataAge) <= self.data.maxDataDelaySec, Errors.STALE_DATA

Both options make the deployed contract fail with a random number for an error (not from defined error list). Can you tell what is wrong?

Hey that error is not related to SmartPy, it is a timestamp discrepancy between milliseconds and seconds. Thanks