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.
- checksums.yaml +7 -0
- data/README.org +19 -0
- data/VERSION +1 -0
- data/bin/state-explorer.sh +215 -0
- data/lib/eth/al_api.rb +12 -0
- data/lib/eth/ast_sexp.rb +519 -0
- data/lib/eth/constants.rb +354 -0
- data/lib/eth/ethereum.rb +2054 -0
- data/lib/eth/ethereum_expression.rb +476 -0
- data/lib/eth/exception.rb +9 -0
- data/lib/eth/global_scope.rb +100 -0
- data/lib/eth/module.rb +18 -0
- data/lib/eth/sexp_processor.rb +66 -0
- data/lib/eth/sexp_processor_call_separator.rb +163 -0
- data/lib/eth/sexp_processor_getter.rb +125 -0
- data/lib/eth/sexp_processor_resolve.rb +122 -0
- data/lib/eth/sexp_processor_scope.rb +355 -0
- data/lib/eth/sexp_utils.rb +214 -0
- data/lib/eth/solidity_compiler.rb +145 -0
- data/lib/eth/solidity_loader.rb +53 -0
- data/lib/eth/solidity_translator.rb +840 -0
- data/lib/mixer/reference.rb +30 -0
- data/lib/mixer/scope.rb +100 -0
- data/lib/mixer/scoped.rb +18 -0
- data/lib/plugin/controller.rb +267 -0
- data/lib/plugin/module.rb +3 -0
- data/lib/plugin/plugin.rb +33 -0
- data/lib/sbuilder-eth.rb +6 -0
- data/sbuilder-eth.gemspec +38 -0
- metadata +149 -0
@@ -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
|