sbuilder-eth 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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