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.