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,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