sbuilder-eth 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,476 @@
1
+ # coding: utf-8
2
+ module Sbuilder
3
+
4
+ module Eth
5
+
6
+ # Module to include to Ethreum class containing methods to
7
+ # translate Solidity expression to Al language expressions.
8
+ module EthereumExpression
9
+
10
+ # ------------------------------------------------------------------
11
+ # @!group Distpath reference to lvalue
12
+
13
+ # @!endgroup
14
+
15
+
16
+ # Dispatch lval reference based lval AST node sexp_type.
17
+ #
18
+ # @return [TlaSexp] :Identifier -> name (='value' attribute),
19
+ # :IndexAccess => [ raise TODO ]
20
+ def lvalAccessDistpatcher
21
+ @lvalAccessDistpatched || {
22
+ :Identifier => ->(identifierAst) { identifierAst['value'] },
23
+ :IndexAccess => ->(indexAccessAst) do
24
+ #intetifierAst = indexAccessAst.identifier
25
+ indexerAst = indexAccessAst.indexer
26
+ indexer = Ethereum.dispatcher( expressionDispatcher, indexerAst.sexp_type, indexerAst)
27
+ alApi.tlaRecordIndex(indexAccessAst.identifier['value'], indexer )
28
+ end,
29
+ }
30
+ end
31
+
32
+ # ------------------------------------------------------------------
33
+ # @!group Expression translation
34
+
35
+ # Translate 'expressionAst' using {#expressionDispatcher}.
36
+ #
37
+ # @return [Al::Expression] trasnalation of 'expressionAst'
38
+ #
39
+ def dispatchExpression( expressionAst ) #
40
+ @logger.debug( "#{__method__}: expressionAst=#{expressionAst}" ) if @logger.debug?
41
+ Ethereum.dispatcher( expressionDispatcher, expressionAst.sexp_type, expressionAst)
42
+ end
43
+
44
+ # Dispatch tranlation for Solidity AST expression based
45
+ # sexp_type of expression sexp_type.
46
+ #
47
+ #
48
+ # @return [Hash] :sexp_type => lambda(AstSexp) to tranlate
49
+ # Solidity AST node to AL language expression
50
+ def expressionDispatcher
51
+ @expressionDispatcher ||= {
52
+ :ExpressionContext => ->(contextAst) do
53
+ # dispatch again using sexp_type of the wrapped expressionAst
54
+ Ethereum.dispatcher( expressionDispatcher, contextAst.expressionAst.sexp_type, contextAst)
55
+ end,
56
+ :BinaryOperation => method(:binaryExpr),
57
+ :Literal => method(:constantExpr),
58
+ :FunctionCall => method(:functionResponseExpr),
59
+ :Identifier => method(:identifierExpr),
60
+ :MemberAccess => method( :memberAccessExpr),
61
+ :IndexAccess => method( :indexAccessExpr ),
62
+ :TupleExpression => method( :tupleExpression ),
63
+ }
64
+ end
65
+
66
+ # Translate expression for 'functionCall' to read operator call
67
+ # reading function response, and accessing return value.
68
+ #
69
+ # Assume: pre-processor has added function call just prior to
70
+ # the statement accessing function response value)
71
+ #
72
+ # TODO: should add support to manage tuple return values from
73
+ # function call
74
+ #
75
+ # @param ast [ExpressionContext,:FunctionCall] function call (or
76
+ # function wrapped into context) to create expression to
77
+ # access response from function call
78
+
79
+ def functionResponseExpr( ast )
80
+ @logger.info "#{__method__}: ast=#{ast}" if @logger.debug?
81
+
82
+ if ast.sexp_type == :ExpressionContext
83
+ functionCallStmt = ast.expressionAst
84
+ else
85
+ functionCallStmt = ast
86
+ end
87
+
88
+
89
+ # call target
90
+ functionCalled = functionCallStmt.callTarget # functionCallStmt.functionCalled.reference
91
+ contractCalled = functionCalled.enclosingScope
92
+ @logger.info "#{__method__} called= #{contractCalled['name']}(#{functionCalled['name']})"
93
+
94
+ # TODO: add suppport for tuples
95
+ if ( functionCallStmt.isConstructorCall )
96
+ # contrcutors return addres
97
+ returnParameter = Constants::FIELD_NAME_ADDRESS
98
+ else
99
+ # normal function
100
+ raise MissingImplementation, "Support only 1 return value, called f with #{functionCalled.responseParameters.parameters.length} params" if functionCalled.responseParameters.parameters.length != 1
101
+ returnParameter = functionCalled.responseParameters.parameters[0]['name']
102
+ end
103
+
104
+ # access return variable in function response
105
+ alApi.tlaRecordField(
106
+ alApi.tlaOperatorCall(alApi.tlaPlainname(Constants::SBUILDER_GET_RESPONSE), [exprInterfaceOperation(contractCalled['name'], functionCalled['name'])]),
107
+ returnParameter
108
+ )
109
+
110
+ end
111
+
112
+
113
+ # Translate tupleExpression by translating each tuple element
114
+ # indididually with {#dispatchExpression}.
115
+ #
116
+ # @return [Array<AlExpression>] array of translated expressions
117
+ #
118
+ def tupleExpression( tupleAst )
119
+
120
+ @logger.info "#{__method__}: proces tuple with #{tupleAst.children.length} elements"
121
+ tupleAst.children.map do |tupleElement|
122
+ dispatchExpression( tupleElement )
123
+ end
124
+
125
+ end
126
+
127
+
128
+ # Translate Solidity AST node of type :BinaryOperation to
129
+ # al-binary expression. Left hand side and right hand side
130
+ # expresions are dispatched using {#expressionDispatcher}, and
131
+ # operator translated using {#binaryOperator}. Evaluate in
132
+ # {ExpressionContext}, if 'ast' is defined within
133
+ # {ExpressionContext}.
134
+ #
135
+ # @param ast [ExpressionContext,Identifier] identifier (or
136
+ # identifier wrapped in context) to create expression for.
137
+ #
138
+ def binaryExpr( ast )
139
+
140
+ if ast.sexp_type == :ExpressionContext
141
+
142
+ # Evaluate lval and rval in ExpressionContext
143
+ lExpr = Ethereum.dispatcher( expressionDispatcher,
144
+ ast.expressionAst.children[0].sexp_type,
145
+ ExpressionContext.createExpressionContext( ast.expressionAst.children[0], ast.context )
146
+ )
147
+ rExpr = Ethereum.dispatcher( expressionDispatcher,
148
+ ast.expressionAst.children[1].sexp_type,
149
+ ExpressionContext.createExpressionContext( ast.expressionAst.children[1], ast.context )
150
+ )
151
+ operator = ast.expressionAst['operator']
152
+
153
+ else
154
+ # No context given - extract element of bin expression
155
+ lExpr = Ethereum.dispatcher( expressionDispatcher,
156
+ ast.children[0].sexp_type,
157
+ ast.children[0]
158
+ )
159
+ operator = ast['operator']
160
+ rExpr = Ethereum.dispatcher( expressionDispatcher,
161
+ ast.children[1].sexp_type,
162
+ ast.children[1]
163
+ )
164
+ end
165
+
166
+ alApi.binaryExpression do
167
+ lval lExpr
168
+ op binaryOperator(operator)
169
+ rval rExpr
170
+ end
171
+
172
+
173
+ end
174
+
175
+ # Translate 'solidityOperator' to 'alOperator. Raise
176
+ # {#MissingImplementation} if no translation found.
177
+ def binaryOperator( solidityOperator )
178
+ # TODO : map to AL-constants
179
+ solidityOperators = {
180
+ "*" => :mult,
181
+ "+" => :plus,
182
+ "-" => :minus,
183
+ "&&" => :and,
184
+ "||" => :or,
185
+ "==" => :equal,
186
+ "!=" => :unequal,
187
+ ">=" => :ge,
188
+ ">" => :gt,
189
+ "<" => :lt,
190
+ "<=" => :le,
191
+ }
192
+ alOperator = solidityOperators[solidityOperator]
193
+ raise MissingImplementation, "Unknown solidity operator #{solidityOperator} - known Solidity operators #{solidityOperators.keys.join(',')}" if alOperator.nil?
194
+ alOperator
195
+ end
196
+
197
+
198
+ # Translate Solidity :Literal to AL Constant
199
+ # expression. Translation inspects AST 'type' attribute to
200
+ # choose correct intepration for AST 'value' attribute.
201
+ #
202
+ # @param ast [ExpressionContext,Constant] constant (or constant
203
+ # wrapped in context) to create expression for.
204
+ #
205
+ #
206
+ def constantExpr( ast )
207
+
208
+ # Context does no impact constant trasnlation
209
+ if ast.sexp_type == :ExpressionContext
210
+ constantAst = ast.expressionAst
211
+ else
212
+ constantAst = ast
213
+ end
214
+
215
+ case constantAst.attribute("type")
216
+ when /^literal_string/
217
+ alApi.constantExpression do
218
+ value constantAst.attribute('value')
219
+ end
220
+ when /^int_const */
221
+ alApi.constantExpression do
222
+ value constantAst.attribute('value').to_i
223
+ end
224
+ when /^bool/
225
+ alApi.constantExpression do
226
+ value constantAst.attribute('value') == "true"
227
+ end
228
+ else
229
+ raise TranslatorException, "Missing implementation for constant expression type '#{constantAst.attribute('type')}' in #{constantAst}"
230
+ end
231
+ end
232
+
233
+ # Create expression to access member in an identifier.
234
+ #
235
+ # Implementation find path for member variables, and creates an
236
+ # {#identifierExpression}. expression to access member of the
237
+ # identifier of 'memberAccessAst'
238
+ #
239
+ # @param ast [ExpressionContext,MemberAccess] MemberAccess (or
240
+ # MemberAccess wrapped in context) to create expression
241
+ # for.
242
+ #
243
+ def memberAccessExpr( ast )
244
+ @logger.debug "#{__method__}: ast=#{ast}" if @logger.debug?
245
+
246
+ if ast.sexp_type == :ExpressionContext
247
+ memberAccessAst = ast.expressionAst
248
+ # Evaluate idExpression within context
249
+ idExpression = Ethereum.dispatcher( expressionDispatcher,
250
+ ast.expressionAst.identifierAst.sexp_type,
251
+ ExpressionContext.createExpressionContext( ast.expressionAst.identifierAst, ast.context )
252
+ )
253
+
254
+ else
255
+ memberAccessAst = ast
256
+ # Create id expression
257
+ idExpression = Ethereum.dispatcher( expressionDispatcher,
258
+ ast.identifierAst.sexp_type,
259
+ ast.identifierAst )
260
+
261
+ end
262
+
263
+ @logger.debug "#{__method__}: memberAccessAst.member=#{memberAccessAst.memberName}, idExpression=#{idExpression}" if @logger.debug?
264
+ # Translation to 'idExpression.member'
265
+ alApi.tlaRecordField( idExpression, memberAccessAst.memberName )
266
+
267
+ end
268
+
269
+
270
+ # Create expression for Solidity identifier. Evaluate identifier
271
+ # in 3 cases 1) in existing contract context (e.g. assign
272
+ # statement) 2) in context (but not for contract) 3) without
273
+ # context.
274
+ #
275
+ # Expression identifier may be available in execution context,
276
+ # for example in TLA action updating contract variable uses set
277
+ # operation and an iteration variable for contracts. In this
278
+ # case, reference to a contract should be resolved to set
279
+ # iteration variable.
280
+ #
281
+ #
282
+ #
283
+ # @param ast [ExpressionContext,Identifier] identifier (or
284
+ # identifier wrapped in context) to create expression for.
285
+ #
286
+ # @return [Al::Expression] 1) Al expression such as 'a.idName'
287
+ # if ast.sexp_type == :ExpressionContext && context for
288
+ # scope of variable declation found in context. 2)
289
+ # create expression using {#identifierReference} - else
290
+ def identifierExpr( ast )
291
+ @logger.debug "#{__method__}: ast=#{ast}" if @logger.debug?
292
+
293
+ if ast.sexp_type == :ExpressionContext && ast.getContext( ast.expressionAst.reference.scopeDefining.sexp_type ) && ast.expressionAst['value'] != Constants::SOL_THIS
294
+
295
+ # if context found for variable scope
296
+ translation = alApi.tlaRecordField(
297
+ ast.getContext( ast.expressionAst.reference.scopeDefining.sexp_type ),
298
+ ast.expressionAst['value']
299
+ )
300
+
301
+ elsif ast.sexp_type == :ExpressionContext
302
+
303
+ # in context but no match -> continue evaluation in context
304
+ translation = alApi.expression(identifierReference( ast.expressionAst ))
305
+
306
+ else
307
+
308
+ # Resolve direct reference to identifier
309
+ translation = alApi.expression(identifierReference( ast ))
310
+
311
+ end
312
+
313
+ @logger.debug( "#{__method__}: translation=#{translation.strOutput}") if @logger.debug?
314
+ translation
315
+ end
316
+
317
+
318
+ # Create expression to access indexed element of identifer. Use
319
+ # {#expressionDispatcher} to dispatch 'identifier' and 'indexer'
320
+ # expresion of IndexAccess AST node (possible wrapped within
321
+ # ExpressionContext).
322
+ #
323
+ # @param ast [ExpressionContext,IndexAccess] IndexAccess (or
324
+ # IndexAccess wrapped in context) to create expression
325
+ # for.
326
+ #
327
+ #
328
+ # @return [Al::Expression] expression to access indexed member
329
+ def indexAccessExpr( ast )
330
+ @logger.debug "#{__method__}: ast=#{ast}" if @logger.debug?
331
+
332
+ if ast.sexp_type == :ExpressionContext
333
+ indexerAst = ast.expressionAst
334
+ # eval identifier with context
335
+ indexExpr = Ethereum.dispatcher(
336
+ expressionDispatcher,
337
+ indexerAst.identifier.sexp_type,
338
+ ExpressionContext.createExpressionContext(indexerAst.identifier, ast.context )
339
+ )
340
+ indexer = Ethereum.dispatcher(
341
+ expressionDispatcher, indexerAst.indexer.sexp_type,
342
+ ExpressionContext.createExpressionContext(indexerAst.indexer, ast.context )
343
+ )
344
+ else
345
+ indexAccessAst = ast
346
+ indexExpr = Ethereum.dispatcher(
347
+ expressionDispatcher,
348
+ indexerAst.identifier.sexp_type,
349
+ indexerAst.identifier
350
+ )
351
+ indexer = Ethereum.dispatcher(
352
+ expressionDispatcher, indexerAst.indexer.sexp_type,
353
+ indexerAst.indexer
354
+ )
355
+ end
356
+
357
+ alApi.tlaRecordIndex( indexExpr, indexer )
358
+
359
+
360
+ end
361
+
362
+
363
+ # @!endgroup
364
+
365
+
366
+ # ------------------------------------------------------------------
367
+ # @!group Resolve identifier reference
368
+
369
+ # Use dispatcher {#referenceContextDispatcher} to create a
370
+ # reference to 'identifierAst'.
371
+ #
372
+ # Impelementation user pointer chain
373
+ # identifierAst.reference.scopeDefining to find 1) variable
374
+ # declaration and 2) scope of declaration to dispatch correct
375
+ # translation method in {#referenceContextDispatcher}.
376
+ #
377
+ # @param identifierAst [Identifier] AST node to resolve.
378
+ def identifierReference( identifierAst )
379
+ # identifierAst = context[:identifierAst]
380
+ @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
381
+ @logger.info "#{__method__}: ident:'#{identifierAst['value']}'" +
382
+ " decl.ref='#{identifierAst.reference.key?( "name") ? identifierAst.reference['name'] : '??' }:#{identifierAst&.reference.sexp_type}'" +
383
+ ", scope='#{identifierAst.reference.scopeDefining.respond_to?( :sexp_type) ? identifierAst.reference.scopeDefining.sexp_type : identifierAst.reference.scopeDefining.class }'"
384
+
385
+ ref = identifierAst.reference.scopeDefining
386
+
387
+ # Reference has already been resolde (e.g. :ModifierIncation)
388
+ return( ref ) if ref.is_a? Sbuilder::Al::Model::Expression
389
+ #
390
+ Ethereum.dispatcher( referenceContextDispatcher, ref.sexp_type, identifierAst )
391
+ end
392
+
393
+
394
+ # Reference to identifier depends on the type of the scope,
395
+ # where the variable, referenced by 'identifierAst', is declared
396
+ # in.
397
+ #
398
+ # Scope may be ::GlobalScope -> e.g. this, :ContractDefinition
399
+ # --> contract member variable #{contractContextReference},
400
+ # :FunctionDefinition --> function local varibale
401
+ # {#functionContextReference}, :ParameterList --> to access
402
+ # Solidity 'msg' {#parameterContextReference},
403
+ #
404
+ def referenceContextDispatcher
405
+ @referenceContextDispatcher ||= {
406
+ :GlobalScope => method(:globalContextReference),
407
+ :ContractDefinition =>method(:contractContextReference),
408
+ :FunctionDefinition =>method(:functionContextReference),
409
+ :ParameterList=>method(:parameterContextReference),
410
+ }
411
+ end
412
+
413
+ # Currently runnign contract found in local variable 'refRunningContractAddress'
414
+ #
415
+ # @return [Al::Reference] reference to currenly running contract
416
+ def globalContextReference(identifierAst)
417
+ @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
418
+ raise "Supports only '#{Constants::SOL_THIS}' - unkwon #{identifierAst['value']}" unless identifierAst['value'] == Constants::SOL_THIS
419
+
420
+ refRunningContractAddress
421
+ end
422
+
423
+ # # Create a reference for an identifier in :ModifierDefinition
424
+ # # context, which has been invoked :ModifierInvocation for a
425
+ # # function (sexp_type :FunctionDefinition).
426
+ # def modifierContextReference( identifierAst )
427
+ # @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
428
+ # functionAst = identifierAst.reference.scopeDefining
429
+ # raise "identifierAst=#{identifierAst}\n\nidentifierAst.reference=#{identifierAst.reference}\n\nfunctionAst=#{functionAst}"
430
+ # end
431
+
432
+ # Create a reference to member variable of currently running
433
+ # contract.
434
+ #
435
+ # Contract members variables are fields in records in
436
+ # 'storageRoot' variable. Current contract is pointed by
437
+ # {#refRunningContractAddress} (, which is local variable with
438
+ # the 'address' of currently running contract ).
439
+ #
440
+ def contractContextReference( identifierAst )
441
+ @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
442
+ contract = identifierAst.reference.scopeDefining
443
+ if identifierAst['value'] == Constants::SOL_THIS
444
+ # this found in local varible thisCid
445
+ refRunningContractAddress
446
+ else
447
+ ethContractVariableReference( refStorageRoot, refRunningContractAddress, identifierAst['value'] )
448
+ end
449
+ end
450
+
451
+ # Translate reference to function local variable.
452
+ def functionContextReference( identifierAst )
453
+ @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
454
+ function = identifierAst.reference.scopeDefining
455
+ contract = function.enclosingScope
456
+
457
+ ethFunctionParameterReference( contract['name'], function['name'] )
458
+ end
459
+
460
+ # Translate reference to an identifier in function paramers (=Solidity 'msg')
461
+ #
462
+ # Msg variable is accessed in a parameter passed to the
463
+ # procedure created to model 'contract' 'function'.
464
+ def parameterContextReference( identifierAst )
465
+ @logger.debug "#{__method__}: identifierAst=#{identifierAst}" if @logger.debug?
466
+ function = identifierAst.reference.scopeDefining.enclosingScope
467
+ contract = function.enclosingScope
468
+ ethFunctionParameterReference( contract['name'], function['name'], identifierAst['value'] )
469
+ end
470
+
471
+
472
+ # @!endgroup
473
+
474
+ end
475
+ end
476
+ end