Skip to content

Commit 32e49d4

Browse files
author
mircial
committed
Add Func and Service candid types
1 parent 0532c7d commit 32e49d4

File tree

2 files changed

+175
-19
lines changed

2 files changed

+175
-19
lines changed

examples/candid.py

+25
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,28 @@ def test(params, rawTypes = None):
295295
test(params=params)
296296
# Sepecific return types
297297
test(params=params, rawTypes=types)
298+
299+
#Func Text
300+
types = Types.Func([Types.Text], [Types.Nat], ['query'])
301+
val = ['expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae', 'rocklabs']
302+
params = [
303+
{'type': types, 'value': val},
304+
305+
]
306+
# There is no specific return type
307+
test(params=params)
308+
# Sepecific return types
309+
test(params=params, rawTypes=types)
310+
311+
312+
#Service Text
313+
types = Types.Service({'rocklabs' : Types.Func([Types.Text], [Types.Nat], ['query'])})
314+
val = 'expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae'
315+
params = [
316+
{'type': types, 'value': val},
317+
318+
]
319+
# There is no specific return type
320+
test(params=params)
321+
# Sepecific return types
322+
test(params=params, rawTypes=types)

ic/candid.py

+150-19
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class TypeIds(Enum):
3636

3737
prefix = "DIDL"
3838

39-
# TODO
4039
class Pipe :
4140
def __init__(self, buffer = b'', length = 0):
4241
self._buffer = buffer
@@ -66,12 +65,6 @@ def readbyte(self):
6665
self._view = self._view[1:]
6766
return res
6867

69-
def write(buf):
70-
pass
71-
72-
def alloc(amount):
73-
pass
74-
7568

7669
class ConstructType: pass
7770
class TypeTable():
@@ -614,7 +607,6 @@ def display(self):
614607
return 'opt ({})'.format(self._type.display())
615608

616609
# Represents an IDL Record
617-
# todo
618610
class RecordClass(ConstructType):
619611
def __init__(self, field: dict):
620612
super().__init__()
@@ -696,7 +688,6 @@ def display(self):
696688
return "record {}".format(d)
697689

698690
# Represents Tuple, a syntactic sugar for Record.
699-
# todo
700691
class TupleClass(RecordClass):
701692
def __init__(self, *_components):
702693
x = {}
@@ -750,7 +741,6 @@ def display(self):
750741
return "record {" + '{}'.format(';'.join(d)) + '}'
751742

752743
# Represents an IDL Variant
753-
# todo
754744
class VariantClass(ConstructType):
755745
def __init__(self, field):
756746
super().__init__()
@@ -918,7 +908,151 @@ def name(self) -> str:
918908
def id(self) -> int:
919909
return TypeIds.Principal.value
920910

921-
# TODO class FunClass and ServiceClass
911+
#Represents an IDL Func reference
912+
class FuncClass(ConstructType):
913+
def __init__(self, argTypes: list, retTypes: list, annotations: list):
914+
super().__init__()
915+
self.argTypes = argTypes
916+
self.retTypes = argTypes
917+
self.annotations = annotations
918+
919+
def covariant(self, x):
920+
return type(x) == list and len(x) == 2 and x[0] and \
921+
(P.from_str(x[0]) if type(x[0]) == str else P.from_hex(x[0].hex())).isPrincipal \
922+
and type(x[1]) == str
923+
924+
def encodeValue(self, vals):
925+
principal = vals[0]
926+
methodName = vals[1]
927+
tag = int.to_bytes(1, 1, byteorder='big')
928+
if isinstance(principal, str):
929+
buf = P.from_str(principal).bytes
930+
elif isinstance(principal, bytes):
931+
buf = principal
932+
else:
933+
raise ValueError("Principal should be string or bytes.")
934+
l = leb128.u.encode(len(buf))
935+
canister = tag + l + buf
936+
937+
method = methodName.encode()
938+
methodLen = leb128.u.encode(len(method))
939+
return tag + canister + methodLen + method
940+
941+
def _buildTypeTableImpl(self, typeTable: TypeTable):
942+
for arg in self.argTypes:
943+
arg.buildTypeTable(typeTable)
944+
for ret in self.retTypes:
945+
ret.buildTypeTable(typeTable)
946+
947+
opCode = leb128.i.encode(TypeIds.Func.value)
948+
argLen = leb128.u.encode(len(self.argTypes))
949+
args = b''
950+
for arg in self.argTypes:
951+
args += arg.encodeType(typeTable)
952+
retLen = leb128.u.encode(len(self.retTypes))
953+
rets = b''
954+
for ret in self.retTypes:
955+
rets += ret.encodeType(typeTable)
956+
annLen = leb128.u.encode(len(self.annotations))
957+
anns = b''
958+
for a in self.annotations:
959+
anns += self._encodeAnnotation(a)
960+
typeTable.add(self, opCode + argLen + args + retLen + rets + annLen + anns)
961+
962+
def decodeValue(self, b: Pipe, t: Type):
963+
x = safeReadByte(b)
964+
if leb128.u.decode(x) != 1:
965+
raise ValueError('Cannot decode function reference')
966+
res = safeReadByte(b)
967+
if leb128.u.decode(res) != 1:
968+
raise ValueError("Cannot decode principal")
969+
length = leb128uDecode(b)
970+
canister = P.from_hex(safeRead(b, length).hex())
971+
mLen = leb128uDecode(b)
972+
buf = safeRead(b, mLen)
973+
method = buf.decode('utf-8')
974+
975+
return [canister, method]
976+
977+
@property
978+
def name(self) -> str:
979+
args = ', '.join(arg.name for arg in self.argTypes)
980+
rets = ', '.join(ret.name for ret in self.retTypes)
981+
anns = ' '.join(self.annotations)
982+
return '({}) → ({}) {}'.format(args, rets, anns)
983+
984+
@property
985+
def id(self) -> int:
986+
return TypeIds.Func.value
987+
988+
def display(self):
989+
args = ', '.join(arg.display() for arg in self.argTypes)
990+
rets = ', '.join(ret.display() for ret in self.retTypes)
991+
anns = ' '.join(self.annotations)
992+
return '({}) → ({}) {}'.format(args, rets, anns)
993+
994+
def _encodeAnnotation(self, ann: str):
995+
if ann == 'query':
996+
return int.to_bytes(1, 1, byteorder='big')
997+
elif ann == 'oneway':
998+
return int.to_bytes(2, 1, byteorder='big')
999+
else:
1000+
raise ValueError('Illeagal function annotation')
1001+
1002+
# Represents an IDL Service reference
1003+
class ServiceClass(ConstructType):
1004+
def __init__(self, field):
1005+
super().__init__()
1006+
self._fields = dict(sorted(field.items(), key=lambda kv: labelHash(kv[0]))) # check
1007+
1008+
def covariant(self, x):
1009+
if isinstance(x,str):
1010+
p = P.from_str(x)
1011+
elif isinstance(x, bytes):
1012+
p = P.from_hex(x.hex())
1013+
else:
1014+
raise ValueError("only support string or bytes format")
1015+
return p.isPrincipal
1016+
1017+
1018+
def encodeValue(self, val):
1019+
tag = int.to_bytes(1, 1, byteorder='big')
1020+
if isinstance(val, str):
1021+
buf = P.from_str(val).bytes
1022+
elif isinstance(val, bytes):
1023+
buf = val
1024+
else:
1025+
raise ValueError("Principal should be string or bytes.")
1026+
l = leb128.u.encode(len(buf))
1027+
return tag + l + buf
1028+
1029+
def _buildTypeTableImpl(self, typeTable: TypeTable):
1030+
for _, v in self._fields.items():
1031+
v.buildTypeTable(typeTable)
1032+
opCode = leb128.i.encode(TypeIds.Service.value)
1033+
length = leb128.u.encode(len(self._fields))
1034+
fields = b''
1035+
for k, v in self._fields.items():
1036+
fields += leb128.u.encode(len(k.encode())) + k.encode() + v.encodeType(typeTable)
1037+
typeTable.add(self, opCode + length + fields)
1038+
1039+
def decodeValue(self, b: Pipe, t: Type):
1040+
res = safeReadByte(b)
1041+
if leb128.u.decode(res) != 1:
1042+
raise ValueError("Cannot decode principal")
1043+
length = leb128uDecode(b)
1044+
return P.from_hex(safeRead(b, length).hex())
1045+
1046+
@property
1047+
def name(self) -> str:
1048+
fields = ''
1049+
for k, v in self._fields.items():
1050+
fields += k + ' : ' + v.name
1051+
return 'service {}'.format(fields)
1052+
1053+
@property
1054+
def id(self) -> int:
1055+
return TypeIds.Service.value
9221056

9231057
# through Pipe to decode bytes
9241058
def leb128uDecode(pipe: Pipe):
@@ -954,7 +1088,6 @@ def readTypeTable(pipe):
9541088
#types length
9551089
typeTable = []
9561090
typeTable_len = leb128uDecode(pipe)
957-
# contruct type todo
9581091
for _ in range(typeTable_len):
9591092
ty = leb128iDecode(pipe)
9601093
if ty == TypeIds.Opt.value or ty == TypeIds.Vec.value:
@@ -1085,10 +1218,10 @@ def buildType(rawTable, table, entry):
10851218
temp = getType(rawTable, table, t)
10861219
fields[name] = temp
10871220
return Types.Variant(fields)
1088-
# elif TypeIds.Func.value:
1089-
# return Types.Func([], [], [])
1090-
# elif TypeIds.Service.value:
1091-
# return Types.Service({})
1221+
elif ty == TypeIds.Func.value:
1222+
return Types.Func([], [], [])
1223+
elif ty == TypeIds.Service.value:
1224+
return Types.Service({})
10921225
else:
10931226
raise ValueError("Illegal op_code: {}".format(ty))
10941227

@@ -1198,10 +1331,8 @@ def Variant(fields):
11981331
def Rec():
11991332
return RecClass()
12001333

1201-
# not supported yet
1202-
'''
12031334
def Func(args, ret, annotations):
12041335
return FuncClass(args, ret, annotations)
1336+
12051337
def Service(t):
12061338
return ServiceClass(t)
1207-
'''

0 commit comments

Comments
 (0)