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,214 @@
1
+ module Sbuilder
2
+ module Eth
3
+ # Utility to create SexpNodes
4
+ #
5
+ class SexpUtils
6
+
7
+ # ------------------------------------------------------------------
8
+ # @!group Id-generator
9
+
10
+ # @attr [Integer] idGenerator class variable used to generate
11
+ # unique identifiers
12
+ @@idGenerator = 1
13
+
14
+ ##
15
+ # @return [String] nextId unique indetifier generated for each call
16
+ def self.nextId
17
+ @@idGenerator += 1
18
+ "canonized-#{@@idGenerator}"
19
+ end
20
+ # @!endgroup
21
+
22
+
23
+
24
+ # ------------------------------------------------------------------
25
+ # @!group YAML snippets
26
+
27
+ def self.strFunctionDefinition( name, inputs, outputs, block )
28
+ inputs = [ inputs ] if inputs.is_a? String
29
+ outputs = [ outputs ] if outputs.is_a? String
30
+ <<~HERE
31
+
32
+ id: #{nextId}
33
+ name: FunctionDefinition
34
+ src: '142:2:0'
35
+ attributes:
36
+ constant: true
37
+ implemented: true
38
+ isConstructor: false
39
+ modifiers:
40
+ -
41
+ name: #{name}
42
+ payable: false
43
+ scope: 18
44
+ stateMutability: pure
45
+ superFunction:
46
+ visibility: public
47
+ children:
48
+ #{children( [
49
+ strParameterList( inputs.map{ |i| strVariableDeclaration(i) }),
50
+ strParameterList( outputs.map{ |i| strVariableDeclaration(i) } ),
51
+ block,
52
+ ]
53
+ )}
54
+ HERE
55
+ end
56
+
57
+ def self.strBlock( statements = [])
58
+ statements = [ statements ] unless statements.is_a?( Array )
59
+ <<~HERE
60
+ id: #{nextId}
61
+ name: Block
62
+ src: '273:37:0'
63
+ children:
64
+ #{children(statements)}
65
+ HERE
66
+ end
67
+
68
+ def self.strIdentifer( identifierName )
69
+ <<~HERE
70
+ id: #{nextId}
71
+ name: Identifier
72
+ src: '290:11:0'
73
+ attributes:
74
+ argumentTypes:
75
+ overloadedDeclarations:
76
+ -
77
+ referencedDeclaration: 3
78
+ type: uint256
79
+ value: #{identifierName}
80
+ HERE
81
+ end
82
+
83
+ def self.strReturnIdentifier( identifierName )
84
+
85
+ <<~HERE
86
+ id: #{nextId}
87
+ name: Return
88
+ src: '283:18:0'
89
+ attributes:
90
+ functionReturnParameters: 16
91
+ children:
92
+ #{children( [ strIdentifer( identifierName ) ])}
93
+ HERE
94
+
95
+ end
96
+
97
+ def self.strParameterList( variables=[] )
98
+ variables = [ variables] if variables.is_a? String
99
+ <<~HERE
100
+ id: #{nextId}
101
+ name: ParameterList
102
+ src: '142:2:0'
103
+ attributes:
104
+ children:
105
+ #{children(variables)}
106
+ HERE
107
+ end
108
+
109
+ # @return [String] YAML string mocking Solidity AST
110
+ # VariableDeclation of 'variableName'
111
+ def self.strVariableDeclaration( variableName )
112
+ # Type should be of no interest because variable names are
113
+ # mapped to domains.
114
+ type ="uint256"
115
+ elementaryType="uint"
116
+
117
+ variableStr=<<~EOS
118
+ name: VariableDeclaration
119
+ id: #{nextId}
120
+ attributes:
121
+ name: #{variableName}
122
+ type: #{type}
123
+ children:
124
+ - name: ElementaryTypeName
125
+ id: #{nextId}
126
+ attributes:
127
+ name: #{elementaryType}
128
+ EOS
129
+ end
130
+
131
+ # @!endgroup
132
+
133
+ # ------------------------------------------------------------------
134
+ # @!group Create AstSexps
135
+
136
+ # @return [AstSexp] Variaable declration for 'variableName'
137
+ def self.sexpVariableDeclaration( variableName )
138
+ AstSexp.from_hash(YAML.load(strVariableDeclaration( variableName )))
139
+ end
140
+
141
+ # Create :ParameterList sexp with parameters for 'variableNames'
142
+ #
143
+ # @param [Array<String>, String] variableNames
144
+ def self.sexpParameterList( variableNames=[] )
145
+ variableNames = [variableNames] if variableNames.is_a?(String)
146
+ AstSexp.from_hash(YAML.load(strParameterList( variableNames.map{ |v| strVariableDeclaration(v)} )))
147
+ end
148
+
149
+ # @!endgroup
150
+
151
+
152
+ # ------------------------------------------------------------------
153
+ # @!group Services
154
+
155
+
156
+ # Create Getter method for 'variableName'
157
+ def self.sexpGetter( variableName )
158
+ AstSexp.from_hash(
159
+ YAML.load(
160
+ strFunctionDefinition(
161
+ getterName(variableName),
162
+ [], # no input
163
+ getterReturnVariableName(variableName),
164
+ strBlock( strReturnIdentifier( variableName ))
165
+ )
166
+ ))
167
+ end
168
+
169
+ def self.sexpReturnContact( sexpContract )
170
+ end
171
+
172
+
173
+
174
+ # @!endgroup
175
+
176
+
177
+ # ------------------------------------------------------------------
178
+ # @!group Helpers
179
+
180
+ # Embedd array 'arr' to YAML string.
181
+ #
182
+ # Implementation intends array 'arr' of String with first line
183
+ # prefixed '- ' and 2nd forward with ' '.
184
+ #
185
+ # @param arr [Array<String>] array of string lines
186
+ #
187
+ def self.children( arr )
188
+ arr.map do |l|
189
+ first = true
190
+ l.gsub(/^(.*)/ ) do |line|
191
+ line = (first ? "- " + line : " " + line )
192
+ first = false
193
+ line
194
+ end
195
+ end.join( "\n")
196
+ end
197
+
198
+ # @return [String] name of getter funcction of variable
199
+ # 'variableName '
200
+ def self.getterName( variableName )
201
+ "get#{ variableName[0].capitalize + variableName[1..-1] }"
202
+ end
203
+
204
+ def self.getterReturnVariableName(variableName)
205
+ "#{Constants::GETTER_RESPONSE_PREFIX}#{variableName}"
206
+ end
207
+
208
+ # @!endgroup
209
+
210
+
211
+
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,145 @@
1
+ require 'open3'
2
+
3
+
4
+ module Sbuilder
5
+ module Eth
6
+
7
+ # Manage solidity compiler configuration {#solc_command},
8
+ # {#solc_flags}, {#output_directory} and provide method {#compile}
9
+ # to create AST for solidity source file.
10
+ #
11
+ class SolidityCompiler
12
+
13
+ # @!attribute [String] solc_command
14
+ attr_writer :solc_command
15
+
16
+ # @!attribute [String] solc_flags
17
+ attr_accessor :solc_flags
18
+
19
+ # @attribute [String] output_directory, can be configured outside
20
+ attr_accessor :output_directory
21
+
22
+ # @!group Constructor
23
+
24
+ def initialize( options={} )
25
+ initDefaults
26
+ end
27
+
28
+ def initDefaults
29
+ @output_directory = "cache/solidity"
30
+ @solc_flags = " --overwrite --combined-json ast" # -o #{output_directory}
31
+ # @solc_command = "#{Dir.pwd}/solc/solidity/build/solc/solc"
32
+ @solc_command = ENV["SOLC"] || "#{Dir.pwd}/solc"
33
+ end
34
+
35
+ # @!endgroup
36
+
37
+ # ------------------------------------------------------------------
38
+ # @!group Configuration accessors
39
+
40
+ def solc_command
41
+ @solc_command
42
+ end
43
+
44
+
45
+ # @return [String] path to a file in output directory
46
+ # def ast_path( sourcePath )
47
+ # "#{output_directory}/#{ast_file(sourcePath)}"
48
+ # end
49
+
50
+ # @return [String] filename for abstract syntax tree for
51
+ # solidity source in +sourcePath+
52
+ # def ast_file( sourcePath )
53
+ # "#{File.basename( sourcePath, File.extname(sourcePath) )}.sol_json.ast"
54
+ # end
55
+
56
+ # @!endgroup Compile
57
+
58
+ # ------------------------------------------------------------------
59
+ # @!group utilities
60
+
61
+ # @return [Boolean] true if +cmd+ points to executable or it is
62
+ # found in PATH
63
+ def which( cmd )
64
+ File.exists?( cmd ) && File.executable?( cmd ) ||
65
+ ENV['PATH'].split(':').select do |folder|
66
+ f = "#{folder}/#{cmd}"
67
+ File.exists?(f) && File.executable?(f)
68
+ end.any?
69
+ end
70
+
71
+ # @!endgroup
72
+
73
+ # ------------------------------------------------------------------
74
+ # @!group Compile
75
+
76
+ # @param sourcePath [String] source path to sol file to compile
77
+ def compile( sourcePath, &blk )
78
+
79
+ # Check sourcePath & compiler command
80
+ # astPaths = cePaths.map do |sourcePath|
81
+ # @logger.info "#{__method__}: solc_command=#{solc_command}, solc_flags=#{solc_flags}, sourcePath=#{sourcePath}, pwd=#{Dir.pwd}"
82
+ raise CompilerException, "Solidity source file '#{sourcePath}': no such file" unless File.exists?( sourcePath )
83
+ # end
84
+ raise CompilerException, "Solidity output directory '#{output_directory}': no such directory" unless Dir.exists?( output_directory )
85
+ raise CompilerException, "Solidity compiler '#{solc_command}': executable not found" unless which( solc_command )
86
+
87
+
88
+ # ast named after first source file, must not exist
89
+ # astPath = ast_path( sourcePath )
90
+ #
91
+ astPath = "#{output_directory}/combined.json"
92
+
93
+ File.delete( astPath ) if File.exists?( astPath )
94
+ raise CompilerException, "Solidity ast file '#{astPath}': could not remove" if File.exists?( astPath )
95
+
96
+
97
+ # cmd = "#{solc_command} #{solc_flags} -o #{output_directory} #{sourcePath} > #{astPath}"
98
+ cmd = "#{solc_command} #{solc_flags} -o #{output_directory} #{sourcePath} > #{output_directory}/out.txt"
99
+ # puts "cmd=#{cmd}"
100
+ out,err,exit_status = Open3.capture3( cmd )
101
+
102
+ # logger.info "#{__method__}: out=#{out}, err=#{err}, st=#{exit_status}"
103
+ raise CompilerException, <<-EOS if exit_status != 0
104
+ Error in '#{cmd}'
105
+
106
+ exit status: #{exit_status}
107
+ stderr: #{err}
108
+ output: #{out}
109
+
110
+ When compiling solidity file #{sourcePath}
111
+ using compiler configuration:
112
+ solc_command: #{solc_command}
113
+ solc_flags: #{solc_flags}
114
+ output_directory: #{output_directory}
115
+
116
+
117
+ EOS
118
+
119
+ # check that ast file was created
120
+ if ! File.exists?( astPath ) then
121
+ raise CompilerException, <<~EOS
122
+ Error compiling #{sourcePath}
123
+
124
+ Compiler command
125
+
126
+ #{cmd}
127
+
128
+ in cwd #{Dir.getwd}
129
+
130
+ failed to create solidity ast file '#{astPath}'
131
+
132
+ EOS
133
+ end
134
+
135
+ # logger.info "#{__method__}, compiledAst=#{compiledAst}"
136
+ yield astPath if block_given?
137
+ astPath
138
+ end
139
+
140
+ # @!endgroup
141
+
142
+ end # class SolidityCompiler
143
+ end
144
+ end
145
+
@@ -0,0 +1,53 @@
1
+ require 'json'
2
+
3
+ module Sbuilder
4
+ module Eth
5
+
6
+ class SolidityLoader
7
+ # ------------------------------------------------------------------
8
+ # @!group Constructor
9
+
10
+ def initialize(options={})
11
+ end
12
+
13
+
14
+ # @!endgroup
15
+
16
+ # ------------------------------------------------------------------
17
+ # @!group Class services
18
+
19
+
20
+
21
+ # @param [String] astPath path JSON file containing AST
22
+ # for several sourceUnits
23
+ #
24
+ # @yield sourceUnit, index, ast [String, Integer, AstSexp] if block given
25
+ #
26
+ # @return <Array<Array[String, int, AstSexp]> for solSource,
27
+ # index, AstTree
28
+ #
29
+
30
+ def load( astPath, &block )
31
+ # Read json form file
32
+ jsonStr = File.read( astPath )
33
+ raise "Empty json string from #{astPath}" if jsonStr.nil? || jsonStr.length == 0
34
+ solcHash = JSON.parse( jsonStr)
35
+
36
+ # Iterate all sources in combined ast && convert to AstSexp structure
37
+ ret =
38
+ solcHash["sourceList"].map.with_index do |solSource, index|
39
+ solAst = solcHash['sources'][solSource]['AST']
40
+ ast = AstSexp.from_hash( solAst )
41
+ yield solSource, index, ast if block_given?
42
+ # Builing array of arrays
43
+ [solSource, index, ast ]
44
+ end
45
+ ret
46
+ end # def load( astPath, &block )
47
+
48
+ # @!endgroup
49
+
50
+ end # class SolidityLoader
51
+ end
52
+ end
53
+
@@ -0,0 +1,840 @@
1
+ require "sbuilder-al"
2
+
3
+
4
+
5
+ module Sbuilder
6
+ module Eth
7
+ class SolidityTranslator
8
+
9
+ # ------------------------------------------------------------------
10
+ # @!group Helper classes
11
+
12
+ # include logger
13
+ include Sbuilder::Al::Util::MyLogger # mix logger
14
+
15
+ # @!endgroup
16
+
17
+
18
+ # ------------------------------------------------------------------
19
+ # @!group Attributes
20
+
21
+ # @!attribute [Hash] options options passed to initilization
22
+ attr_accessor :options
23
+
24
+ # @!attribute [Sbuilder::Eth::Ethreum] ethApi the singleton Ethereum api inteface
25
+ attr_accessor :ethApi
26
+
27
+ # @!endgroup
28
+
29
+
30
+ # ------------------------------------------------------------------
31
+ # @!group Constructor
32
+
33
+ def initialize(options={})
34
+
35
+ self.options = options
36
+ @logger = getLogger( nil, options )
37
+ @logger.info( "#{__method__}: translator created")
38
+
39
+ initApi
40
+
41
+ end
42
+
43
+ def initApi
44
+ self.ethApi = Ethereum.instance( options )
45
+ end
46
+
47
+
48
+ # @!endgroup
49
+
50
+ # ------------------------------------------------------------------
51
+ # @!group Class services
52
+
53
+ # @return [Array<AlObject>] Array of runtime objects, which
54
+ # should be added to only once. Particulary creates domain
55
+ # object 'codeHash', which includes values 'contracClasses'
56
+ #
57
+ # Assume that this method is called only once for ethereum
58
+ #
59
+ # @param contractClasses [Array<String>] array of knonw contract
60
+ # classes defined
61
+ def solidityRuntime(contractClasses)
62
+ ethApi.ethRuntime + [ ethApi.ethDomainCodeHash( contractClasses) ]
63
+ end
64
+
65
+
66
+ # For first time: return ethRuntime
67
+ #
68
+ # For each contract:
69
+ #
70
+ # - create contractType
71
+ #
72
+ # @param ast [AstSexp] abstract syntax tree
73
+ #
74
+ # @param soliditySource [String] name of source file
75
+ #
76
+ # @param block [Block] block to yield name of contract translated
77
+ #
78
+ # @return [Array<AlObject>] array of translated objects
79
+ def solidityTranslate( ast, soliditySource=nil, &block )
80
+
81
+ @logger.info "#{__method__}: soliditySource=#{soliditySource}"
82
+
83
+ # Run times object returned only once
84
+ alObjects = []
85
+ @logger.debug "#{__method__}: alObjects=#{alObjects}" if @logger.debug?
86
+
87
+ if ( ast ) then
88
+
89
+ @logger.info "#{__method__}: ast:#{ast.sexp_type}, name=#{ast.attribute('name')}"
90
+ # valid input
91
+ alObjects =
92
+ ast.nodes_with_match(:ContractDefinition).reduce( alObjects ) do |memo,contractAst|
93
+
94
+ # Let caller to know classes translated
95
+ yield contractAst['name'] if block_given?
96
+
97
+ # Load 'contractMappingTypes' before 'contractType' to
98
+ # create type defines before they are refenced
99
+ memo += contractMappingTypes(contractAst)
100
+ memo += contractType(contractAst)
101
+ memo += contracFunctions(contractAst, soliditySource)
102
+
103
+ end
104
+ end
105
+ alObjects
106
+ end
107
+
108
+ # @return [Hash] metaDefs (metatype definition) used by AlObject
109
+ # created in {#solidityTranslate}
110
+ #
111
+ # Implementation retrives 'metaDefs' from {#ethApi} Ethreum api
112
+ # inferface (used to create alObject in {#solidityTranslate})
113
+ def metaDefs
114
+ ethApi.ethMetaDefs
115
+ end
116
+
117
+ # @!endgroup
118
+
119
+ # ------------------------------------------------------------------
120
+ # @!group tranlate steops
121
+
122
+ # @param soliditySource [String] name of source file for contract implementantion
123
+ #
124
+ # @return [Array<AlObject>] AL object array modeling Solitiy
125
+ # contract functions as 1) function entry, 2) function (as a
126
+ # transaction) implementation 3) function exit
127
+ def contracFunctions(contractAst, soliditySource)
128
+
129
+ alObjects =
130
+ contractAst.nodes_with_match(:FunctionDefinition).reduce( [] ) do |memo,functionAst|
131
+ memo += [ functionEntry(contractAst, functionAst) ]
132
+ memo += functionImplementation( contractAst, functionAst, soliditySource )
133
+ memo += [functionExit(contractAst, functionAst)]
134
+ end || []
135
+ alObjects
136
+ end
137
+
138
+ # @return [AlObject] AL snippet to implement start of
139
+ # transaction (= entry to a function 'functionAst' in Solidity
140
+ # contract 'contractAst')
141
+ def functionEntry(contractAst, functionAst)
142
+ ethApi.ethServiceEntry( contractAst['name'], functionAst['name'] )
143
+ end
144
+
145
+ # @return [AlObject] AL snippet implemeting service exit
146
+ # (=completion of transaction for a function 'functionAst' in
147
+ # Solidity contract 'contractAst')
148
+ def functionExit(contractAst, functionAst)
149
+ ethApi.ethServiceExit( contractAst['name'], functionAst['name'] )
150
+ end
151
+
152
+ # @return [Array<AlObject>] AL object array for implementing
153
+ # function 'functionAst' in Solidity contract 'contractAst'.
154
+ #
155
+ # @param functionAst [AstSexp:FunctionDefinition] ast of
156
+ # function to implement
157
+ #
158
+ def functionImplementation(contractAst, functionAst, soliditySource )
159
+ @logger.debug "#{__method__}: functionAst=#{functionAst}" if @logger.debug?
160
+ @logger.info "#{__method__}: soliditySource=#{soliditySource}, functionAst.sexp_type=#{functionAst.sexp_type}"
161
+
162
+ # lambda visit req(pos=0)/resp (pos=1) parameters (and types)
163
+ lParameters = lambda do |fAst, pos|
164
+ fAst.children { |c| c.sexp_type == :ParameterList || c.sexp_type == :ResponseParameterList }[pos].parameters.map{ |declAst| ethApi.ethVariableDeclaration(declAst) }
165
+ end
166
+
167
+
168
+ sourceLink = soliditySource.nil? ? nil : {
169
+ :sourceModule => soliditySource,
170
+ :sourceLine => functionAst['sourceLine'],
171
+ }
172
+
173
+ [
174
+ ethApi.ethTransaction( contractAst.attribute('name'), # contract name
175
+ functionAst.attribute("name"), # function name --> operation
176
+ requestParameters: lParameters.curry[functionAst,0],
177
+ responseParameters: lParameters.curry[functionAst,1],
178
+ sourceLink: sourceLink
179
+ ) do |txBlock|
180
+
181
+ #if contractAst.attribute('name') == functionAst.attribute("name")
182
+ if ethApi.isConstructor( contractAst.attribute('name'), functionAst.attribute("name"))
183
+ # constructor
184
+ dispatchContractCreationPreamble( txBlock, contractAst, functionAst)
185
+ else
186
+ # normal function
187
+ dispatchMessageCallPreamble( txBlock, contractAst, functionAst)
188
+ end
189
+
190
+ # add common actions before actually executing block stmts
191
+ dispatchCommonPreambe( txBlock, contractAst, functionAst )
192
+
193
+ # Set 'invocationCtx' on :ModifierDefinition for
194
+ # :ModifierInvocation
195
+ functionAst.modifierInvocations.each do |modifierInvocation|
196
+ @logger.debug "#{__method__}: modifierInvocation=#{modifierInvocation}" if @logger.debug?
197
+
198
+ # 'msg' points to request parameters of 'functionAst' being translated
199
+ initInvocationCtx = { Constants::SOL_MSG => functionAst }
200
+
201
+ # create 'invocationCtx' with evaluted args of 'modifierInvocation'
202
+ invocationCtx = modifierInvocation.arguments.reduce( initInvocationCtx ) do |ctx,arg|
203
+
204
+ # Formal parameter in :ModifierDefinition by position
205
+ # (exclude initInvocationCtx params)
206
+ parameterPos = ctx.keys.length - initInvocationCtx.keys.length
207
+ modifierFormalParameter = modifierInvocation.modifierDefinition.requestParameters.parameterByIndex( parameterPos )
208
+ @logger.info "#{__method__}: modifierInvocation=#{modifierInvocation.modifierDefinition['name']}, param #{ctx.keys.length}=#{modifierFormalParameter['name']}"
209
+
210
+ # arg translation to tlaExpression
211
+ translatedArg = ethApi.dispatchExpression( arg )
212
+ @logger.debug "#{__method__}: modifierInvocation=#{modifierInvocation.modifierDefinition['name']}, param #{ctx.keys.length}=#{modifierFormalParameter}--> #{translatedArg.strOutput}" if @logger.debug?
213
+ # Build ctx hash
214
+ ctx[modifierFormalParameter["name"]] = translatedArg
215
+ ctx
216
+ end
217
+
218
+ @logger.debug "#{__method__} using invocationCtx=#{invocationCtx}" if @logger.debug?
219
+ modifierInvocation.modifierDefinition.invocationCtx = invocationCtx
220
+
221
+ # expand preambleStatements (=staements upto _ -char)
222
+ modifierInvocation.modifierDefinition.preambleStatements.each do |stmtAst|
223
+ dispatchBlockStatement( txBlock, stmtAst, contractAst, functionAst )
224
+ end
225
+
226
+ # 'invocationCtx', valid only for during dispatchBlockStatement
227
+ modifierInvocation.modifierDefinition.invocationCtx = nil
228
+
229
+ end # process modifiers
230
+
231
+ # process statements for :Block of :FunctionDefinition
232
+ functionAst.statements.each do |stmtAst|
233
+ dispatchBlockStatement( txBlock, stmtAst, contractAst, functionAst )
234
+ end
235
+
236
+ # successfull return here
237
+ ethApi.ethActionFinalReturn( txBlock, contractAst["name"], functionAst["name"] )
238
+
239
+ end # ethTransaction
240
+ ]
241
+ end
242
+
243
+
244
+ # Create function 'Al::FunctionDefinition' for each :Mapping
245
+ # contract member variable for mapping 'from' 'to'
246
+ def contractMappingTypes( contractAst )
247
+
248
+ # Create 'ehContractMappingFunction' for all ':Mapping' member variables
249
+ alObjects = contractAst.memberVariables.
250
+ select { |variableAst| variableAst.variableType.sexp_type == :Mapping }.
251
+ map { |variableAst| ethApi.ethContractMappingFunction( contractAst, variableAst) } ||
252
+ []
253
+
254
+ alObjects
255
+ end
256
+
257
+ # Create 'AL::Definion' for contract 'contractAst'.
258
+ #
259
+ # For each 'memberVariable' the type definition add either
260
+ #
261
+ # * parameter - for :ElementaryTypeName
262
+ #
263
+ # * reference - for :Mapping and ::UserDefinedTypeName (for contracts)
264
+ # pointing to type definition created separately
265
+ #
266
+ # @return [Array<AlObject>] 'ethContractType' for 'contractAst',
267
+ # allow several, creates just one
268
+ #
269
+ # TODO: add support structs, enums, arrays
270
+ def contractType( contractAst )
271
+ @logger.debug "#{__method__}: ast=#{contractAst}" if @logger.debug?
272
+ @logger.info "#{__method__}: contract.name=#{contractAst.attribute('name')}"
273
+
274
+ alObjects = []
275
+ alObjects <<
276
+ ethApi.ethContractType( contractAst[ "name"] ) do |contractTypeDefinition|
277
+
278
+ # for each :VariableDeclation (direct childer of 'contractAst'
279
+ contractAst.memberVariables.each do |variableAst|
280
+ @logger.debug "#{__method__}: variableAst=#{variableAst}" if @logger.debug?
281
+ @logger.info "#{__method__}: variable.name=#{variableAst.attribute('name')}@#{contractAst[ 'name']}"
282
+
283
+ # Default non-array declaraion
284
+ variableType = variableAst.variableType
285
+ isArray = false
286
+
287
+
288
+ #
289
+ if variableType.sexp_type == :ArrayTypeName
290
+ isArray = true
291
+ # Array s(:VariableDeclaration, s(:ArrayTypeName,s(?variableType) ))
292
+ variableType = variableType.children[0]
293
+ end
294
+
295
+ case variableType.sexp_type
296
+ when :ElementaryTypeName
297
+ contractTypeDefinition.parameter( variableAst["name"], isArray )
298
+ when :UserDefinedTypeName
299
+ isArray = false
300
+ contractTypeDefinition.reference( variableAst["name"], isArray, variableType.reference["name"] )
301
+ when :Mapping
302
+ isArray = false
303
+ contractTypeDefinition.reference(
304
+ variableAst.attribute("name"), isArray,
305
+ ethApi.nContractMappingMemberDefinition( contractAst['name'], variableAst['name'] ) )
306
+ else
307
+ raise TranslatorException, "Implemenation for state variable type '#{variableAst.variableType.sexp_type}' not supported #{variableAst}"
308
+ end # case
309
+ end #
310
+ end # alObject create
311
+
312
+ end # def contractType( contractAst )
313
+
314
+ # @!endgroup
315
+
316
+ # ------------------------------------------------------------------
317
+ # @!group Block pre/post -ables
318
+
319
+ # Actions starting transaction for running constructor
320
+ # 'functionAst' of contract 'contractAst'.
321
+ #
322
+ # Actions:
323
+ # - init return response
324
+ # - choose free id 'NextId' from the pool of free address
325
+ # in tla state variable 'eth_addressPool'
326
+ # - create new record for 'contractAst' account 'eth_accounts'
327
+ # - create new record member variables in contract 'contractAst' into
328
+ # 'eth_storageRoot'
329
+ # - save the identifier of constructed contracto to a local
330
+ # variable 'thisCid'
331
+ # - create a labeled step 'start' to transaction procedure
332
+ # to avoid clashes if traction statements make assignments
333
+ # to tla variables modified in this preamble.
334
+ #
335
+ # @param blockBuilder [Al::BlockBuilder] block for actions
336
+ # implementing constructor
337
+ #
338
+ # @param contractAst [AstSexp] contract to construct
339
+ #
340
+ # @param functionAst [AstSexp] ast of contract constructor
341
+ #
342
+
343
+ def dispatchContractCreationPreamble( blockBuilder, contractAst, functionAst)
344
+
345
+ # Init return response
346
+ ethApi.ethActioInitReturnValue( blockBuilder, contractAst, functionAst )
347
+
348
+ # accounts := [ address |-> NextId, balance |-> 0, codeHash |-> ]
349
+ ethApi.ethActionNewAddress( blockBuilder, contractAst )
350
+
351
+ # storageRoot := [ address |-> NextId, f1 |-> ... ]
352
+ ethApi.ethActionNewStorageRoot( blockBuilder, contractAst )
353
+
354
+ # thisCid := NextId
355
+ ethApi.ethActionInitThis( blockBuilder, ethApi.ethNextId )
356
+
357
+ # NB: must the last because nextId changed
358
+ # addressPool := addrepress \ NextId
359
+ ethApi.ethActionConsumeAddress( blockBuilder )
360
+
361
+ # Add label (=skip w. label) after preamble
362
+ blockBuilder.label( "start" )
363
+
364
+ end
365
+
366
+ # Add AL preamble to block 'txBlock' for message call
367
+ # (=non-constructor) function 'functionAst' in contract
368
+ # 'contractAst'
369
+ #
370
+ # * init response return
371
+ #
372
+ # * set thisCid (local variable) to point 'msg.recipient'
373
+ # (from procedure input variable record field recipientl)
374
+ #
375
+ # @param blockBuilder [Al::BlockBuilder] block for actions
376
+ # implementing constructor
377
+ #
378
+ # @param contractAst [AstSexp] contract to construct
379
+ #
380
+ # @param functionAst [AstSexp] ast of contract constructor
381
+ #
382
+ def dispatchMessageCallPreamble( blockBuilder, contractAst, functionAst)
383
+
384
+ # Init response return
385
+ ethApi.ethActioInitReturnValue( blockBuilder, contractAst, functionAst )
386
+
387
+ # thisCid := msg.recipint
388
+ ethApi.ethActionInitThis(
389
+ blockBuilder,
390
+ ethApi.ethMsgRecipient(contractAst["name"], functionAst["name"])
391
+ )
392
+
393
+ # Add label (=skip w. label) after preamble
394
+ blockBuilder.label( "start" )
395
+
396
+ end
397
+
398
+ # Add common actions before actually executing tx
399
+ #
400
+ # Actions:
401
+ #
402
+ # - ethActionCheckContractExists: create empty account and move
403
+ # value if account 'thisCid' does not exist (possible for
404
+ # message call, assume that constructor has created new
405
+ # account pointed by thisCid )
406
+ #
407
+ # - ethActionCheckContractType: failure if account codeHash is
408
+ # - in-correct
409
+ #
410
+ # - ethActionMovevalue: move msg.value from msg.sender to 'thisCid'
411
+ #
412
+ # @param blockBuilder [Al::BlockBuilder] block for actions
413
+ # implementing constructor
414
+ #
415
+ # @param contractAst [AstSexp] contract to construct
416
+ #
417
+ # @param functionAst [AstSexp] ast of contract constructor
418
+ #
419
+ def dispatchCommonPreambe( blockBuilder, contractAst, functionAst)
420
+
421
+ # Create empty account and exit for non existing account
422
+ ethApi.ethActionCheckContractExists( blockBuilder, contractAst, functionAst)
423
+
424
+ # Abort if codeHash does not match currencly executing contract
425
+ ethApi.ethActionCheckContractType( blockBuilder, contractAst, functionAst)
426
+
427
+ # Move value from sender to recipient
428
+ ethApi.ethActionMovevalue( blockBuilder, contractAst, functionAst)
429
+
430
+ end
431
+
432
+ # @!endgroup
433
+
434
+ # ------------------------------------------------------------------
435
+ # @!group description Block statements
436
+
437
+
438
+ # Dispatch translation for 'stmtAst' in block for function
439
+ # 'functionAst' in contract 'functionAst'. Statements actions
440
+ # are added to block 'blockBuilder'
441
+ #
442
+ # @param blockBuilder [AL:BlockBuilder] AL block where statement should be added
443
+ #
444
+ # @param stmtAst [SexpA] statement to dispatch
445
+ #
446
+ # @param contractAst [SexpAst] contract of 'functionAst'
447
+ #
448
+ # @param functionAst [SexpAst] function of block where statement
449
+ # belongs to
450
+ #
451
+ # TODO: add support for statements: switch, while, for, break,
452
+ # continue, ternary if. Document if some of these statements are
453
+ # handled by preprocessing.
454
+ #
455
+ # TODO: add support for revert and throw (from version 0.4.13
456
+ # the throw keyword is deprecated)
457
+ #
458
+ # TODO: add support for (internal and external )function calls.
459
+ #
460
+ # TODO: add support for named function call arguments (or add
461
+ # comment if handled by preprocesor)
462
+ #
463
+ # TODO: add suppport for calling constructor new keyword
464
+
465
+
466
+ def dispatchBlockStatement( blockBuilder, stmtAst, contractAst, functionAst )
467
+ @logger.debug( "#{__method__}: stmtAst=#{stmtAst}") if @logger.debug?
468
+ @logger.info "#{__method__} dispatch stmt: #{stmtAst.sexp_type} within #{contractAst['name']}(#{functionAst['name']})"
469
+
470
+ case stmtAst.sexp_type
471
+ when :Return
472
+ returnStatement( blockBuilder, stmtAst, contractAst, functionAst )
473
+ when :ExpressionStatement
474
+ exprStmt = stmtAst.children[0]
475
+ case exprStmt.sexp_type
476
+ when :Assignment
477
+ assignStatement( blockBuilder, exprStmt )
478
+ when :FunctionCall
479
+ dispatchFunctionCallStmt( blockBuilder, exprStmt, contractAst, functionAst )
480
+ else
481
+ raise TranslatorException,
482
+ "Implemenation for 'ExpressionStatement' missing for statement type '#{exprStmt.sexp_type}' in #{exprStmt}"
483
+ end
484
+ when :FunctionCall
485
+ # Function call inserted for expression calls
486
+ dispatchFunctionCallStmt( blockBuilder, stmtAst, contractAst, functionAst )
487
+ when :IfStatement
488
+ ifStatement( blockBuilder, stmtAst, contractAst, functionAst )
489
+ when :Throw
490
+ throwStatement( blockBuilder, stmtAst, contractAst, functionAst )
491
+ else
492
+ raise TranslatorException, "Implemenation missing for statement type '#{stmtAst.sexp_type}' in #{stmtAst}"
493
+ end
494
+ end
495
+
496
+ # Distpatch traslation of :FunctionCall -statement. Call to
497
+ # Ethereum infrastructure services (=functions flagged as
498
+ # :reservedFunctionType) are processed separately.
499
+ #
500
+ # - SelfDestruct --> {#SelfDestruct}
501
+ # - Revert --> {#throwStatement}: alias for throw
502
+ #
503
+ # TODO: add support for suicide/kill (or document why not
504
+ # supported)
505
+ #
506
+ # TODO: add support for "normal function calls" (eiher running locally,
507
+ # or running in transaction.
508
+ #
509
+ def dispatchFunctionCallStmt( blockBuilder, functionCallStmt, contractAst, functionAst )
510
+ @logger.debug( "#{__method__}: functionCallStmt=#{functionCallStmt}") if @logger.debug?
511
+
512
+ # Dispatch well known Solidity functions ':reservedFunctionType'
513
+ dispatchSolidityFunctions = {
514
+ :SelfDestruct => ->( callStmt ) do
515
+ selfdestructStmt( blockBuilder, callStmt, contractAst, functionAst )
516
+ end,
517
+ :Revert => ->(revertStmt) do
518
+ throwStatement( blockBuilder, revertStmt, contractAst, functionAst )
519
+ end,
520
+ :Require => ->(requireStmt) do
521
+ requireStatement( blockBuilder, requireStmt, contractAst, functionAst )
522
+ end,
523
+ }
524
+
525
+ @logger.debug "#{__method__}: functionCallStmt&.functionCalled=#{functionCallStmt&.functionCalled}" if @logger.debug?
526
+ if functionCallStmt.isCallToReservedFunctionType
527
+ # dispatch Solidity well known function e.g. selfDestruct
528
+ # based on sexp_type of the resolved reference
529
+ Ethereum.dispatcher( dispatchSolidityFunctions, functionCallStmt.functionCalled.reference.sexp_type, functionCallStmt )
530
+ else
531
+ # "Normal" function calls
532
+ functionCallStatement( blockBuilder, functionCallStmt, contractAst, functionAst )
533
+ end
534
+
535
+ end
536
+
537
+ # Translate =selfdestruct= statement into body 'blockBuilder' of
538
+ # Solidity contract 'contractAst' function 'functionAst.'
539
+ #
540
+ # Delegate implemention to {#Ethereum::ethDeleteAccount}
541
+ def selfdestructStmt(blockBuilder, selfdestructCallStmtAst, contractAst, functionAst )
542
+ @logger.debug( "#{__method__}: selfdestructCallStmtAst=#{selfdestructCallStmtAst}") if @logger.debug?
543
+
544
+ astRecipient = selfdestructCallStmtAst.arguments[0]
545
+ @logger.debug( "#{__method__}: astRecipient=#{astRecipient}") if @logger.debug?
546
+
547
+ # Delete account by removing entries for currently running
548
+ # contract
549
+ ethApi.ethDeleteAccount( blockBuilder, astRecipient )
550
+
551
+ end
552
+
553
+ # Translate :Assign statement assigning expression 'rval' to
554
+ # 'lval', where lval.sexp_type is :Identifier (assignment to
555
+ # elementary type variable), or lval.sexp_type is :IndexAccess
556
+ # (assignment to a mapping variable)
557
+ #
558
+ # Currently supports assigning to contract member variable
559
+ # :ContractDefinition, and :FunctionDefinition (for return
560
+ # value)
561
+ #
562
+ # @param assignStmtAst [Assignment] assing to 'lval' value in
563
+ # 'rval' using operator 'operator'
564
+ def assignStatement( blockBuilder, assignStmtAst )
565
+ @logger.debug( "#{__method__}: assignStmtAst=#{assignStmtAst}") if @logger.debug?
566
+ @logger.info( "#{__method__}: assignStmtAst.lval=#{assignStmtAst&.lval.sexp_type}")
567
+
568
+
569
+ # Should have pre-processed operator such as +=, -=, ...
570
+ raise TranslatorException, "Operator implemenation missing #{assignStmtAst}" unless assignStmtAst["operator"] == "="
571
+
572
+ dispatcherVariableLval = {
573
+ # Assign to contract state variable
574
+ :ContractDefinition => ->(assignStmtAst) do
575
+ variableAst = assignStmtAst.lval
576
+ contractAst = variableAst.sexp_type == :IndexAccess ?
577
+ variableAst.identifier.reference.scopeDefining :
578
+ variableAst.sexp_type == :Identifier ?
579
+ variableAst.reference.scopeDefining :
580
+ (raise TranslatorException, "Unsupported lval type #{assignStmtAst.lval}")
581
+
582
+ expressionAst = assignStmtAst.rval
583
+ blockBuilder.assignTo do
584
+ variable ethApi.refStorageRoot
585
+ rval ethApi.ethAssignStatement( contractAst, variableAst, expressionAst )
586
+ end # assignTo
587
+ end, # lambda for :ContractDefinition
588
+
589
+ # assign to function return value
590
+ :ResponseParameterList => ->(assignStatement) do
591
+ blockBuilder.returnValue(
592
+ assignStmtAst.lval["value"],
593
+ ethApi.dispatchExpression( assignStmtAst.rval ))
594
+ end , # lambda for :FunctionDefinition
595
+
596
+ }
597
+
598
+ # Path to lval different for :Identifer and :IndexAccess
599
+ lvalScope = if ( assignStmtAst.lval.sexp_type == :Identifier )
600
+ assignStmtAst&.lval&.reference&.scopeDefining&.sexp_type
601
+ elsif ( assignStmtAst.lval.sexp_type == :IndexAccess )
602
+ assignStmtAst.lval.identifier.reference&.scopeDefining&.sexp_type
603
+ else
604
+ raise TranslatorException, "Unsupported assigment to #{assignStmtAst&.lval} in #{assignStmtAst}" unless [:Identifier, :IndexAccess].include?( assignStmtAst&.lval.sexp_type )
605
+ end
606
+ @logger.debug( "#{__method__}: lvalScope=#{lvalScope} for #{assignStmtAst.lval.sexp_type}") if @logger.debug?
607
+
608
+ # Call lambda(assignStmtAst.lval) dispatcherVariableLval[lvalScope]
609
+ Ethereum.dispatcher( dispatcherVariableLval, lvalScope, assignStmtAst )
610
+ end
611
+
612
+ # Translate 'returnStmtAst' from function 'functionAst' in
613
+ # contract 'contractAst' to AL constructs 'blockBuilder.ret'
614
+ # and goto end (of function procedure).
615
+ #
616
+ # Edge cases for return expressions:
617
+ #
618
+ # * no return parameters for a function: -> ret(true)
619
+ #
620
+ # * tuple-parameter/array of parameters: -> ret(true,exprRecord),
621
+ # where 'exprRecord' maps function return parameter to
622
+ # 'retExpr', where 'retExpr' are translated using
623
+ # {#dispatchExpression} with array flatten (to deal with
624
+ # return :TupleExpression vs. return array of expressions)
625
+ #
626
+ #
627
+ # @param blockBuilder [AL:BlockBuilder] AL block where statement
628
+ # should be added, see {#dispatchBlockStatement}
629
+ #
630
+ # @param returnStmtAst see {#dispatchBlockStatement}
631
+ #
632
+ #
633
+ def returnStatement( blockBuilder, returnStmtAst, contractAst, functionAst )
634
+
635
+ expressionAsts = returnStmtAst.children
636
+ @logger.debug "#{__method__}: expressionAsts=#{expressionAsts}" if @logger.debug?
637
+ @logger.info "#{__method__}: function '#{functionAst['name']}' returns #{expressionAsts.length} expressions: #{expressionAsts}"
638
+
639
+ if expressionAsts.length == 0
640
+ # just return status (response remains unchanged)
641
+ blockBuilder.ret true
642
+ else
643
+
644
+ # translate return expression (flatten for :TupleExpression)
645
+ retExpressions = expressionAsts.map do |expressionAst|
646
+ ethApi.dispatchExpression( expressionAst )
647
+ end.flatten
648
+
649
+ # Assert param count == return expressions count
650
+ raise TranslatorException,
651
+ "Number of ret expressions #{retExpressions.length} " +
652
+ "!= number of return params #{functionAst.responseParameters.parameters.length}" if retExpressions.length != functionAst.responseParameters.parameters.length
653
+
654
+ # return TRUE, [ ret0 |-> expr0, ret1 |-> expr1, ... ]
655
+ blockBuilder.ret(
656
+ true,
657
+ ethApi.tlaRecord(
658
+ functionAst.responseParameters.parameters.map.with_index do |parameter, i|
659
+ ethApi.tlaRecordFieldDef( parameter["name"], retExpressions[i] )
660
+ end
661
+ ))
662
+ end
663
+
664
+ # end execution (=jump to end label)
665
+ ethApi.ethActionGotoEnd( blockBuilder, contractAst["name"], functionAst["name"] )
666
+ end
667
+
668
+
669
+ # Translate 'requireSmtAst' to 'blockBuilder.failure' if
670
+ # expression ('requireSmtAst.expression') evaluates FALSE.
671
+ #
672
+ # @param functionCallAst [FunctionCall] Function call statement
673
+ # for requiremet with exactly 1 argument for requirement
674
+ # expression.
675
+ #
676
+ #
677
+ def requireStatement( blockBuilder, functionCallAst, contractAst, functionAst )
678
+
679
+ @logger.info "#{__method__} add throw #{functionCallAst}"
680
+
681
+ raise "Require supports only one argument now #{functionCallAst.arguments.length} in #{functionCallAst.arguments}" unless functionCallAst.arguments.length == 1
682
+
683
+ ifStmt = blockBuilder.ifThen( ethApi.not(ethApi.tlaParenthesis(ethApi.dispatchExpression( functionCallAst.arguments[0] )))) do
684
+ ifBlockBuilder = self
685
+ ifBlockBuilder.failure( ethApi.nEthnInterfaceOperation( contractAst["name"], functionAst["name"] ))
686
+ end
687
+ end
688
+
689
+ # Translate 'functionCallStmt' -statement into the body
690
+ # 'blockBuilder' of Solidity contract 'contractAst' function
691
+ # 'functionAst'.
692
+ #
693
+ #
694
+ def functionCallStatement( blockBuilder, functionCallStmt, contractAst, functionAst )
695
+
696
+ @logger.debug "#{__method__}: functionCallStmt=#{functionCallStmt}" if @logger.debug?
697
+
698
+ # call target
699
+ functionCalled = functionCallStmt.callTarget # functionCallStmt.functionCalled.reference
700
+ contractCalled = functionCalled.enclosingScope
701
+ @logger.debug "#{__method__}: functionCalled=#{functionCalled}, contractCalled=#{contractCalled}" if @logger.debug?
702
+
703
+ # :create, :call, :remote, :callcode, :delegatecall
704
+ callType = callType( functionCallStmt, contractAst, functionAst, contractCalled, functionCalled )
705
+ valueModifier = functionCallStmt.valueModified
706
+
707
+
708
+ @logger.info "#{__method__}: callType=#{callType}, valueModifier=#{valueModifier}, caller=#{contractAst['name']}(#{functionAst["name"]}) called=#{contractCalled['name']}(#{functionCalled["name"]})"
709
+
710
+ # Use 'valueModifier' from f-call if present, defauls 0
711
+ valueModifierExpression =
712
+ if valueModifier then
713
+ raise "Expect one argument in #{valueModifier.arguments} as value modifer in #{valueModifier} " if valueModifier.arguments.length != 1
714
+ ethApi.dispatchExpression(valueModifier.arguments.first)
715
+ else
716
+ ethApi.expression(0)
717
+ end
718
+ @logger.debug "#{__method__}: valueModifierExpression=#{valueModifierExpression.strOutput}" if @logger.debug?
719
+
720
+ # Evaluate arguments in function call (including common
721
+ # paremters recient, sender, originator, value)
722
+ parameters = ethApi.ethCallParameters(
723
+ callType, contractAst["name"], functionAst["name"],
724
+
725
+ # Common parameters
726
+ ethApi.ethCallCommonRequestParameters( callType, contractAst["name"], functionAst["name"], functionCallStmt, valueModifierExpression ),
727
+
728
+ # Evaluate function call arguments
729
+ functionCallStmt.arguments.map.with_index do |argument, parameter_index|
730
+ parameterAst = functionCalled.requestParameters.parameters[parameter_index]
731
+ # [paremeter name, AlExpression] -pair
732
+ [ parameterAst["name"], ethApi.dispatchExpression(argument) ]
733
+ end
734
+ ) # parameters to function call
735
+
736
+ # dispatch actual call
737
+ case callType
738
+ when :create
739
+ ethApi.ethActionCall_create( blockBuilder, contractAst, functionAst, functionCallStmt, parameters, contractCalled, functionCalled )
740
+ when :call
741
+ ethApi.ethActionCall_call( blockBuilder, contractAst, functionAst, functionCallStmt, parameters, contractCalled, functionCalled )
742
+ when :callcode
743
+ ethApi.ethActionCall_callcode( blockBuilder, contractAst, functionAst, functionCallStmt, parameters, contractCalled, functionCalled )
744
+ when :delegatecall
745
+ ethApi.ethActionCall_delegatecall( blockBuilder, contractAst, functionAst, functionCallStmt, parameters, contractCalled, functionCalled )
746
+ else
747
+ raise TranslatorException, "Unknown callType #{callType}"
748
+ end
749
+ end
750
+
751
+
752
+ # @return [:create, :call, :remote, :callcode, :delegatecall]
753
+ # callType for 'contractCaller(functionCaller)' calling
754
+ # 'contractCalled(functionCalled)'
755
+ #
756
+ # Symbol values returned corresponds to EVM mnemonics in
757
+ # Ethereum yellow paper appedix F, section 'System operations'.
758
+ def callType( functionCallStmt, contractCaller, functionCaller, contractCalled, functionCalled )
759
+ callType =
760
+ if contractCalled['name'] == functionCalled['name']
761
+ :create
762
+ elsif functionCallStmt.functionCalled.sexp_type == :Identifier # local call
763
+ :delegatecall
764
+ # elsif # library ??
765
+ # :callcode
766
+ else # functionCallStmt.functionCalled.sexp_type == :MemberAccess # remote if called trough membar access
767
+ :call
768
+ end
769
+ @logger.info "#{__method__}: #{contractCaller["name"]}(#{functionCaller["name"]}) call to #{contractCalled["name"]}(#{functionCalled["name"]}) --> #{callType}"
770
+ callType
771
+ end
772
+ #
773
+ # Triggering exception for 'throwSmtAst' is simply setting
774
+ # 'failure' on 'blockBuilder'
775
+ #
776
+ # @param blockBuilder [AL:BlockBuilder] AL block where statement
777
+ # should be added, see {#dispatchBlockStatement}
778
+
779
+ def throwStatement( blockBuilder, throwSmtAst, contractAst, functionAst )
780
+ @logger.info "#{__method__} add throw #{throwSmtAst}"
781
+ blockBuilder.failure( ethApi.nEthnInterfaceOperation( contractAst["name"], functionAst["name"] ))
782
+ end
783
+
784
+ # Translate Solidity 'ifStmtAst' to AL 'ifThen' and 'elseThen'
785
+ # blocks
786
+ def ifStatement( blockBuilder, ifStmtAst, contractAst, functionAst )
787
+ @logger.info "#{__method__} add if stmt for condition #{ifStmtAst.condition}"
788
+ # puts "ifStmtAst.conditiion=#{ifStmtAst.condition}"
789
+ ifStmt = blockBuilder.ifThen( ethApi.dispatchExpression(ifStmtAst.condition )) {
790
+ ifBlockBuilder = self
791
+ # add statements in ifBlock
792
+ ifStmtAst.ifThen.children do |stmt|
793
+ dispatchBlockStatement( ifBlockBuilder, stmt, contractAst, functionAst )
794
+ end
795
+
796
+ }
797
+
798
+ if ( ifStmtAst.elseThen )
799
+ # else brach given - add corresponding elseBlock
800
+ ifStmt.elseThen {
801
+
802
+ elseBlockBuilder = self
803
+ # add statements in elseBlock
804
+ ifStmtAst.elseThen.children do |stmt|
805
+ dispatchBlockStatement( elseBlockBuilder, stmt, contractAst, functionAst )
806
+ end
807
+
808
+ }
809
+ end
810
+ end
811
+
812
+ # @!endgroup
813
+
814
+
815
+ # ------------------------------------------------------------------
816
+ # @!group Private helpers
817
+
818
+ # # @param mapper [Hash] key=symbol to choose, value = lamba
819
+ # # @param ast [AstSext] AST node to dispatch
820
+ # def dispatcher( mapper, key, ast )
821
+
822
+ # @logger.debug( "#{__method__}: mapper=#{mapper.keys.join(',')} key=#{key} ast=#{ast}")
823
+
824
+ # # Error checks
825
+ # raise TranslatorException, "Missing dispatcher key to dispatch one #{mapper.keys.join(',')}. Error dispatching ast node: #{ast}" if key.nil?
826
+ # raise TranslatorException, "Dispatch key #{key} not found in known keys:#{mapper.keys.join(',')}. Error dispatching ast node: #{ast}" if mapper[key].nil?
827
+
828
+ # # invoke lamba
829
+ # mapper[key][ast]
830
+
831
+ # end
832
+
833
+ # @!endgroup
834
+
835
+
836
+
837
+
838
+ end # class
839
+ end
840
+ end