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