Pymich types#
Pymich defines some special data structures in order to be able to respect the semantic of the underlying generated Michelson code. Indeed, Michelson lists behave vastly differently than Python lists. Although it would be possible to compile Python lists to Michelson, this could not be done without introducing a significant overhead resulting in much higher gas costs than expected. Instead, the route that Pymich takes is to redefine the basic datastructures provided by Michelson and respect its semantics. These reimplemented data-structures ensure that one gets the expected gas consumptions when compiling down to Michelson while maintaining the same results when ran under CPython and a Michelson VM.
Morever, a second argument in favor of redefining the datatypes is that Michelson data types do not necessarily implement all methods that Python types do. An example of that are lists that, in Python, define an append
, whereas its Michelson counterpart do not. Hence, by redefining the data structures, we can expose only the relevant methods, and Python IDEs will be able to typecheck Pymich code and provide proper auto complete.
These data structures are implemented à la Immutable.js, a Javascript library designed by Facebook in order to disallow mutations. The idea is that any datastructure modification returns a new data structure rather than modifies the original one. The implementation of these datastructures can be found here
Before diving into the specifics of each data-type, let us go through an example of how these immutable data structures behave:
from pymich.michelson_types import Nat, Map
michelson_map_a = Map[Nat, Nat]()
michelson_map_a[Nat(1)] = Nat(1)
michelson_map_b = Map[Nat, Map[Nat, Nat]]()
michelson_map_b[Nat(1)] = michelson_map_a
michelson_map_a_copy = michelson_map_b[Nat(1)]
michelson_map_a_copy[Nat(2)] = Nat(2)
michelson_map_a_copy[Nat(2)] # works
michelson_map_a[Nat(2)] # throws
Where as using the native Python dictionary would lead to the following behavior:
python_map_a = {1: 1}
python_map_b = {1: python_map_a}
python_map_a_bis = python_map_b[1]
python_map_a_bis[2] = 2
python_map_a_bis[2] # works
python_map_a[2] # works as well
Hence, by using the Pymich data structures, we can run our contracts in the CPython interpreter and benefit from all the Python tooling while playing well with the underlying Michelson data structures.
Base types#
Base types are the simplest types possible in Pymich. Notice that all base types are constructed from a constructor in pymich.michelson_types
and take a python type as argument. Hence in Pymich, we only use Python strings to construct pymich.michelson_types.String
and pymich.michelson_types.Address
. Python numbers are used to construct pymich.michelson_types.Nat
, pymich.michelson_types.Int
, pymich.michelson_types.Mutez
and pymich.michelson_types.Timestamp
.
from pymich.michelson_types import *
some_address = Address("KT1Ha4yFVeyzw6KRAdkzq6TxDHB97KG4pZe8")
some_string = String("my string")
some_nat = Nat(10)
some_int = Int(-10)
some_mutez = Mutez(42)
some_timestamp = Timestamp(1650053840)
This choice allows for powerful integration into the Python dev tooling ecosystem. For example, using Mypy, one can get proper typechecking right into any editor:
Nat(10) - Nat(15) # returns an Int
Int(10) + Nat(10) # returns an Int
Int(10) < Nat(15) # throws a type error
Nat(10) / Nat(3) # div operator not allowed
Nat(10) // Nat(3) # floor div is allowed
len(String("foo") # returns a String
Int(-1).is_nat() # returns None
Int(1).is_nat() # returns Nat(1)
Generic types#
Generic types are data structures that are build from basic types such as lists, maps, big maps, contracts and sets.
Lists#
my_list = List[Nat]()
my_new_list = my_list.prepend(Nat(42))
my_list_length = len(my_list)
# loops
sum = Nat(0)
for el in my_list:
sum = sum + el
Note
In the near future, list literals will be able to instanciated
with List(el1, ..., eln)
.
Maps#
my_map = Map[Nat, String]()
my_key = Nat(42)
my_map[my_key] = String("The answer.")
my_string = my_map[my_key]
my_second_map = my_map.add(Nat(10), String("Ten"))
foo = Nat(10) in my_second_map # foo is typed as a boolean
# loops
sum = Nat(0)
for key, value in my_list:
sum = key + len(value)
Big maps#
my_big_map = BigMap[Nat, String]()
my_key = Nat(42)
my_big_map[my_key] = String("The answer.")
my_string = my_big_map[my_key]
my_second_big_map = my_big_map.add(Nat(10), String("Ten"))
foo = Nat(10) in my_second_map # foo is typed as a boolean
Sets#
my_big_map = BigMap[Nat, String]()
my_key = Nat(42)
my_big_map[my_key] = String("The answer.")
my_string = my_big_map[my_key]
my_second_big_map = my_big_map.add(Nat(10), String("Ten"))
foo = Nat(10) in my_second_map # foo is typed as a boolean
Records#
class Student(Record):
name: String
age: Nat
grades: Map[String, Nat]
alice = Student(
Name("alice"),
Nat(42),
Map[String, Nat](),
)
class_code = String("FR101")
alice.grades[class_code] = Nat(15)
french_grade = alice.grades[class_code]
Note
Just like with other data structures, record attribute access returns a copy of its content
Contracts#
@dataclass
class TransferParam:
_from: Address
_to: Address
value: Nat
fa12 = "KT1EwUrkbmGxjiRvmEAa8HLGhjJeRocqVTFi"
transfer_entrypoint = Contract[TransferParam](fa12, "%transfer")
Transactions#
# Transfer to a contract
transaction(
transfer_entrypoint,
Mutez(0),
TransferParam(self.fa12, self.fa12, Nat(10)),
)
# Transfer to an implicit address
transaction(Contract[Unit](SENDER), AMOUNT, Unit)
Grammar#
python_types ::=
| python_number
| python_string
base_types ::=
| Unit
| String
| Nat
| Int
| Boolean
| Address
| Bytes
type ::=
| base_type
| Operation
| Contract
| Map[type, type]
| BigMap[type, type]
| List[type]
| Set[type]
| Record[attr1=type, ..., attrn=type]
API#
- class michelson_types.BaseContract(ops: 'Operations' = <michelson_types.Operations object at 0x7f5c17a82610>)#
- __eq__(other)#
Return self==value.
- __hash__ = None#
- class michelson_types.BigMap#
Michelson big map abstraction. It differs with a regular Python dictionary in that it: - is instanciated from literals by deepcopying the literal key/values - adding an element will add a copy of that element - getting an element will return a copy of that element - it is not iterable
- __getitem__(key: KeyType) ValueType #
Returns a COPY of the value to retrieve
- __setitem__(key: KeyType, value: ValueType) None #
Store a COPY of the value
- __str__() str #
Return str(self).
- add(key: KeyType, value: ValueType) BigMap[KeyType, ValueType] #
Returns a COPY of self with a COPY of the value added
- get(key: KeyType, default: ValueType) ValueType #
Returns a copy of the value
- class michelson_types.Bytes(value: MichelsonType)#
-
- __hash__() int #
Return hash(self).
- class michelson_types.List(*elements: ParameterType)#
- __hash__() int #
Return hash(self).
- __str__() str #
Return str(self).
- class michelson_types.Map#
Michelson big map abstraction. It differs with a regular Python dictionary in that it: - is instanciated from literals by deepcopying the literal key/values - adding an element will add a copy of that element - getting an element will return a copy of that element
- __hash__() int #
Return hash(self).
- __setitem__(key: KeyType, value: ValueType) None #
Store a COPY of the value
- __str__() str #
Return str(self).
- add(key: KeyType, value: ValueType) Map[KeyType, ValueType] #
Returns a COPY of self with a COPY of the value added
- get(key: KeyType, default: ValueType) ValueType #
Returns a copy of the value
- class michelson_types.MichelsonType#
Base type to inherit from to define further types.
- class michelson_types.Operation#
- class michelson_types.Record#
- __eq__(o: object) bool #
Return self==value.
- __hash__() int #
Return hash(self).
- __setattr__(_Record__name: str, value: MichelsonType) None #
Implement setattr(self, name, value).
- class michelson_types.Set(*args: ParameterType)#
Michelson set abstraction. It differs with a regular Python set in that when instanciating or adding an element to a set, a copy of that element is added rather than the element itself.
- __hash__() int #
Return hash(self).
- __str__() str #
Return str(self).
- add(element: ParameterType) None #
Adds a COPY of the value
- remove(element: ParameterType) None #
Removes an element from the set
- class michelson_types.String(value: str)#
Michelson string type.
- Parameters:
value – value
- __hash__() int #
Return hash(self).
- __str__() str #
Return str(self).
- class michelson_types.Timestamp(timestamp: int)#
-
- __hash__() int #
Return hash(self).
- __str__() str #
Return str(self).
- class michelson_types.Unit#
Michelson Unit type.