feat: add detecor code
This commit is contained in:
1 parent
6ed28aa202
commit
91db5e272b
40 files changed
+9918
No files matched your search
@@ -0,0 +1,2 @@
|
||||
# Part of the code uses [Vandal] (https://github.com/usyd-blockchain/vandal) as reference.
|
||||
# For any question, please contact Mengya Zhang <zhang.9407@osu.edu>.
|
||||
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Script for Processing Smart Contract Transactions
|
||||
|
||||
This script processes smart contract transactions from a specified input folder.
|
||||
It reads each transaction file, builds a Transactional Abstract Control Flow Graph (TAC EFG),
|
||||
and exports the related facts based on a set of target opcodes. The script maintains a log
|
||||
of any errors encountered during processing and saves the progress to continue from the last
|
||||
processed file in case of interruption. The error log and progress are saved in CSV and text
|
||||
files, respectively.
|
||||
|
||||
To run the script, use the command: `python3 analyze3.py`
|
||||
|
||||
|
||||
Author: Yangyi Zou
|
||||
Date: Dec 2023
|
||||
Usage: python3 analyze3.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from os.path import abspath, dirname, join
|
||||
import src.exporter as exporter
|
||||
import src.tac_efg as tac_efg
|
||||
import src.settings as settings
|
||||
import csv, threading, queue, time
|
||||
import pdb,traceback
|
||||
|
||||
# 初始化错误日志和进度跟踪
|
||||
INTERMIDIATE_FOLDER = 'intermediate'
|
||||
LOGGING_FOLDER = 'logging'
|
||||
if not os.path.exists(LOGGING_FOLDER):
|
||||
os.makedirs(LOGGING_FOLDER)
|
||||
if not os.path.exists(INTERMIDIATE_FOLDER):
|
||||
os.makedirs(INTERMIDIATE_FOLDER)
|
||||
|
||||
progress_file_path = os.path.join(INTERMIDIATE_FOLDER,'progress_test.txt')
|
||||
error_log_file_path = os.path.join(INTERMIDIATE_FOLDER,'error_log_test.csv')
|
||||
|
||||
progress_file = open(progress_file_path, 'a') if os.path.exists(progress_file_path) else open(progress_file_path, 'w')
|
||||
|
||||
error_log_file_mode = 'a' if os.path.exists(error_log_file_path) else 'w'
|
||||
error_log_file = open(error_log_file_path, error_log_file_mode, newline='')
|
||||
|
||||
error_writer = csv.writer(error_log_file)
|
||||
if error_log_file_mode == 'w':
|
||||
error_writer.writerow(['Transaction', 'Error'])
|
||||
|
||||
def log_error(transaction, error):
|
||||
"""记录错误到 CSV 文件"""
|
||||
error_writer.writerow([transaction[:-4], str(error)])
|
||||
error_log_file.flush()
|
||||
|
||||
def setup_file_logger(filename):
|
||||
"""为每个文件设置独立的日志处理器"""
|
||||
log_filename = os.path.join(LOGGING_FOLDER, filename[:-4] + '_error.log')
|
||||
file_handler = logging.FileHandler(log_filename, mode='w') # 使用写入模式,覆盖旧日志
|
||||
formatter = logging.Formatter('%(levelname)s:%(name)s:%(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
# 获取根日志器并添加新的文件处理器
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.addHandler(file_handler)
|
||||
# 返回文件处理器以便之后移除
|
||||
return file_handler
|
||||
|
||||
def remove_file_logger(file_handler):
|
||||
"""移除指定的文件日志处理器"""
|
||||
logger = logging.getLogger()
|
||||
logger.removeHandler(file_handler)
|
||||
file_handler.close()
|
||||
|
||||
def update_progress(transaction):
|
||||
"""更新进度文件"""
|
||||
with open(progress_file_path, 'w') as progress_file:
|
||||
progress_file.write(transaction)
|
||||
|
||||
# 设置超时时间(以秒为单位)
|
||||
timeout_seconds = 30 # 示例:2分钟
|
||||
|
||||
def process_file(filename, line_number):
|
||||
# pdb.set_trace()
|
||||
# 为当前文件设置日志处理器
|
||||
file_handler = setup_file_logger(filename)
|
||||
|
||||
"""处理单个文件的函数,同时显示行号"""
|
||||
start_time = time.time()
|
||||
try:
|
||||
input_file = os.path.join(input_folder, filename)
|
||||
output_folder = os.path.join('./facts/fact_' + filename[:-4])
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
with open(input_file, "r") as f:
|
||||
efg = tac_efg.TACGraph.from_opcode(f)
|
||||
|
||||
# 假设 export 操作是耗时操作,需要放在超时检测中
|
||||
if time.time() - start_time < timeout_seconds:
|
||||
exporter.EFGTsvExporter(efg).export(output_dir=output_folder, out_opcodes=target_opcodes)
|
||||
else:
|
||||
raise TimeoutError("Processing exceeded time limit.")
|
||||
|
||||
# 计算处理时间并打印信息
|
||||
processing_time = time.time() - start_time
|
||||
print(f"完成处理文件 Processed {filename} in {processing_time:.2f} seconds")
|
||||
|
||||
update_progress(filename)
|
||||
|
||||
except TimeoutError as te:
|
||||
log_error(filename, str(te))
|
||||
print(f"Line {line_number}: {filename} skipped due to timeout.")
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing file {filename}: {e}", exc_info=True)
|
||||
log_error(filename, str(e))
|
||||
|
||||
print(f"Exception type: {type(e)}")
|
||||
print(f"Exception message: {e}")
|
||||
traceback.print_exc() # 这需要先导入 traceback 模块
|
||||
remove_file_logger(file_handler)
|
||||
|
||||
# 修改 file_processing_worker 函数,以传递行号到 process_file
|
||||
def file_processing_worker():
|
||||
"""线程工作函数,处理文件队列中的文件"""
|
||||
line_number = 0
|
||||
while True:
|
||||
filename = file_queue.get()
|
||||
print("------------------------------------------------------------------------------------------------------")
|
||||
print(f"Line number:{line_number} Processing file: {filename}")
|
||||
if filename is None:
|
||||
break
|
||||
line_number += 1
|
||||
process_file(filename, line_number)
|
||||
file_queue.task_done()
|
||||
|
||||
# 设置日志等级
|
||||
# logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(name)s:%(message)s')
|
||||
|
||||
# 设置目标操作码
|
||||
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD', 'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE', 'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE', 'STATICCALL']
|
||||
|
||||
|
||||
#####################################################
|
||||
#####################################################
|
||||
# 设置输入文件夹路径
|
||||
# input_folder = "/home/yangyi/database/TxSpector/detector/test" # For test debug
|
||||
# input_folder = "test" # For test debug
|
||||
input_folder = "/home/yangyi/database/smart_contract-dataset/Tx_tuples"
|
||||
|
||||
# 设置线程数和文件队列
|
||||
num_threads = 1
|
||||
file_queue = queue.Queue()
|
||||
#####################################################
|
||||
#####################################################
|
||||
|
||||
|
||||
# 创建并启动线程
|
||||
threads = []
|
||||
for _ in range(num_threads):
|
||||
thread = threading.Thread(target=file_processing_worker)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
|
||||
# 读取上次处理的进度
|
||||
last_processed = None
|
||||
if os.path.exists(progress_file_path):
|
||||
with open(progress_file_path, 'r') as progress_file:
|
||||
last_processed = progress_file.read().strip()
|
||||
|
||||
# 将文件添加到队列(在启动线程之后)
|
||||
for filename in sorted(os.listdir(input_folder)):
|
||||
if filename.endswith('.txt') and (not last_processed or filename > last_processed):
|
||||
file_queue.put(filename)
|
||||
|
||||
# 等待队列清空
|
||||
file_queue.join()
|
||||
|
||||
# 停止工作线程
|
||||
for _ in range(num_threads):
|
||||
file_queue.put(None)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
print(" ==================------------- Processing complete -------------==================")
|
||||
error_log_file.close()
|
||||
@@ -0,0 +1,125 @@
|
||||
import os
|
||||
import subprocess
|
||||
import argparse
|
||||
import logging
|
||||
import requests, json, time
|
||||
import sys
|
||||
from os.path import abspath, dirname, join
|
||||
import src.exporter as exporter
|
||||
import src.tac_efg as tac_efg
|
||||
import src.settings as settings
|
||||
|
||||
def start_logo():
|
||||
print("="*145)
|
||||
print("{:^7}|{:^69}|{:^46}|{:^19}|".format("Line", "Transaction Hash", "Receiver contract hash","Time"))
|
||||
print("{:^7}|{:^68}|{:^45}|{:^18}|".format("Number", "\U0001F4B0", "\U0001F4B5", "\U000023F0"))
|
||||
print("-"*145)
|
||||
|
||||
def line_show(lineNumber:int, transaction_hash:str, receiver_hash, exec_time: str):
|
||||
print("{:^7}|{:^69}|{:^46}|{:^19}|".format(lineNumber, transaction_hash, receiver_hash, exec_time))
|
||||
|
||||
def end_logo():
|
||||
print("="*141)
|
||||
|
||||
def format_time(seconds):
|
||||
days, seconds = divmod(seconds, 86400)
|
||||
hours, seconds = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
seconds = round(seconds,3)
|
||||
|
||||
if days > 0:
|
||||
return f"{int(days)}d {int(hours)}h {int(minutes)}m {seconds}s"
|
||||
elif hours > 0:
|
||||
return f"{int(hours)}h {int(minutes)}m {seconds}s"
|
||||
elif minutes > 0:
|
||||
return f"{int(minutes)}m, {seconds}s"
|
||||
else:
|
||||
return f"{seconds} seconds"
|
||||
|
||||
def debug_trace_receiver(tx_hash):
|
||||
"""
|
||||
Get the receiver contract hash
|
||||
"""
|
||||
URL = "http://localhost:8545"
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_getTransactionByHash",
|
||||
"params": [tx_hash],
|
||||
"id": 1
|
||||
}
|
||||
response = requests.post(URL, headers=headers, data=json.dumps(data))
|
||||
result = response.json()
|
||||
try:
|
||||
to_address = result.get('result',{}).get('to','No address found')
|
||||
except Exception as e:
|
||||
print(f"show the error: {e}")
|
||||
return to_address # Return the 'to' address as a string
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Set up logger, with appropriate log level depending on verbosity.
|
||||
log_level = logging.WARNING
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
||||
|
||||
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD',
|
||||
'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE',
|
||||
'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE',
|
||||
'STATICCALL']
|
||||
|
||||
# Set the path to your input folder and the path to the analyze_geth.sh script
|
||||
input_folder = './Reenter_OPCODE'
|
||||
script_path = './bin/analyze_geth.sh'
|
||||
|
||||
start_logo()
|
||||
number = 0
|
||||
# Iterate over all files in the input folder
|
||||
for filename in os.listdir(input_folder):
|
||||
start_time = time.time()
|
||||
number += 1
|
||||
if filename.endswith('.txt'): # Check if the file is a .txt file
|
||||
input_file = os.path.join(input_folder, filename)
|
||||
trans_hash = filename[:-4]
|
||||
output_folder = os.path.join('./facts/fact_' + trans_hash) # Removing '.txt' and appending 'fact_'
|
||||
|
||||
# Create the output folder if it doesn't exist
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
|
||||
# Build TAC EFG from input file
|
||||
try:
|
||||
logging.info("Reading from '%s'.", input_file)
|
||||
with open(input_file, "r") as f:
|
||||
# print(f" \033[92m{number}\033[0m {trans_hash}",end=" ")
|
||||
efg = tac_efg.TACGraph.from_opcode(f)
|
||||
logging.info("Initial EFG generation completed.")
|
||||
|
||||
# Catch a Control-C and exit with UNIX failure status 1
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\nUser interrupted processing at transaction hash: {trans_hash[:-4]}")
|
||||
print("Exiting program.")
|
||||
sys.exit(1)
|
||||
|
||||
# Generate facts file
|
||||
exporter.EFGTsvExporter(efg).export(output_dir=output_folder,
|
||||
out_opcodes=target_opcodes)
|
||||
|
||||
# Generate the receiver hash sc_addr.facts
|
||||
try:
|
||||
to_address = debug_trace_receiver(trans_hash)
|
||||
output_path = os.path.join(output_folder, "sc_addr.facts")
|
||||
with open(output_path, "w") as output_file:
|
||||
if to_address:
|
||||
output_file.write(to_address) # Write the 'to' address directly
|
||||
else:
|
||||
output_file.write("No address found")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error processing {filename}: {e.stderr.decode()}")
|
||||
|
||||
end_time = time.time()
|
||||
formatted_time = format_time(end_time - start_time)
|
||||
line_show(number,trans_hash,to_address,formatted_time)
|
||||
|
||||
end_logo()
|
||||
@@ -0,0 +1,8 @@
|
||||
INFO:root:
|
||||
+------------------------------+
|
||||
| TxSpector Detector v0.0.1 |
|
||||
| (c) The Ohio State University |
|
||||
+------------------------------+
|
||||
INFO:root:Reading from '<stdin>'.
|
||||
CRITICAL:root:
|
||||
Interrupted by user
|
||||
@@ -0,0 +1,409 @@
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;CALLVALUE;0
|
||||
6;DUP1;
|
||||
7;ISZERO;0
|
||||
8;PUSH2;16
|
||||
11;JUMPI;1,16
|
||||
16;JUMPDEST;
|
||||
17;POP;0
|
||||
18;PUSH1;4
|
||||
20;CALLDATASIZE;4
|
||||
21;LT;4,4
|
||||
22;PUSH2;54
|
||||
25;JUMPI;0,54
|
||||
26;PUSH1;0
|
||||
28;CALLDATALOAD;36288808125440614087156239300432291571913474525407125660472422305149220814848
|
||||
29;PUSH1;224
|
||||
31;SHR;36288808125440614087156239300432291571913474525407125660472422305149220814848,224
|
||||
32;DUP1;
|
||||
33;PUSH4;1346026703
|
||||
38;EQ;1346026703,1346026703
|
||||
39;PUSH2;59
|
||||
42;JUMPI;1,59
|
||||
59;JUMPDEST;
|
||||
60;PUSH2;67
|
||||
63;PUSH2;99
|
||||
66;JUMP;99
|
||||
99;JUMPDEST;
|
||||
100;PUSH1;0
|
||||
102;DUP1;
|
||||
103;PUSH1;0
|
||||
105;SWAP1;
|
||||
106;SLOAD;0
|
||||
107;SWAP1;
|
||||
108;PUSH2;256
|
||||
111;EXP;0,256
|
||||
112;SWAP1;
|
||||
113;DIV;1,347364959843557222306392896278436377038921993872
|
||||
114;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
135;AND;347364959843557222306392896278436377038921993872,1461501637330902918203684832716283019655932542975
|
||||
136;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
157;AND;347364959843557222306392896278436377038921993872,1461501637330902918203684832716283019655932542975
|
||||
158;PUSH4;4037859285
|
||||
163;PUSH1;64
|
||||
165;MLOAD;128
|
||||
166;DUP2;
|
||||
167;PUSH4;4294967295
|
||||
172;AND;4037859285,4294967295
|
||||
173;PUSH1;224
|
||||
175;SHL;4037859285,224
|
||||
176;DUP2;
|
||||
177;MSTORE;
|
||||
178;PUSH1;4
|
||||
180;ADD;128,4
|
||||
181;PUSH1;32
|
||||
183;PUSH1;64
|
||||
185;MLOAD;128
|
||||
186;DUP1;
|
||||
187;DUP4;
|
||||
188;SUB;128,132
|
||||
189;DUP2;
|
||||
190;PUSH1;0
|
||||
192;DUP8;
|
||||
193;DUP1;
|
||||
194;EXTCODESIZE;375
|
||||
195;ISZERO;375
|
||||
196;DUP1;
|
||||
197;ISZERO;0
|
||||
198;PUSH2;206
|
||||
201;JUMPI;1,206
|
||||
206;JUMPDEST;
|
||||
207;POP;0
|
||||
208;GAS;4397
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;CALLVALUE;0
|
||||
6;DUP1;
|
||||
7;ISZERO;0
|
||||
8;PUSH2;16
|
||||
11;JUMPI;1,16
|
||||
16;JUMPDEST;
|
||||
17;POP;0
|
||||
18;PUSH1;4
|
||||
20;CALLDATASIZE;4
|
||||
21;LT;4,4
|
||||
22;PUSH2;54
|
||||
25;JUMPI;0,54
|
||||
26;PUSH1;0
|
||||
28;CALLDATALOAD;108860470973059015388586700352357298692816548327441413882039699691817989570560
|
||||
29;PUSH1;224
|
||||
31;SHR;108860470973059015388586700352357298692816548327441413882039699691817989570560,224
|
||||
32;DUP1;
|
||||
33;PUSH4;1490037513
|
||||
38;EQ;4037859285,1490037513
|
||||
39;PUSH2;59
|
||||
42;JUMPI;0,59
|
||||
43;DUP1;
|
||||
44;PUSH4;4037859285
|
||||
49;EQ;4037859285,4037859285
|
||||
50;PUSH2;89
|
||||
53;JUMPI;1,89
|
||||
89;JUMPDEST;
|
||||
90;PUSH2;97
|
||||
93;PUSH2;125
|
||||
96;JUMP;125
|
||||
125;JUMPDEST;
|
||||
126;PUSH1;0
|
||||
128;DUP1;
|
||||
129;TIMESTAMP;1694821674
|
||||
130;SWAP1;
|
||||
131;POP;0
|
||||
132;PUSH1;10
|
||||
134;PUSH1;0
|
||||
136;SLOAD;0
|
||||
137;DUP3;
|
||||
138;PUSH2;147
|
||||
141;SWAP2;
|
||||
142;SWAP1;
|
||||
143;PUSH2;212
|
||||
146;JUMP;212
|
||||
212;JUMPDEST;
|
||||
213;PUSH1;0
|
||||
215;PUSH2;223
|
||||
218;DUP3;
|
||||
219;PUSH2;264
|
||||
222;JUMP;264
|
||||
264;JUMPDEST;
|
||||
265;PUSH1;0
|
||||
267;DUP2;
|
||||
268;SWAP1;
|
||||
269;POP;0
|
||||
270;SWAP2;
|
||||
271;SWAP1;
|
||||
272;POP;1694821674
|
||||
273;JUMP;223
|
||||
223;JUMPDEST;
|
||||
224;SWAP2;
|
||||
225;POP;1694821674
|
||||
226;PUSH2;234
|
||||
229;DUP4;
|
||||
230;PUSH2;264
|
||||
233;JUMP;264
|
||||
264;JUMPDEST;
|
||||
265;PUSH1;0
|
||||
267;DUP2;
|
||||
268;SWAP1;
|
||||
269;POP;0
|
||||
270;SWAP2;
|
||||
271;SWAP1;
|
||||
272;POP;1694821657
|
||||
273;JUMP;234
|
||||
234;JUMPDEST;
|
||||
235;SWAP3;
|
||||
236;POP;1694821657
|
||||
237;DUP3;
|
||||
238;DUP3;
|
||||
239;LT;1694821657,1694821674
|
||||
240;ISZERO;0
|
||||
241;PUSH2;253
|
||||
244;JUMPI;1,253
|
||||
253;JUMPDEST;
|
||||
254;DUP3;
|
||||
255;DUP3;
|
||||
256;SUB;1694821657,1694821674
|
||||
257;SWAP1;
|
||||
258;POP;0
|
||||
259;SWAP3;
|
||||
260;SWAP2;
|
||||
261;POP;1694821657
|
||||
262;POP;1694821674
|
||||
263;JUMP;147
|
||||
147;JUMPDEST;
|
||||
148;LT;10,17
|
||||
149;ISZERO;0
|
||||
150;PUSH2;158
|
||||
153;JUMPI;1,158
|
||||
158;JUMPDEST;
|
||||
159;TIMESTAMP;1694821674
|
||||
160;SWAP1;
|
||||
161;POP;1694821674
|
||||
162;PUSH1;1
|
||||
164;SLOAD;1
|
||||
165;SWAP2;
|
||||
166;POP;0
|
||||
167;POP;1694821674
|
||||
168;SWAP1;
|
||||
169;JUMP;97
|
||||
97;JUMPDEST;
|
||||
98;PUSH1;64
|
||||
100;MLOAD;128
|
||||
101;PUSH2;110
|
||||
104;SWAP2;
|
||||
105;SWAP1;
|
||||
106;PUSH2;185
|
||||
109;JUMP;185
|
||||
185;JUMPDEST;
|
||||
186;PUSH1;0
|
||||
188;PUSH1;32
|
||||
190;DUP3;
|
||||
191;ADD;32,128
|
||||
192;SWAP1;
|
||||
193;POP;0
|
||||
194;PUSH2;206
|
||||
197;PUSH1;0
|
||||
199;DUP4;
|
||||
200;ADD;0,128
|
||||
201;DUP5;
|
||||
202;PUSH2;170
|
||||
205;JUMP;170
|
||||
170;JUMPDEST;
|
||||
171;PUSH2;179
|
||||
174;DUP2;
|
||||
175;PUSH2;264
|
||||
178;JUMP;264
|
||||
264;JUMPDEST;
|
||||
265;PUSH1;0
|
||||
267;DUP2;
|
||||
268;SWAP1;
|
||||
269;POP;0
|
||||
270;SWAP2;
|
||||
271;SWAP1;
|
||||
272;POP;12
|
||||
273;JUMP;179
|
||||
179;JUMPDEST;
|
||||
180;DUP3;
|
||||
181;MSTORE;
|
||||
182;POP;12
|
||||
183;POP;128
|
||||
184;JUMP;206
|
||||
206;JUMPDEST;
|
||||
207;SWAP3;
|
||||
208;SWAP2;
|
||||
209;POP;12
|
||||
210;POP;128
|
||||
211;JUMP;110
|
||||
110;JUMPDEST;
|
||||
111;PUSH1;64
|
||||
113;MLOAD;128
|
||||
114;DUP1;
|
||||
115;SWAP2;
|
||||
116;SUB;128,160
|
||||
117;SWAP1;
|
||||
118;RETURN;32,128
|
||||
209;CALL;1,0
|
||||
210;ISZERO;1
|
||||
211;DUP1;
|
||||
212;ISZERO;0
|
||||
213;PUSH2;226
|
||||
216;JUMPI;1,226
|
||||
226;JUMPDEST;
|
||||
227;POP;0
|
||||
228;POP;132
|
||||
229;POP;4037859285
|
||||
230;POP;347364959843557222306392896278436377038921993872
|
||||
231;PUSH1;64
|
||||
233;MLOAD;128
|
||||
234;RETURNDATASIZE;32
|
||||
235;PUSH1;31
|
||||
237;NOT;31
|
||||
238;PUSH1;31
|
||||
240;DUP3;
|
||||
241;ADD;31,32
|
||||
242;AND;115792089237316195423570985008687907853269984665640564039457584007913129639904,63
|
||||
243;DUP3;
|
||||
244;ADD;32,128
|
||||
245;DUP1;
|
||||
246;PUSH1;64
|
||||
248;MSTORE;
|
||||
249;POP;160
|
||||
250;DUP2;
|
||||
251;ADD;32,128
|
||||
252;SWAP1;
|
||||
253;PUSH2;262
|
||||
256;SWAP2;
|
||||
257;SWAP1;
|
||||
258;PUSH2;379
|
||||
261;JUMP;379
|
||||
379;JUMPDEST;
|
||||
380;PUSH1;0
|
||||
382;PUSH1;32
|
||||
384;DUP3;
|
||||
385;DUP5;
|
||||
386;SUB;128,160
|
||||
387;SLT;32,32
|
||||
388;ISZERO;0
|
||||
389;PUSH2;397
|
||||
392;JUMPI;1,397
|
||||
397;JUMPDEST;
|
||||
398;PUSH1;0
|
||||
400;PUSH2;411
|
||||
403;DUP5;
|
||||
404;DUP3;
|
||||
405;DUP6;
|
||||
406;ADD;0,128
|
||||
407;PUSH2;358
|
||||
410;JUMP;358
|
||||
358;JUMPDEST;
|
||||
359;PUSH1;0
|
||||
361;DUP2;
|
||||
362;MLOAD;12
|
||||
363;SWAP1;
|
||||
364;POP;0
|
||||
365;PUSH2;373
|
||||
368;DUP2;
|
||||
369;PUSH2;582
|
||||
372;JUMP;582
|
||||
582;JUMPDEST;
|
||||
583;PUSH2;591
|
||||
586;DUP2;
|
||||
587;PUSH2;536
|
||||
590;JUMP;536
|
||||
536;JUMPDEST;
|
||||
537;PUSH1;0
|
||||
539;DUP2;
|
||||
540;SWAP1;
|
||||
541;POP;0
|
||||
542;SWAP2;
|
||||
543;SWAP1;
|
||||
544;POP;12
|
||||
545;JUMP;591
|
||||
591;JUMPDEST;
|
||||
592;DUP2;
|
||||
593;EQ;12,12
|
||||
594;PUSH2;602
|
||||
597;JUMPI;1,602
|
||||
602;JUMPDEST;
|
||||
603;POP;12
|
||||
604;JUMP;373
|
||||
373;JUMPDEST;
|
||||
374;SWAP3;
|
||||
375;SWAP2;
|
||||
376;POP;160
|
||||
377;POP;128
|
||||
378;JUMP;411
|
||||
411;JUMPDEST;
|
||||
412;SWAP2;
|
||||
413;POP;0
|
||||
414;POP;0
|
||||
415;SWAP3;
|
||||
416;SWAP2;
|
||||
417;POP;160
|
||||
418;POP;128
|
||||
419;JUMP;262
|
||||
262;JUMPDEST;
|
||||
263;SWAP1;
|
||||
264;POP;0
|
||||
265;PUSH32;67711100502772707642132795203347290566490950481849279840619261695477678726252
|
||||
298;DUP2;
|
||||
299;PUSH1;64
|
||||
301;MLOAD;160
|
||||
302;PUSH2;311
|
||||
305;SWAP2;
|
||||
306;SWAP1;
|
||||
307;PUSH2;477
|
||||
310;JUMP;477
|
||||
477;JUMPDEST;
|
||||
478;PUSH1;0
|
||||
480;PUSH1;32
|
||||
482;DUP3;
|
||||
483;ADD;32,160
|
||||
484;SWAP1;
|
||||
485;POP;0
|
||||
486;PUSH2;498
|
||||
489;PUSH1;0
|
||||
491;DUP4;
|
||||
492;ADD;0,160
|
||||
493;DUP5;
|
||||
494;PUSH2;435
|
||||
497;JUMP;435
|
||||
435;JUMPDEST;
|
||||
436;PUSH2;444
|
||||
439;DUP2;
|
||||
440;PUSH2;536
|
||||
443;JUMP;536
|
||||
536;JUMPDEST;
|
||||
537;PUSH1;0
|
||||
539;DUP2;
|
||||
540;SWAP1;
|
||||
541;POP;0
|
||||
542;SWAP2;
|
||||
543;SWAP1;
|
||||
544;POP;12
|
||||
545;JUMP;444
|
||||
444;JUMPDEST;
|
||||
445;DUP3;
|
||||
446;MSTORE;
|
||||
447;POP;12
|
||||
448;POP;160
|
||||
449;JUMP;498
|
||||
498;JUMPDEST;
|
||||
499;SWAP3;
|
||||
500;SWAP2;
|
||||
501;POP;12
|
||||
502;POP;160
|
||||
503;JUMP;311
|
||||
311;JUMPDEST;
|
||||
312;PUSH1;64
|
||||
314;MLOAD;160
|
||||
315;DUP1;
|
||||
316;SWAP2;
|
||||
317;SUB;160,192
|
||||
318;SWAP1;
|
||||
319;LOG1;67711100502772707642132795203347290566490950481849279840619261695477678726252,32,160
|
||||
320;POP;12
|
||||
321;JUMP;67
|
||||
67;JUMPDEST;
|
||||
68;STOP;
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: analyze.sh trace_file output_file"
|
||||
exit
|
||||
fi
|
||||
set -x
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
|
||||
# Here are the opcodes that we need to detect attacks, you can add or delete them
|
||||
$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD CALL DELEGATE CALLCODE STATICCALL -n -t $2 $1
|
||||
#$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD CALL DELEGATE CALLCODE STATICCALL -t $2 $1
|
||||
#$DIR/decompile_geth -o CREATE BALANCE CALLER CALLVALUE STOP RETURN REVERT ORIGIN CALLDATALOAD EQ TIMESTAMP NUMBER DIFFICULTY COINBASE BLOCKHASH GASLIMIT EXTCODESIZE SELFDESTRUCT JUMPI JUMP JUMPDEST SSTORE SLOAD MSTORE MLAOD CALL DELEGATE CALLCODE STATICCALL PUSH1 -n -t $2 $1
|
||||
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# Standard lib imports
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
# Prepend .. to $PATH so the project modules can be imported below
|
||||
|
||||
# 用于将以太坊虚拟机(EVM)字节码反汇编为三地址代码进行程序分析
|
||||
|
||||
|
||||
src_path = join(dirname(abspath(__file__)), "..")
|
||||
sys.path.insert(0, src_path)
|
||||
|
||||
# Local project imports
|
||||
import src.exporter as exporter
|
||||
import src.tac_efg as tac_efg
|
||||
import src.settings as settings
|
||||
|
||||
# Version string to display with -v
|
||||
VERSION = """\
|
||||
+------------------------------+
|
||||
| TxSpector Detector v0.0.1 |
|
||||
| (c) The Ohio State University |
|
||||
+------------------------------+\
|
||||
"""
|
||||
|
||||
|
||||
# Define a version() function in case we want dynamic version strings later
|
||||
def version():
|
||||
return VERSION
|
||||
|
||||
|
||||
# Configure argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description="An EVM bytecode disassembly that generates"
|
||||
"three-address code for program analysis.")
|
||||
|
||||
parser.add_argument("-t",
|
||||
"--tsv",
|
||||
nargs="?",
|
||||
const="",
|
||||
metavar="DIR",
|
||||
default=None,
|
||||
help="generate tab-separated .facts files for Souffle "
|
||||
"and write files to the specified directory, which "
|
||||
"will be recursively created if it does not exist "
|
||||
"(current working directory by default).")
|
||||
|
||||
parser.add_argument("-o",
|
||||
"--opcodes",
|
||||
nargs="*",
|
||||
default=[],
|
||||
metavar="OPCODE",
|
||||
help="If producing tsv output, also include relations "
|
||||
"encoding all occurrences of the specified "
|
||||
"list of opcodes. Opcode X will be stored in "
|
||||
"op_X.facts.")
|
||||
|
||||
parser.add_argument("-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="emit verbose debug output to stderr.")
|
||||
|
||||
parser.add_argument("-vv",
|
||||
"--prolix",
|
||||
action="store_true",
|
||||
help="higher verbosity level, including extra debug "
|
||||
"messages from dataflow analysis and elsewhere.")
|
||||
|
||||
parser.add_argument("-n",
|
||||
"--no_out",
|
||||
action="store_true",
|
||||
help="do not output decompiled graph.")
|
||||
|
||||
parser.add_argument("-V",
|
||||
"--version",
|
||||
action="store_true",
|
||||
help="show program's version number and exit.")
|
||||
|
||||
parser.add_argument("infile",
|
||||
nargs="?",
|
||||
type=argparse.FileType("r"),
|
||||
default=sys.stdin,
|
||||
help="file from which decompiler input should be read "
|
||||
"(stdin by default).")
|
||||
|
||||
parser.add_argument("outfile",
|
||||
nargs="?",
|
||||
type=argparse.FileType("w"),
|
||||
default=sys.stdout,
|
||||
help="file to which decompiler output should be written "
|
||||
"(stdout by default).")
|
||||
|
||||
# Parse the arguments.
|
||||
args = parser.parse_args()
|
||||
|
||||
# Set up logger, with appropriate log level depending on verbosity.
|
||||
log_level = logging.WARNING
|
||||
if args.prolix:
|
||||
log_level = logging.DEBUG
|
||||
elif args.verbose:
|
||||
log_level = logging.INFO
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)
|
||||
|
||||
# Handle --version
|
||||
if args.version:
|
||||
sys.exit(1)
|
||||
|
||||
# Always show version for log_level >= LOW
|
||||
logging.info("\n" + version())
|
||||
|
||||
# Build TAC EFG from input file
|
||||
try:
|
||||
logging.info("Reading from '%s'.", args.infile.name)
|
||||
efg = tac_efg.TACGraph.from_opcode(args.infile)
|
||||
logging.info("Initial EFG generation completed.")
|
||||
|
||||
# Catch a Control-C and exit with UNIX failure status 1
|
||||
except KeyboardInterrupt:
|
||||
logging.critical("\nInterrupted by user")
|
||||
sys.exit(1)
|
||||
|
||||
# Generate output using the requested exporter(s)
|
||||
# blocks, textual version of efg
|
||||
if not args.no_out:
|
||||
logging.info("Writing string output.")
|
||||
print(exporter.EFGStringExporter(efg).export(), file=args.outfile)
|
||||
|
||||
# Generate facts file
|
||||
if args.tsv is not None:
|
||||
logging.info("Writing TSV output.")
|
||||
exporter.EFGTsvExporter(efg).export(output_dir=args.tsv,
|
||||
out_opcodes=args.opcodes)
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,735 @@
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;4
|
||||
8;LT;4,4
|
||||
9;PUSH2;67
|
||||
12;JUMPI;0,67
|
||||
13;PUSH1;0
|
||||
15;CALLDATALOAD;71634460638095550392100722737204534410467526076113882865062923086234512261120
|
||||
16;PUSH1;224
|
||||
18;SHR;71634460638095550392100722737204534410467526076113882865062923086234512261120,224
|
||||
19;DUP1;
|
||||
20;PUSH4;302407648
|
||||
25;EQ;2657069820,302407648
|
||||
26;PUSH2;288
|
||||
29;JUMPI;0,288
|
||||
30;DUP1;
|
||||
31;PUSH4;2467045379
|
||||
36;EQ;2657069820,2467045379
|
||||
37;PUSH2;331
|
||||
40;JUMPI;0,331
|
||||
41;DUP1;
|
||||
42;PUSH4;2657069820
|
||||
47;EQ;2657069820,2657069820
|
||||
48;PUSH2;396
|
||||
51;JUMPI;1,396
|
||||
396;JUMPDEST;
|
||||
397;PUSH2;404
|
||||
400;PUSH2;518
|
||||
403;JUMP;518
|
||||
518;JUMPDEST;
|
||||
519;PUSH8;1000000000000000000
|
||||
528;CALLVALUE;1000000000000000000
|
||||
529;LT;1000000000000000000,1000000000000000000
|
||||
530;ISZERO;0
|
||||
531;PUSH2;615
|
||||
534;JUMPI;1,615
|
||||
615;JUMPDEST;
|
||||
616;PUSH1;0
|
||||
618;DUP1;
|
||||
619;SLOAD;0
|
||||
620;SWAP1;
|
||||
621;PUSH2;256
|
||||
624;EXP;0,256
|
||||
625;SWAP1;
|
||||
626;DIV;1,1148000406285257731517353366331601018510389802278
|
||||
627;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
648;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
649;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
670;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
671;PUSH4;3504541104
|
||||
676;PUSH8;1000000000000000000
|
||||
685;PUSH1;64
|
||||
687;MLOAD;128
|
||||
688;DUP3;
|
||||
689;PUSH4;4294967295
|
||||
694;AND;3504541104,4294967295
|
||||
695;PUSH1;224
|
||||
697;SHL;3504541104,224
|
||||
698;DUP2;
|
||||
699;MSTORE;
|
||||
700;PUSH1;4
|
||||
702;ADD;128,4
|
||||
703;PUSH1;0
|
||||
705;PUSH1;64
|
||||
707;MLOAD;128
|
||||
708;DUP1;
|
||||
709;DUP4;
|
||||
710;SUB;128,132
|
||||
711;DUP2;
|
||||
712;DUP6;
|
||||
713;DUP9;
|
||||
714;DUP1;
|
||||
715;EXTCODESIZE;1063
|
||||
716;ISZERO;1063
|
||||
717;DUP1;
|
||||
718;ISZERO;0
|
||||
719;PUSH2;727
|
||||
722;JUMPI;1,727
|
||||
727;JUMPDEST;
|
||||
728;POP;0
|
||||
729;GAS;90637
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;4
|
||||
8;LT;4,4
|
||||
9;PUSH2;78
|
||||
12;JUMPI;0,78
|
||||
13;PUSH1;0
|
||||
15;CALLDATALOAD;94482241256677223720308674365448432550660581809977780367718865401173155774464
|
||||
16;PUSH1;224
|
||||
18;SHR;94482241256677223720308674365448432550660581809977780367718865401173155774464,224
|
||||
19;DUP1;
|
||||
20;PUSH4;669136355
|
||||
25;EQ;3504541104,669136355
|
||||
26;PUSH2;106
|
||||
29;JUMPI;0,106
|
||||
30;DUP1;
|
||||
31;PUSH4;773487949
|
||||
36;EQ;3504541104,773487949
|
||||
37;PUSH2;207
|
||||
40;JUMPI;0,207
|
||||
41;DUP1;
|
||||
42;PUSH4;1872738698
|
||||
47;EQ;3504541104,1872738698
|
||||
48;PUSH2;266
|
||||
51;JUMPI;0,266
|
||||
52;DUP1;
|
||||
53;PUSH4;3504541104
|
||||
58;EQ;3504541104,3504541104
|
||||
59;PUSH2;309
|
||||
62;JUMPI;1,309
|
||||
309;JUMPDEST;
|
||||
310;PUSH2;317
|
||||
313;PUSH2;843
|
||||
316;JUMP;843
|
||||
843;JUMPDEST;
|
||||
844;CALLVALUE;1000000000000000000
|
||||
845;PUSH1;0
|
||||
847;DUP1;
|
||||
848;CALLER;354605892528587159537909970736718245026464010688
|
||||
849;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
870;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
871;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
892;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
893;DUP2;
|
||||
894;MSTORE;
|
||||
895;PUSH1;32
|
||||
897;ADD;0,32
|
||||
898;SWAP1;
|
||||
899;DUP2;
|
||||
900;MSTORE;
|
||||
901;PUSH1;32
|
||||
903;ADD;32,32
|
||||
904;PUSH1;0
|
||||
906;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
907;PUSH1;0
|
||||
909;DUP3;
|
||||
910;DUP3;
|
||||
911;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
912;ADD;1000000000000000000,0
|
||||
913;SWAP3;
|
||||
914;POP;1000000000000000000
|
||||
915;POP;0
|
||||
916;DUP2;
|
||||
917;SWAP1;
|
||||
918;SSTORE;1000000000000000000,71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
919;POP;1000000000000000000
|
||||
920;CALLVALUE;1000000000000000000
|
||||
921;PUSH1;1
|
||||
923;PUSH1;0
|
||||
925;DUP3;
|
||||
926;DUP3;
|
||||
927;SLOAD;1
|
||||
928;ADD;1000000000000000000,1000000000000000000
|
||||
929;SWAP3;
|
||||
930;POP;1000000000000000000
|
||||
931;POP;0
|
||||
932;DUP2;
|
||||
933;SWAP1;
|
||||
934;SSTORE;2000000000000000000,1
|
||||
935;POP;2000000000000000000
|
||||
936;JUMP;317
|
||||
317;JUMPDEST;
|
||||
318;STOP;
|
||||
730;CALL;1,0
|
||||
731;ISZERO;1
|
||||
732;DUP1;
|
||||
733;ISZERO;0
|
||||
734;PUSH2;747
|
||||
737;JUMPI;1,747
|
||||
747;JUMPDEST;
|
||||
748;POP;0
|
||||
749;POP;132
|
||||
750;POP;1000000000000000000
|
||||
751;POP;3504541104
|
||||
752;POP;1148000406285257731517353366331601018510389802278
|
||||
753;PUSH1;0
|
||||
755;DUP1;
|
||||
756;SLOAD;0
|
||||
757;SWAP1;
|
||||
758;PUSH2;256
|
||||
761;EXP;0,256
|
||||
762;SWAP1;
|
||||
763;DIV;1,1148000406285257731517353366331601018510389802278
|
||||
764;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
785;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
786;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
807;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
808;PUSH4;773487949
|
||||
813;PUSH8;1000000000000000000
|
||||
822;PUSH1;64
|
||||
824;MLOAD;128
|
||||
825;DUP3;
|
||||
826;PUSH4;4294967295
|
||||
831;AND;773487949,4294967295
|
||||
832;PUSH1;224
|
||||
834;SHL;773487949,224
|
||||
835;DUP2;
|
||||
836;MSTORE;
|
||||
837;PUSH1;4
|
||||
839;ADD;128,4
|
||||
840;DUP1;
|
||||
841;DUP3;
|
||||
842;DUP2;
|
||||
843;MSTORE;
|
||||
844;PUSH1;32
|
||||
846;ADD;132,32
|
||||
847;SWAP2;
|
||||
848;POP;1000000000000000000
|
||||
849;POP;132
|
||||
850;PUSH1;0
|
||||
852;PUSH1;64
|
||||
854;MLOAD;128
|
||||
855;DUP1;
|
||||
856;DUP4;
|
||||
857;SUB;128,164
|
||||
858;DUP2;
|
||||
859;PUSH1;0
|
||||
861;DUP8;
|
||||
862;DUP1;
|
||||
863;EXTCODESIZE;1063
|
||||
864;ISZERO;1063
|
||||
865;DUP1;
|
||||
866;ISZERO;0
|
||||
867;PUSH2;875
|
||||
870;JUMPI;1,875
|
||||
875;JUMPDEST;
|
||||
876;POP;0
|
||||
877;GAS;54621
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;36
|
||||
8;LT;4,36
|
||||
9;PUSH2;78
|
||||
12;JUMPI;0,78
|
||||
13;PUSH1;0
|
||||
15;CALLDATALOAD;20853193852723734048814770637610870652489083209607696777694672748161695528627
|
||||
16;PUSH1;224
|
||||
18;SHR;20853193852723734048814770637610870652489083209607696777694672748161695528627,224
|
||||
19;DUP1;
|
||||
20;PUSH4;669136355
|
||||
25;EQ;773487949,669136355
|
||||
26;PUSH2;106
|
||||
29;JUMPI;0,106
|
||||
30;DUP1;
|
||||
31;PUSH4;773487949
|
||||
36;EQ;773487949,773487949
|
||||
37;PUSH2;207
|
||||
40;JUMPI;1,207
|
||||
207;JUMPDEST;
|
||||
208;CALLVALUE;0
|
||||
209;DUP1;
|
||||
210;ISZERO;0
|
||||
211;PUSH2;219
|
||||
214;JUMPI;1,219
|
||||
219;JUMPDEST;
|
||||
220;POP;0
|
||||
221;PUSH2;264
|
||||
224;PUSH1;4
|
||||
226;DUP1;
|
||||
227;CALLDATASIZE;36
|
||||
228;SUB;4,36
|
||||
229;PUSH1;32
|
||||
231;DUP2;
|
||||
232;LT;32,32
|
||||
233;ISZERO;0
|
||||
234;PUSH2;242
|
||||
237;JUMPI;1,242
|
||||
242;JUMPDEST;
|
||||
243;DUP2;
|
||||
244;ADD;32,4
|
||||
245;SWAP1;
|
||||
246;DUP1;
|
||||
247;DUP1;
|
||||
248;CALLDATALOAD;1000000000000000000
|
||||
249;SWAP1;
|
||||
250;PUSH1;32
|
||||
252;ADD;4,32
|
||||
253;SWAP1;
|
||||
254;SWAP3;
|
||||
255;SWAP2;
|
||||
256;SWAP1;
|
||||
257;POP;36
|
||||
258;POP;4
|
||||
259;POP;36
|
||||
260;PUSH2;444
|
||||
263;JUMP;444
|
||||
444;JUMPDEST;
|
||||
445;DUP1;
|
||||
446;PUSH1;0
|
||||
448;DUP1;
|
||||
449;CALLER;354605892528587159537909970736718245026464010688
|
||||
450;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
471;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
472;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
493;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
494;DUP2;
|
||||
495;MSTORE;
|
||||
496;PUSH1;32
|
||||
498;ADD;0,32
|
||||
499;SWAP1;
|
||||
500;DUP2;
|
||||
501;MSTORE;
|
||||
502;PUSH1;32
|
||||
504;ADD;32,32
|
||||
505;PUSH1;0
|
||||
507;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
508;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
509;LT;1000000000000000000,1000000000000000000
|
||||
510;ISZERO;0
|
||||
511;PUSH2;519
|
||||
514;JUMPI;1,519
|
||||
519;JUMPDEST;
|
||||
520;PUSH1;0
|
||||
522;CALLER;354605892528587159537909970736718245026464010688
|
||||
523;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
544;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
545;DUP3;
|
||||
546;PUSH1;64
|
||||
548;MLOAD;128
|
||||
549;DUP1;
|
||||
550;PUSH1;0
|
||||
552;ADD;128,0
|
||||
553;SWAP1;
|
||||
554;POP;128
|
||||
555;PUSH1;0
|
||||
557;PUSH1;64
|
||||
559;MLOAD;128
|
||||
560;DUP1;
|
||||
561;DUP4;
|
||||
562;SUB;128,128
|
||||
563;DUP2;
|
||||
564;DUP6;
|
||||
565;DUP8;
|
||||
566;GAS;51884
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;0
|
||||
8;LT;4,0
|
||||
9;PUSH2;67
|
||||
12;JUMPI;1,67
|
||||
67;JUMPDEST;
|
||||
68;JUMPDEST;
|
||||
69;PUSH8;1000000000000000000
|
||||
78;PUSH1;0
|
||||
80;DUP1;
|
||||
81;SLOAD;0
|
||||
82;SWAP1;
|
||||
83;PUSH2;256
|
||||
86;EXP;0,256
|
||||
87;SWAP1;
|
||||
88;DIV;1,1148000406285257731517353366331601018510389802278
|
||||
89;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
110;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
111;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
132;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
133;BALANCE;1000000000000000000
|
||||
134;LT;1000000000000000000,1000000000000000000
|
||||
135;PUSH2;286
|
||||
138;JUMPI;0,286
|
||||
139;PUSH1;0
|
||||
141;DUP1;
|
||||
142;SLOAD;0
|
||||
143;SWAP1;
|
||||
144;PUSH2;256
|
||||
147;EXP;0,256
|
||||
148;SWAP1;
|
||||
149;DIV;1,1148000406285257731517353366331601018510389802278
|
||||
150;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
171;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
172;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
193;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
194;PUSH4;773487949
|
||||
199;PUSH8;1000000000000000000
|
||||
208;PUSH1;64
|
||||
210;MLOAD;128
|
||||
211;DUP3;
|
||||
212;PUSH4;4294967295
|
||||
217;AND;773487949,4294967295
|
||||
218;PUSH1;224
|
||||
220;SHL;773487949,224
|
||||
221;DUP2;
|
||||
222;MSTORE;
|
||||
223;PUSH1;4
|
||||
225;ADD;128,4
|
||||
226;DUP1;
|
||||
227;DUP3;
|
||||
228;DUP2;
|
||||
229;MSTORE;
|
||||
230;PUSH1;32
|
||||
232;ADD;132,32
|
||||
233;SWAP2;
|
||||
234;POP;1000000000000000000
|
||||
235;POP;132
|
||||
236;PUSH1;0
|
||||
238;PUSH1;64
|
||||
240;MLOAD;128
|
||||
241;DUP1;
|
||||
242;DUP4;
|
||||
243;SUB;128,164
|
||||
244;DUP2;
|
||||
245;PUSH1;0
|
||||
247;DUP8;
|
||||
248;DUP1;
|
||||
249;EXTCODESIZE;1063
|
||||
250;ISZERO;1063
|
||||
251;DUP1;
|
||||
252;ISZERO;0
|
||||
253;PUSH2;261
|
||||
256;JUMPI;1,261
|
||||
261;JUMPDEST;
|
||||
262;POP;0
|
||||
263;GAS;40551
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;36
|
||||
8;LT;4,36
|
||||
9;PUSH2;78
|
||||
12;JUMPI;0,78
|
||||
13;PUSH1;0
|
||||
15;CALLDATALOAD;20853193852723734048814770637610870652489083209607696777694672748161695528627
|
||||
16;PUSH1;224
|
||||
18;SHR;20853193852723734048814770637610870652489083209607696777694672748161695528627,224
|
||||
19;DUP1;
|
||||
20;PUSH4;669136355
|
||||
25;EQ;773487949,669136355
|
||||
26;PUSH2;106
|
||||
29;JUMPI;0,106
|
||||
30;DUP1;
|
||||
31;PUSH4;773487949
|
||||
36;EQ;773487949,773487949
|
||||
37;PUSH2;207
|
||||
40;JUMPI;1,207
|
||||
207;JUMPDEST;
|
||||
208;CALLVALUE;0
|
||||
209;DUP1;
|
||||
210;ISZERO;0
|
||||
211;PUSH2;219
|
||||
214;JUMPI;1,219
|
||||
219;JUMPDEST;
|
||||
220;POP;0
|
||||
221;PUSH2;264
|
||||
224;PUSH1;4
|
||||
226;DUP1;
|
||||
227;CALLDATASIZE;36
|
||||
228;SUB;4,36
|
||||
229;PUSH1;32
|
||||
231;DUP2;
|
||||
232;LT;32,32
|
||||
233;ISZERO;0
|
||||
234;PUSH2;242
|
||||
237;JUMPI;1,242
|
||||
242;JUMPDEST;
|
||||
243;DUP2;
|
||||
244;ADD;32,4
|
||||
245;SWAP1;
|
||||
246;DUP1;
|
||||
247;DUP1;
|
||||
248;CALLDATALOAD;1000000000000000000
|
||||
249;SWAP1;
|
||||
250;PUSH1;32
|
||||
252;ADD;4,32
|
||||
253;SWAP1;
|
||||
254;SWAP3;
|
||||
255;SWAP2;
|
||||
256;SWAP1;
|
||||
257;POP;36
|
||||
258;POP;4
|
||||
259;POP;36
|
||||
260;PUSH2;444
|
||||
263;JUMP;444
|
||||
444;JUMPDEST;
|
||||
445;DUP1;
|
||||
446;PUSH1;0
|
||||
448;DUP1;
|
||||
449;CALLER;354605892528587159537909970736718245026464010688
|
||||
450;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
471;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
472;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
493;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
494;DUP2;
|
||||
495;MSTORE;
|
||||
496;PUSH1;32
|
||||
498;ADD;0,32
|
||||
499;SWAP1;
|
||||
500;DUP2;
|
||||
501;MSTORE;
|
||||
502;PUSH1;32
|
||||
504;ADD;32,32
|
||||
505;PUSH1;0
|
||||
507;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
508;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
509;LT;1000000000000000000,1000000000000000000
|
||||
510;ISZERO;0
|
||||
511;PUSH2;519
|
||||
514;JUMPI;1,519
|
||||
519;JUMPDEST;
|
||||
520;PUSH1;0
|
||||
522;CALLER;354605892528587159537909970736718245026464010688
|
||||
523;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
544;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
545;DUP3;
|
||||
546;PUSH1;64
|
||||
548;MLOAD;128
|
||||
549;DUP1;
|
||||
550;PUSH1;0
|
||||
552;ADD;128,0
|
||||
553;SWAP1;
|
||||
554;POP;128
|
||||
555;PUSH1;0
|
||||
557;PUSH1;64
|
||||
559;MLOAD;128
|
||||
560;DUP1;
|
||||
561;DUP4;
|
||||
562;SUB;128,128
|
||||
563;DUP2;
|
||||
564;DUP6;
|
||||
565;DUP8;
|
||||
566;GAS;38034
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;PUSH1;4
|
||||
7;CALLDATASIZE;0
|
||||
8;LT;4,0
|
||||
9;PUSH2;67
|
||||
12;JUMPI;1,67
|
||||
67;JUMPDEST;
|
||||
68;JUMPDEST;
|
||||
69;PUSH8;1000000000000000000
|
||||
78;PUSH1;0
|
||||
80;DUP1;
|
||||
81;SLOAD;0
|
||||
82;SWAP1;
|
||||
83;PUSH2;256
|
||||
86;EXP;0,256
|
||||
87;SWAP1;
|
||||
88;DIV;1,1148000406285257731517353366331601018510389802278
|
||||
89;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
110;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
111;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
132;AND;1148000406285257731517353366331601018510389802278,1461501637330902918203684832716283019655932542975
|
||||
133;BALANCE;0
|
||||
134;LT;1000000000000000000,0
|
||||
135;PUSH2;286
|
||||
138;JUMPI;1,286
|
||||
286;JUMPDEST;
|
||||
287;STOP;
|
||||
567;CALL;1,0
|
||||
568;SWAP3;
|
||||
569;POP;354605892528587159537909970736718245026464010688
|
||||
570;POP;128
|
||||
571;POP;1000000000000000000
|
||||
572;RETURNDATASIZE;0
|
||||
573;DUP1;
|
||||
574;PUSH1;0
|
||||
576;DUP2;
|
||||
577;EQ;0,0
|
||||
578;PUSH2;615
|
||||
581;JUMPI;1,615
|
||||
615;JUMPDEST;
|
||||
616;PUSH1;96
|
||||
618;SWAP2;
|
||||
619;POP;0
|
||||
620;JUMPDEST;
|
||||
621;POP;0
|
||||
622;POP;96
|
||||
623;SWAP1;
|
||||
624;POP;0
|
||||
625;DUP1;
|
||||
626;PUSH2;739
|
||||
629;JUMPI;1,739
|
||||
739;JUMPDEST;
|
||||
740;DUP2;
|
||||
741;PUSH1;0
|
||||
743;DUP1;
|
||||
744;CALLER;354605892528587159537909970736718245026464010688
|
||||
745;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
766;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
767;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
788;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
789;DUP2;
|
||||
790;MSTORE;
|
||||
791;PUSH1;32
|
||||
793;ADD;0,32
|
||||
794;SWAP1;
|
||||
795;DUP2;
|
||||
796;MSTORE;
|
||||
797;PUSH1;32
|
||||
799;ADD;32,32
|
||||
800;PUSH1;0
|
||||
802;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
803;PUSH1;0
|
||||
805;DUP3;
|
||||
806;DUP3;
|
||||
807;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
808;SUB;1000000000000000000,1000000000000000000
|
||||
809;SWAP3;
|
||||
810;POP;1000000000000000000
|
||||
811;POP;0
|
||||
812;DUP2;
|
||||
813;SWAP1;
|
||||
814;SSTORE;0,71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
815;POP;0
|
||||
816;DUP2;
|
||||
817;PUSH1;1
|
||||
819;PUSH1;0
|
||||
821;DUP3;
|
||||
822;DUP3;
|
||||
823;SLOAD;1
|
||||
824;SUB;1000000000000000000,2000000000000000000
|
||||
825;SWAP3;
|
||||
826;POP;1000000000000000000
|
||||
827;POP;0
|
||||
828;DUP2;
|
||||
829;SWAP1;
|
||||
830;SSTORE;1000000000000000000,1
|
||||
831;POP;1000000000000000000
|
||||
832;POP;1
|
||||
833;POP;1000000000000000000
|
||||
834;JUMP;264
|
||||
264;JUMPDEST;
|
||||
265;STOP;
|
||||
264;CALL;1,0
|
||||
265;ISZERO;1
|
||||
266;DUP1;
|
||||
267;ISZERO;0
|
||||
268;PUSH2;281
|
||||
271;JUMPI;1,281
|
||||
281;JUMPDEST;
|
||||
282;POP;0
|
||||
283;POP;164
|
||||
284;POP;773487949
|
||||
285;POP;1148000406285257731517353366331601018510389802278
|
||||
286;JUMPDEST;
|
||||
287;STOP;
|
||||
567;CALL;1,0
|
||||
568;SWAP3;
|
||||
569;POP;354605892528587159537909970736718245026464010688
|
||||
570;POP;128
|
||||
571;POP;1000000000000000000
|
||||
572;RETURNDATASIZE;0
|
||||
573;DUP1;
|
||||
574;PUSH1;0
|
||||
576;DUP2;
|
||||
577;EQ;0,0
|
||||
578;PUSH2;615
|
||||
581;JUMPI;1,615
|
||||
615;JUMPDEST;
|
||||
616;PUSH1;96
|
||||
618;SWAP2;
|
||||
619;POP;0
|
||||
620;JUMPDEST;
|
||||
621;POP;0
|
||||
622;POP;96
|
||||
623;SWAP1;
|
||||
624;POP;0
|
||||
625;DUP1;
|
||||
626;PUSH2;739
|
||||
629;JUMPI;1,739
|
||||
739;JUMPDEST;
|
||||
740;DUP2;
|
||||
741;PUSH1;0
|
||||
743;DUP1;
|
||||
744;CALLER;354605892528587159537909970736718245026464010688
|
||||
745;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
766;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
767;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
788;AND;354605892528587159537909970736718245026464010688,1461501637330902918203684832716283019655932542975
|
||||
789;DUP2;
|
||||
790;MSTORE;
|
||||
791;PUSH1;32
|
||||
793;ADD;0,32
|
||||
794;SWAP1;
|
||||
795;DUP2;
|
||||
796;MSTORE;
|
||||
797;PUSH1;32
|
||||
799;ADD;32,32
|
||||
800;PUSH1;0
|
||||
802;SHA3;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
803;PUSH1;0
|
||||
805;DUP3;
|
||||
806;DUP3;
|
||||
807;SLOAD;71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
808;SUB;1000000000000000000,0
|
||||
809;SWAP3;
|
||||
810;POP;1000000000000000000
|
||||
811;POP;0
|
||||
812;DUP2;
|
||||
813;SWAP1;
|
||||
814;SSTORE;115792089237316195423570985008687907853269984665640564039456584007913129639936,71705274935761785479379189425703652296799616776219122606543621932795362146736
|
||||
815;POP;115792089237316195423570985008687907853269984665640564039456584007913129639936
|
||||
816;DUP2;
|
||||
817;PUSH1;1
|
||||
819;PUSH1;0
|
||||
821;DUP3;
|
||||
822;DUP3;
|
||||
823;SLOAD;1
|
||||
824;SUB;1000000000000000000,1000000000000000000
|
||||
825;SWAP3;
|
||||
826;POP;1000000000000000000
|
||||
827;POP;0
|
||||
828;DUP2;
|
||||
829;SWAP1;
|
||||
830;SSTORE;0,1
|
||||
831;POP;0
|
||||
832;POP;1
|
||||
833;POP;1000000000000000000
|
||||
834;JUMP;264
|
||||
264;JUMPDEST;
|
||||
265;STOP;
|
||||
878;CALL;1,0
|
||||
879;ISZERO;1
|
||||
880;DUP1;
|
||||
881;ISZERO;0
|
||||
882;PUSH2;895
|
||||
885;JUMPI;1,895
|
||||
895;JUMPDEST;
|
||||
896;POP;0
|
||||
897;POP;164
|
||||
898;POP;773487949
|
||||
899;POP;1148000406285257731517353366331601018510389802278
|
||||
900;JUMP;404
|
||||
404;JUMPDEST;
|
||||
405;STOP;
|
||||
@@ -0,0 +1,290 @@
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;CALLVALUE;0
|
||||
6;DUP1;
|
||||
7;ISZERO;1
|
||||
8;PUSH2;10
|
||||
11;JUMPI;1,10
|
||||
16;JUMPDEST;
|
||||
17;POP;0
|
||||
18;PUSH1;4
|
||||
20;CALLDATASIZE;4
|
||||
21;LT;4,4
|
||||
22;PUSH2;54
|
||||
25;JUMPI;0,36
|
||||
26;PUSH1;0
|
||||
28;CALLDATALOAD;36288808125440614087156239300432291571913474525407125660472422305149220814848
|
||||
29;PUSH1;224
|
||||
31;SHR;36288808125440614087156239300432291571913474525407125660472422305149220814848,224
|
||||
32;DUP1;
|
||||
33;PUSH4;1346026703
|
||||
38;EQ;1346026703,1346026703
|
||||
39;PUSH2;59
|
||||
42;JUMPI;1,59
|
||||
59;JUMPDEST;
|
||||
60;PUSH2;43
|
||||
63;PUSH2;141
|
||||
66;JUMP;141
|
||||
141;JUMPDEST;
|
||||
142;PUSH1;0
|
||||
144;DUP1;
|
||||
145;PUSH1;0
|
||||
147;DUP1;
|
||||
148;SLOAD;1034896644053684534091083175701429597294878387947
|
||||
149;SWAP1;
|
||||
150;PUSH2;256
|
||||
153;EXP;0,256
|
||||
154;SWAP1;
|
||||
155;DIV;1,1034896644053684534091083175701429597294878387947
|
||||
156;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
177;AND;1034896644053684534091083175701429597294878387947,1461501637330902918203684832716283019655932542975
|
||||
178;PUSH20;1461501637330902918203684832716283019655932542975
|
||||
199;AND;1034896644053684534091083175701429597294878387947,1461501637330902918203684832716283019655932542975
|
||||
200;PUSH4;4037859285
|
||||
205;PUSH1;64
|
||||
207;MLOAD;128
|
||||
208;DUP2;
|
||||
209;PUSH2;4294967295
|
||||
214;AND;4037859285;4294967295
|
||||
215;PUSH1;224
|
||||
217;SHR;4037859285,224
|
||||
218;DUP2;
|
||||
219;MSTORE
|
||||
220;PUSH1;4
|
||||
222;ADD;128,4
|
||||
223;PUSH1;32
|
||||
225;PUSH1;64
|
||||
227;MLOAD;128
|
||||
228;DUP1;
|
||||
229;DUP4;
|
||||
230;SUB;128,132
|
||||
231;DUP2;
|
||||
232;PUSH1;0
|
||||
234;DUP8;
|
||||
235;DUP1;
|
||||
236;EXTCODESIZE;513
|
||||
237;ISZERO;513
|
||||
238;DUP1;
|
||||
239;ISZERO;0
|
||||
240;PUSH2;248
|
||||
243;JUMPI;1,248
|
||||
248;JUMPDEST;
|
||||
249;POP;0
|
||||
250;GAS;36091
|
||||
0;PUSH1;128
|
||||
2;PUSH1;64
|
||||
4;MSTORE;
|
||||
5;CALLVALUE;0
|
||||
6;DUP1;
|
||||
7;ISZERO;1
|
||||
8;PUSH2;10
|
||||
11;JUMPI;1,10
|
||||
16;JUMPDEST;
|
||||
17;POP;0
|
||||
18;PUSH1;4
|
||||
20;CALLDATASIZE;4
|
||||
21;LT;4,4
|
||||
22;PUSH2;54
|
||||
25;JUMPI;0,36
|
||||
26;PUSH1;0
|
||||
28;CALLDATALOAD;108860470973059015388586700352357298692816548327441413882039699691817989570560
|
||||
29;PUSH1;224
|
||||
31;SHR;108860470973059015388586700352357298692816548327441413882039699691817989570560,224
|
||||
32;DUP1;
|
||||
33;PUSH4;1490037513
|
||||
38;EQ;4037859285,1490037513
|
||||
39;PUSH2;59
|
||||
42;JUMPI;0,59
|
||||
43;DUP1;
|
||||
44;PUSH4;4037859285
|
||||
49;EQ;4037859285,4037859285
|
||||
50;PUSH2;59
|
||||
53;JUMPI;1,59
|
||||
89;JUMPDEST;
|
||||
90;PUSH1;0
|
||||
93;PUSH2;125
|
||||
96;JUMP;125
|
||||
125;JUMPDEST;
|
||||
126;PUSH1;0
|
||||
128;DUP1;
|
||||
129;SLOAD;0
|
||||
130;TIMESTAMP;1692939836
|
||||
131;EQ;0,1692939836
|
||||
132;ISZERO;1
|
||||
133;PUSH2;141
|
||||
136;JUMPI;1,8d
|
||||
141;JUMPDEST;
|
||||
142;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
|
||||
175;TIMESTAMP;1692939836
|
||||
176;PUSH1;64
|
||||
178;MLOAD;128
|
||||
179;DUP1;
|
||||
180;DUP3;
|
||||
181;DUP2;
|
||||
182;MSTORE;
|
||||
183;PUSH1;32
|
||||
185;ADD;80,20
|
||||
186;SWAP2;
|
||||
187;POP;1692939836
|
||||
188;POP;128
|
||||
189;PUSH1;64
|
||||
191;MLOAD;128
|
||||
192;DUP1;
|
||||
193;SWAP2;
|
||||
194;SUB;128,160
|
||||
195;SWAP1;
|
||||
196;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
|
||||
197;TIMESTAMP;1692939836
|
||||
198;PUSH1;0
|
||||
200;DUP2;
|
||||
201;SWAP1;
|
||||
202;SSTORE;1692939836,0
|
||||
203;POP;1692939836
|
||||
204;PUSH1;0
|
||||
206;PUSH1;7
|
||||
208;TIMESTAMP;1692939836
|
||||
209;DUP2;
|
||||
210;PUSH2;215
|
||||
213;JUMPI;7;215
|
||||
215;JUMPDEST;
|
||||
216;MOD;0
|
||||
217;EQ;0,0
|
||||
218;PUSH2;331
|
||||
221;JUMPI;1,331
|
||||
331;JUMPDEST;
|
||||
332;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
|
||||
365;TIMESTAMP;1692939836
|
||||
366;PUSH1;64
|
||||
368;MLOAD;128
|
||||
369;DUP1;
|
||||
370;DUP3;
|
||||
371;DUP2;
|
||||
372;MSTORE;
|
||||
373;PUSH1;32
|
||||
375;ADD;80,20
|
||||
376;SWAP2;
|
||||
377;POP;1692939836
|
||||
378;POP;128
|
||||
379;PUSH1;64
|
||||
381;MLOAD;128
|
||||
382;DUP1;
|
||||
383;SWAP2;
|
||||
384;SUB;128,160
|
||||
385;SWAP1;
|
||||
386;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
|
||||
387;PUSH32;52189957839671922653803054351540587877049321209015323053385286709118451475657
|
||||
420;PUSH1;7
|
||||
422;TIMESTAMP;1692939836
|
||||
423;DUP2;
|
||||
424;PUSH2;429
|
||||
427;JUMPI;7,429
|
||||
429;JUMPDEST;
|
||||
430;MOD;0
|
||||
431;PUSH1;64
|
||||
433;MLOAD;128
|
||||
434;DUP1;
|
||||
435;DUP3;
|
||||
436;DUP2;
|
||||
437;MSTORE;
|
||||
438;PUSH1;32
|
||||
440;ADD;80,20
|
||||
441;SWAP2;
|
||||
442;POP;0
|
||||
443;POP;128
|
||||
444;PUSH1;64
|
||||
446;MLOAD;128
|
||||
447;DUP1;
|
||||
448;SWAP2;
|
||||
449;SUB;128,160
|
||||
450;SWAP1;
|
||||
451;LOG1;52189957839671922653803054351540587877049321209015323053385286709118451475657,32,128
|
||||
452;PUSH1;1
|
||||
454;SLOAD;1
|
||||
455;SWAP1;
|
||||
456;POP;0
|
||||
457;SWAP1;
|
||||
458;JUMP;61
|
||||
97;JUMPDEST;
|
||||
98;PUSH1;64
|
||||
100;MLOAD;128
|
||||
101;DUP1;
|
||||
102;DUP3;
|
||||
103;DUP2;
|
||||
104;MSTORE;
|
||||
105;PUSH1;32
|
||||
107;ADD;128,32
|
||||
108;SWAP2;
|
||||
109;POP;12
|
||||
110;POP;128
|
||||
111;PUSH1;64
|
||||
113;MLOAD;128
|
||||
114;DUP1;
|
||||
115;SWAP2;
|
||||
116;SUB;128,160
|
||||
117;SWAP1;
|
||||
118;RETURN;32;128
|
||||
251;CALL;1,0
|
||||
252;ISZERO;0
|
||||
254;DUP1;
|
||||
255;ISZERO;1
|
||||
256;PUSH2;268
|
||||
259;JUMPI;1,268
|
||||
268;JUMPDEST;
|
||||
265;POP;0
|
||||
270;POP;132
|
||||
271;POP;4037859285
|
||||
272;POP;1034896644053684534091083175701429597294878387947
|
||||
273;PUSH1;64
|
||||
275;MLOAD;128
|
||||
276;RETURNDATASIZE;20
|
||||
277;PUSH1;32
|
||||
279;DUP2;
|
||||
280;LT;20,20
|
||||
281;ISZERO;1
|
||||
282;PUSH2;290
|
||||
285;JUMPI;1,290
|
||||
290;JUMPDEST;
|
||||
291;DUP2;
|
||||
292;ADD;32;128
|
||||
293;SWAP1;
|
||||
294;DUP1;
|
||||
295;DUP1;
|
||||
296;MLOAD;c
|
||||
297;SWAP1;
|
||||
298;PUSH1;32
|
||||
300;ADD;80,20
|
||||
301;SWAP1;
|
||||
302;SWAP3;
|
||||
303;SWAP2;
|
||||
304;SWAP1;
|
||||
305;POP;a0
|
||||
306;POP;128
|
||||
307;POP;a0
|
||||
308;SWAP1;
|
||||
309;POP;0
|
||||
310;DUP1;
|
||||
311;SWAP2;
|
||||
312;POP;0
|
||||
313;POP;12
|
||||
314;SWAP1;
|
||||
315;JUMP;43
|
||||
67;JUMPDEST;
|
||||
68;PUSH1;64
|
||||
70;MLOAD;128
|
||||
71;DUP1;
|
||||
72;DUP3;
|
||||
73;DUP2;
|
||||
74;MSTORE;
|
||||
75;PUSH1;32
|
||||
77;ADD;80,20
|
||||
78;SWAP2;
|
||||
79;POP;12
|
||||
80;POP;128
|
||||
81;PUSH1;64
|
||||
83;MLOAD;128
|
||||
84;DUP1;
|
||||
85;SWAP2;
|
||||
86;SUB;128,160
|
||||
87;SWAP1;
|
||||
88;RETURN;32;128
|
||||
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
import json
|
||||
need_check = [
|
||||
"ADD", "MUL", "SUB", "DIV", "SDIV", "MOD", "SMOD", "ADDMOD", "MULMOD", "EXP", "SIGNEXTEND", "LT", "GT", "SLT",
|
||||
"SGT", "EQ", "ISZERO", "AND", "OR", "XOR", "NOT", "BYTE", "SHL", "SHR", "SAR",
|
||||
"CALLDATACOPY", "CODECOPY", "EXTCODECOPY", "RETURNDATACOPY", "PREVRANDAO", "POP", "SSTORE", "JUMPI", "LOG0"
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 check_stack.py <path_to_reconstruct_json>")
|
||||
sys.exit(1)
|
||||
filepath = sys.argv[1]
|
||||
codes = {
|
||||
|
||||
}
|
||||
vars = {
|
||||
|
||||
}
|
||||
with open(filepath, 'r') as f:
|
||||
value = f.read()
|
||||
codes = json.loads(value)
|
||||
for code in codes:
|
||||
if codes[code]["opcode"] in need_check:
|
||||
arg_count = len(codes[code]["args"])
|
||||
if codes[code]["args"] != codes[code]["stacks"][-arg_count:]:
|
||||
if codes[code]["opcode"] == "CALLDATACOPY":
|
||||
continue
|
||||
print(codes[code]["args"], codes[code]["stacks"][-arg_count:])
|
||||
print(codes[code])
|
||||
print("=====================================")
|
||||
exit(1)
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 设置日志文件夹的路径
|
||||
LOGGING_DIR="logging"
|
||||
|
||||
# 遍历logging文件夹中的所有文件
|
||||
for file in "$LOGGING_DIR"/*; do
|
||||
# 检查文件是否包含"ERROR"
|
||||
if ! grep -q "Traceback" "$file"; then
|
||||
# 如果不包含"ERROR",则删除文件
|
||||
echo "Deleting $file as it does not contain Traceback "
|
||||
rm "$file"
|
||||
else
|
||||
echo "$file contains ERROR, not deleting"
|
||||
fi
|
||||
done
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import sys
|
||||
|
||||
from graphviz import Digraph
|
||||
import src.tac_efg as tac_efg
|
||||
import src.exporter as exporter
|
||||
|
||||
def block_k_depth(block):
|
||||
return f"{block.evm_ops[0].call_depth}"
|
||||
|
||||
|
||||
def block_k(block):
|
||||
dividing_line = "⋮"
|
||||
variable = "" if block.evm_ops[-1].value is None else block.evm_ops[-1].value
|
||||
# return f"{block.evm_ops[0].call_depth}-{block.entry}({block.evm_ops[0].opcode.name})->{block.exit}({block.evm_ops[-1].opcode.name})"
|
||||
return f"{block.evm_ops[0].call_depth}\n{block.entry};{block.evm_ops[0].opcode.name}; {block.evm_ops[0].value}\n{dividing_line}\n{block.exit}; {block.evm_ops[-1].opcode.name}; {variable}"
|
||||
|
||||
|
||||
def draw_cfg(efg):
|
||||
dot = Digraph(comment='CFG')
|
||||
dot.format = "svg"
|
||||
|
||||
for block in efg.blocks:
|
||||
dot.node(block_k(block), block_k(block), shape='box')
|
||||
for edge in efg.edge_list():
|
||||
dot.edge(block_k(edge[0]), block_k(edge[1]))
|
||||
dot.render('cfg')
|
||||
|
||||
|
||||
def draw_depth_cfg(efg):
|
||||
dot = Digraph(comment='depth-CFG')
|
||||
dot.format = "svg"
|
||||
|
||||
for block in efg.blocks:
|
||||
dot.node(block_k_depth(block), block_k_depth(block), shape='box')
|
||||
for edge in efg.edge_list():
|
||||
dot.edge(block_k_depth(edge[0]), block_k_depth(edge[1]))
|
||||
dot.render('depth-cfg')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
with open(sys.argv[1], "r") as f:
|
||||
efg = tac_efg.TACGraph.from_opcode(f)
|
||||
print(efg)
|
||||
|
||||
exporter.EFGDotExporter(efg).export(out_filename="suicidal_ganache.html")
|
||||
|
||||
# draw_cfg(efg)
|
||||
# draw_depth_cfg(efg)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Script for Adding sc_addr smart contract to address to the facts folder
|
||||
|
||||
To run the script, use the command: `python3 extract_sc_addr.py`
|
||||
|
||||
Author: Yangyi Zou
|
||||
Date: Dec 2023
|
||||
Usage: python3 extract_sc_addr.py
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
|
||||
def fetch_transaction_details(transaction_hash: str):
|
||||
url = 'http://localhost:8545'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
params = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "eth_getTransactionByHash",
|
||||
"params": [transaction_hash],
|
||||
"id": 1
|
||||
}
|
||||
try:
|
||||
response = requests.post(url, json=params, headers=headers)
|
||||
response_json = response.json()
|
||||
if 'result' in response_json and response_json['result']:
|
||||
return response_json['result']
|
||||
else:
|
||||
print(f"No result found for transaction hash: {transaction_hash}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Request failed for transaction hash: {transaction_hash}, error: {e}")
|
||||
return None
|
||||
|
||||
def extract_to_address_from_facts(folder_path='facts'):
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for subfolder in dirs:
|
||||
transaction_hash = subfolder.split('_')[-1]
|
||||
transaction_details = fetch_transaction_details(transaction_hash)
|
||||
if transaction_details and 'to' in transaction_details:
|
||||
to_address = transaction_details['to']
|
||||
file_path = os.path.join(root, subfolder, 'sc_addr.facts')
|
||||
try:
|
||||
with open(file_path, 'w') as file:
|
||||
file.write(to_address)
|
||||
count += 1
|
||||
print(f"Processed {count}: {subfolder} -> sc_addr.facts")
|
||||
except IOError as e:
|
||||
print(f"Failed to write to {file_path}, error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
extract_to_address_from_facts()
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Script for Processing Smart Contract Transactions
|
||||
|
||||
This script processes smart contract transactions from a specified input folder.
|
||||
It reads each transaction file, builds a Transactional Abstract Control Flow Graph (TAC EFG),
|
||||
and exports the related facts based on a set of target opcodes. The script maintains a log
|
||||
of any errors encountered during processing and saves the progress to continue from the last
|
||||
processed file in case of interruption. The error log and progress are saved in CSV and text
|
||||
files, respectively.
|
||||
|
||||
To run the script, use the command: `python3 analyze3.py`
|
||||
|
||||
|
||||
Author: Yangyi Zou
|
||||
Date: May 19 2024
|
||||
Usage: python3 analyze3.py
|
||||
"""
|
||||
import time
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from os.path import abspath, dirname, join
|
||||
import src.exporter as exporter
|
||||
import src.tac_efg_lab as tac_efg
|
||||
import src.settings as settings
|
||||
import csv, threading, queue, time
|
||||
import pdb,traceback
|
||||
import psycopg2.pool
|
||||
from KEY import *
|
||||
import threading
|
||||
import queue
|
||||
from tqdm import tqdm
|
||||
|
||||
# 设置目标操作码
|
||||
target_opcodes = ['CREATE', 'BALANCE', 'CALLER', 'CALLVALUE', 'STOP', 'RETURN', 'REVERT', 'ORIGIN', 'CALLDATALOAD', 'EQ', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'COINBASE', 'BLOCKHASH', 'GASLIMIT', 'EXTCODESIZE', 'SELFDESTRUCT', 'JUMPI', 'JUMP', 'JUMPDEST', 'SSTORE', 'SLOAD', 'CALL', 'DELEGATE', 'CALLCODE', 'STATICCALL']
|
||||
|
||||
# TODO 1: Initialize database connection pool, error logging, and progress tracking
|
||||
def create_connection_pool():
|
||||
return psycopg2.pool.ThreadedConnectionPool(1, 10, user=USER, password=PASSWORD, host=HOST, port=PORT, database=DATABASE)
|
||||
conn_pool = create_connection_pool()
|
||||
|
||||
def update_progress(tx_hash, conn_pool):
|
||||
with conn_pool.getconn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("DELETE FROM framework_progress")
|
||||
cursor.execute("INSERT INTO framework_progress (last_processed_tx_hash) VALUES (%s)", (tx_hash,))
|
||||
conn.commit()
|
||||
conn_pool.putconn(conn)
|
||||
|
||||
def log_error(tx_hash, error, conn_pool):
|
||||
"""Logs an error for a transaction in the database"""
|
||||
with conn_pool.getconn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("INSERT INTO error_log (tx_hash, error_message) VALUES (%s, %s)", (tx_hash, error))
|
||||
conn_pool.putconn(conn)
|
||||
|
||||
|
||||
|
||||
# TODO 2: Fetch transaction data
|
||||
def get_last_processed_tx_hash(conn_pool):
|
||||
"""获取上次处理的最后一个事务哈希。"""
|
||||
with conn_pool.getconn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute("SELECT last_processed_tx_hash FROM framework_progress LIMIT 1")
|
||||
result = cursor.fetchone()
|
||||
conn_pool.putconn(conn)
|
||||
return result[0] if result else None
|
||||
|
||||
def fetch_transactions(conn_pool,start_after_hash=None,limit=200000):
|
||||
"""Fetches transactions from the database."""
|
||||
with conn_pool.getconn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
if start_after_hash:
|
||||
cursor.execute("SELECT * FROM tx_rtl_part_b_dedup WHERE tx_hash > %s ORDER BY tx_hash ASC LIMIT %s", (start_after_hash,limit))
|
||||
else:
|
||||
cursor.execute("SELECT tx_hash, rtl_dedup, to_address FROM tx_rtl_part_b_dedup ORDER BY tx_hash ASC LIMIT %s", (limit,))
|
||||
transactions = cursor.fetchall()
|
||||
conn_pool.putconn(conn)
|
||||
return transactions
|
||||
|
||||
def setup_directories(tx_hash):
|
||||
"""Sets up directories for output based on the transaction hash."""
|
||||
output_folder = os.path.join('./facts_test', f'fact_{tx_hash}')
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder)
|
||||
return output_folder
|
||||
|
||||
# TODO 3: Process transactions
|
||||
def process_transaction(tx_hash, rtl, to_address, output_folder, conn_pool):
|
||||
"""Processes a transaction and exports the facts."""
|
||||
start_time = time.time()
|
||||
try:
|
||||
rtl_file_like = io.StringIO(rtl)
|
||||
efg = tac_efg.TACGraph.from_opcode(rtl_file_like)
|
||||
# print(f"this is the efg content,{efg}")
|
||||
# 假设 export 操作是耗时操作,需要放在超时检测中
|
||||
if time.time() - start_time < timeout_seconds:
|
||||
exporter.EFGTsvExporter(efg).export(output_dir=output_folder, out_opcodes=target_opcodes)
|
||||
facts_file_path = os.path.join(output_folder, 'sc_addr.facts')
|
||||
with open(os.path.join(output_folder, 'sc_addr.facts'), 'w') as f:
|
||||
f.write(to_address)
|
||||
|
||||
else:
|
||||
|
||||
raise TimeoutError("Processing exceeded time limit.")
|
||||
processing_time = time.time() - start_time
|
||||
print(f"Processed {tx_hash} in {processing_time:.2f} seconds")
|
||||
update_progress(tx_hash, conn_pool)
|
||||
|
||||
except TimeoutError as te:
|
||||
log_error(tx_hash, str(te), conn_pool)
|
||||
except Exception as e:
|
||||
logging.error(f"Error processing {tx_hash}: {e}", exc_info=True)
|
||||
log_error(tx_hash, str(e), conn_pool)
|
||||
traceback.print_exc()
|
||||
|
||||
# TODO 5: Setup and start threads
|
||||
def start_processing_thread(conn_pool, num_threads=1):
|
||||
"""Starts a pool of threads to process transactions."""
|
||||
threads = []
|
||||
for _ in range(num_threads):
|
||||
thread = threading.Thread(target=file_processing_worker, args=(conn_pool, pbar))
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
return threads
|
||||
|
||||
# Ensure global constants are defined
|
||||
timeout_seconds = 40 # Example: 30 seconds timeout for processing each transaction
|
||||
# TODO 6: Main execution block
|
||||
if __name__ == "__main__":
|
||||
last_processed_hash = get_last_processed_tx_hash(conn_pool)
|
||||
transactions = fetch_transactions(conn_pool, start_after_hash=last_processed_hash)
|
||||
file_queue = queue.Queue()
|
||||
|
||||
# Prepare tqdm progress bar
|
||||
pbar = tqdm(total=len(transactions), desc='Processing Transactions', unit='tx')
|
||||
|
||||
# Update your file_processing_worker function to update tqdm progress upon each task completion
|
||||
# TODO 4: Set threading and parallel processing mechanisms
|
||||
def file_processing_worker(conn_pool, pbar):
|
||||
"""Thread worker function that processes files from the queue."""
|
||||
while True:
|
||||
tx_data = file_queue.get()
|
||||
if tx_data is None: # Correctly handle the queue's end signal
|
||||
file_queue.task_done()
|
||||
break
|
||||
tx_hash, rtl, to_address = tx_data
|
||||
print(f"Now processing {tx_hash}")
|
||||
output_folder = setup_directories(tx_hash)
|
||||
process_transaction(tx_hash, rtl, to_address, output_folder, conn_pool)
|
||||
file_queue.task_done()
|
||||
pbar.update(1)
|
||||
|
||||
# Enqueue transactions
|
||||
for tx in transactions:
|
||||
file_queue.put(tx)
|
||||
|
||||
# Start threads
|
||||
threads = start_processing_thread(conn_pool, num_threads=1)
|
||||
|
||||
# Wait for all items in the queue to be processed
|
||||
file_queue.join()
|
||||
|
||||
# Stop all threads
|
||||
for _ in range(len(threads)):
|
||||
file_queue.put(None)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
print("Processing complete.")
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import csv
|
||||
|
||||
def get_last_processed_filename(progress_file_path):
|
||||
try:
|
||||
with open(progress_file_path, "r") as file:
|
||||
return file.read().strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
def set_last_processed_filename(progress_file_path, filename):
|
||||
with open(progress_file_path, "w") as file:
|
||||
file.write(filename)
|
||||
|
||||
def is_all_processed(input_folder, progress_file_path):
|
||||
last_processed = get_last_processed_filename(progress_file_path)
|
||||
if not last_processed:
|
||||
return False
|
||||
filenames = sorted(os.listdir(input_folder))
|
||||
return last_processed not in filenames or filenames.index(last_processed) == len(filenames) - 1
|
||||
|
||||
def log_error(error_log_file, transaction, error_message):
|
||||
with open(error_log_file, 'a', newline='') as file:
|
||||
writer = csv.writer(file)
|
||||
writer.writerow([transaction, error_message])
|
||||
|
||||
def run_script(input_folder, progress_file_path, error_log_file):
|
||||
while not is_all_processed(input_folder, progress_file_path):
|
||||
last_processed = get_last_processed_filename(progress_file_path)
|
||||
try:
|
||||
subprocess.run(['python3', 'analyze3.py'], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Script crashed. Restarting...")
|
||||
log_error(error_log_file, last_processed if last_processed else "unknown", "Script crashed: " + str(e))
|
||||
filenames = sorted(os.listdir(input_folder))
|
||||
next_file_index = filenames.index(last_processed) + 1 if last_processed in filenames else 0
|
||||
if next_file_index < len(filenames):
|
||||
next_file = filenames[next_file_index]
|
||||
set_last_processed_filename(progress_file_path, next_file)
|
||||
time.sleep(1)
|
||||
|
||||
print("All files have been processed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_folder = "/home/yangyi/database/smart_contract-dataset/Tx_tuples"
|
||||
progress_file_path = 'progress.txt'
|
||||
error_log_file = 'error_log.csv'
|
||||
run_script(input_folder, progress_file_path, error_log_file)
|
||||
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
import json
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 reconstruct.py <path_to_log_file>")
|
||||
sys.exit(1)
|
||||
log_file = sys.argv[1]
|
||||
codes = {
|
||||
|
||||
}
|
||||
vars = {
|
||||
|
||||
}
|
||||
with open(log_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
idx = 0
|
||||
for line in lines:
|
||||
if line.startswith('INFO:root:OP:'):
|
||||
line = line.replace("INFO:root:OP:", "").strip()
|
||||
stacks, op = line.split("->")
|
||||
stacks = [item.strip() for item in stacks.replace("[", "").replace("]", "").split(",")]
|
||||
op = op.strip()
|
||||
ops = op.split(" ")
|
||||
pc, opcode, args = ops[0], ops[1], ops[2:]
|
||||
codes[idx] = {
|
||||
"pc": pc,
|
||||
"opcode": opcode,
|
||||
"args": args,
|
||||
"stacks": stacks
|
||||
}
|
||||
idx += 1
|
||||
elif line.startswith("WARNING:root:"):
|
||||
line = line.replace("WARNING:root:", "").strip()
|
||||
name, value = line.split("=")
|
||||
name = name.strip()
|
||||
value = value.strip()
|
||||
vars[name] = value
|
||||
|
||||
# Apply vars
|
||||
for pc, code in codes.items():
|
||||
for i, arg in enumerate(code["stacks"]):
|
||||
if arg in vars:
|
||||
code["stacks"][i] = vars[arg]
|
||||
import json
|
||||
# print(vars)
|
||||
print(json.dumps(codes, indent=4))
|
||||
@@ -0,0 +1,98 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Reentrancy
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
|
||||
// Filter and obtain the pairs (x, y) with the dependency relationship
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
|
||||
// Collect all the call related opcodes info
|
||||
.decl CallOperator(target_val:Value, call_loc:number, call_cd:number, call_cn:number) //zoey - target_val: target address, call_loc: call location, call_cd: call depth, call_cn: call number
|
||||
CallOperator(target_val, call_loc, call_cd, call_cn) :-
|
||||
(op_CALL(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
|
||||
op_STATICCALL(_, _, target_var, _, _, _, _, _, call_loc, call_cd, call_cn); //zoey - staticcall for testing why it is wrong
|
||||
// op_STATICCALL(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
|
||||
op_DELEGATE(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn);
|
||||
op_CALLCODE(_, _, target_var, _, _, _, _, _, _, call_loc, call_cd, call_cn)),
|
||||
value(target_var, target_val).
|
||||
|
||||
|
||||
// Check the second condition. A opcode sstore needs to in the returned call of the call where the sstore appears.
|
||||
.decl ReenResult(sload_loc: number, jumpi_loc:number, sload_depth:number, sload_call_number:number,
|
||||
sstore_loc:number, sstore_depth:number, sstore_cn:number, sstore_sc_addr:Value, sload_sc_addr:Value)
|
||||
.output ReenResult
|
||||
ReenResult(sload_loc, jumpi_loc, sload_depth, sload_call_number, sstore_loc, sstore_depth, sstore_cn, sstore_sc_addr, sload_sc_addr):-
|
||||
// Step1: Get sload
|
||||
// a) its call depth must be greater than 2
|
||||
// b) it must have the same call depth and call number with jumpi, which depends on sload
|
||||
op_SLOAD(_, sload_addr_var, sload_val_var, sload_loc, sload_depth, sload_call_number),
|
||||
sload_depth > 2,
|
||||
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, sload_depth, sload_call_number),
|
||||
depends(jumpi_cond_var, sload_val_var),
|
||||
|
||||
// Step2: Get sstore
|
||||
// a) sstore call depth must be less than sload call depth
|
||||
op_SSTORE(_, sstore_addr_var, _, sstore_loc, sstore_depth, sstore_cn),
|
||||
sload_depth >= sstore_depth + 2,
|
||||
sstore_loc > sload_loc,
|
||||
value(sstore_addr_var, sstore_addr_var_val),
|
||||
value(sload_addr_var, sload_addr_var_val),
|
||||
sstore_addr_var_val = sload_addr_var_val,
|
||||
|
||||
// Step3: Make sure sload and sstore are from the same contract
|
||||
((sstore_depth = 1,
|
||||
sc_addr(sstore_sc_addr));
|
||||
(sstore_depth != 1,
|
||||
CallOperator(sstore_sc_addr, _, sstore_depth-1 , call_cn),
|
||||
call_cn >= sstore_cn)
|
||||
),
|
||||
|
||||
CallOperator(sload_sc_addr, _, sload_depth-1 , call_cn),
|
||||
call_cn >= sload_call_number,
|
||||
|
||||
sstore_sc_addr = sload_sc_addr.
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Unchecked return call value
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
|
||||
// Filter and obtain the pairs (x, y) with the dependency relationship
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
|
||||
// Step1: Obtain all the call and their return values
|
||||
.decl Step1(gas_var:Variable, target_var:Variable, value_var:Variable, call_success:Variable, call_loc:number, call_cn:number)
|
||||
.output Step1
|
||||
Step1(gas_var, target_var, value_var, call_success, call_loc, call_cn) :-
|
||||
op_CALL(_, gas_var, target_var, value_var, _, _, _, _, call_success, call_loc, 1, call_cn).
|
||||
|
||||
|
||||
// Step2: Find all the calls with jumpi, which means the call is checked in some if-else conditions
|
||||
.decl Step2(call_loc:number, call_cn:number)
|
||||
.output Step2
|
||||
Step2(call_loc, call_cn) :-
|
||||
Step1(_, _, _, call_success, call_loc, call_cn),
|
||||
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, call_cn),
|
||||
jumpi_loc > call_loc,
|
||||
depends(jumpi_cond_var, call_success).
|
||||
|
||||
|
||||
// Step3: Get all the calls and remove ones that are checked so that we can get all the unchecked calls
|
||||
.decl Step3(call_loc:number, call_cn:number, cs_val:Value, gas_val:Value, target_val:Value, value_val:Value)
|
||||
.output Step3
|
||||
Step3(call_loc, call_cn, cs_val, gas_val, target_val, value_val) :-
|
||||
Step1(gas_var, target_var, value_var, cs_success, call_loc, call_cn),
|
||||
!Step2(call_loc, _),
|
||||
value(cs_success, cs_val),
|
||||
value(gas_var, gas_val),
|
||||
value(target_var, target_val),
|
||||
value(value_var, value_val).
|
||||
@@ -0,0 +1,72 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Failed send
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
|
||||
// Filter and obtain the pairs (x, y) with the dependency relationship
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
|
||||
.decl FailedSendResult(revert_loc:number, call_loc:number, target_val:Value, value_val:Value)
|
||||
.output FailedSendResult
|
||||
FailedSendResult(revert_loc, call_loc, target_val, value_val):-
|
||||
|
||||
// Step 2: The result should be revert.
|
||||
// Tip: Step 2 is moved to the front is to optimize the performance
|
||||
op_REVERT(_, _, _, revert_loc, 1, _),
|
||||
|
||||
// Step 1: There should be a send, which includes a call opcode and value is not null. In addition, the call return value should be failure.
|
||||
op_CALL(_, _, target_var, value_var, _, _, _, _, call_success, call_loc, 1, _),
|
||||
!value(value_var, "0x0"),
|
||||
value(call_success, "0x0"),
|
||||
|
||||
// Step 3: The call return value should be checked
|
||||
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, _),
|
||||
jumpi_loc > call_loc,
|
||||
revert_loc > jumpi_loc,
|
||||
depends(jumpi_cond_var, call_success),
|
||||
value(target_var, target_val),
|
||||
value(value_var, value_val).
|
||||
@@ -0,0 +1,64 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Timestamp Dependence, similar for the other dependeces, such as block state
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
|
||||
// Filter and obtain the pairs (x, y) with the dependency relationship
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
|
||||
.decl TimestampDependenceResult(bsd_loc:number, bsd_val:Value, jumpi_loc:number)
|
||||
.output TimestampDependenceResult
|
||||
TimestampDependenceResult(bsd_loc, bsd_val, jumpi_loc):-
|
||||
|
||||
// Step1: Obtain all the timestamp opcodes
|
||||
op_TIMESTAMP(_, bsd_var, bsd_loc, 1, _),
|
||||
|
||||
// Step2: This opcode should determine a path
|
||||
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, _, _),
|
||||
jumpi_loc > bsd_loc,
|
||||
depends(jumpi_cond_var, bsd_var),
|
||||
value(bsd_var, bsd_val).
|
||||
@@ -0,0 +1,85 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Unsecured balance
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
// no caller, just call
|
||||
.decl Situation1(c:number)
|
||||
.output Situation1
|
||||
Situation1(c) :-
|
||||
c=count:{op_CALLER(_, _, _, 1, _)}.
|
||||
|
||||
|
||||
// Step1: detect all the call satisifies the conditions where the target address is not CONST.
|
||||
.decl Step1( target_var:Variable, calldataload_var:Variable, value_var:Variable, call_success:Variable, call_loc:number, callvalue_opcode:Opcode)
|
||||
.output Step1
|
||||
Step1(target_var, calldataload_var, value_var, call_success, call_loc, callvalue_opcode) :-
|
||||
op_CALL(_, _, target_var, value_var, _, _, _, _, call_success, call_loc, 1, _),
|
||||
op_CALLDATALOAD(_, _, calldataload_var, calldataload_loc, 1, _),
|
||||
depends(target_var, calldataload_var),
|
||||
call_loc > calldataload_loc,
|
||||
!value(value_var, "0x0"),
|
||||
def(value_var, _, call_value_loc, _, _),
|
||||
op(_, callvalue_opcode, call_value_loc),
|
||||
callvalue_opcode != "CALLVALUE".
|
||||
|
||||
// Step3: find all the calls removing all the calls in Step2
|
||||
.decl Step3(call_loc:number, calldataload_var:Variable, calvalue_opcode:Opcode, target_val:Value, value_val:Value, success_val:Value, count_caller:number)
|
||||
.output Step3
|
||||
Step3(call_loc, calldataload_var, callvalue_opcode, target_val, value_val, success_val, count_caller) :-
|
||||
Step1(target_var, calldataload_var, value_var, call_success, call_loc, callvalue_opcode),
|
||||
Situation1(count_caller),
|
||||
(count_caller = 0;
|
||||
count_caller = 1,
|
||||
1 = count:{use(target_var, _, _, _, 1, _)},
|
||||
op_CALLER(_, target_var, _, 1, _);
|
||||
count_caller = 1,
|
||||
0 = count:{use(target_var, _, _, _, 1, _)}),
|
||||
value(target_var, target_val),
|
||||
value(value_var, value_val),
|
||||
value(call_success, success_val).
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Misuse of origin
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
// Step 1: there is some origin opcode, which is correctly compared with the msg.sender.
|
||||
.decl Step1(ori_loc:number, ori_val:Value, caller_loc:number, caller_val:Value, used_loc:number, used_op:Opcode)
|
||||
.output Step1
|
||||
Step1(ori_loc, ori_val, caller_loc, caller_val, used_loc, used_op) :-
|
||||
op_ORIGIN(_, ori_var, ori_loc, 1, _),
|
||||
use(ori_var, _, _, used_loc, 1, _),
|
||||
op_CALLER(_, caller_var, caller_loc, 1, _),
|
||||
use(caller_var, _, _, used_loc, 1, _),
|
||||
op(_, used_op, used_loc),
|
||||
value(ori_var, ori_val),
|
||||
value(caller_var, caller_val).
|
||||
|
||||
// Step 2: remove the origins in the step 1
|
||||
// Step 3: for the left origins, they should be used by a jumpi or a sstore.
|
||||
.decl MisuseOriginResult(ori_loc:number, ori_val:Value, loc:number, opcode:Opcode)
|
||||
.output MisuseOriginResult
|
||||
MisuseOriginResult(ori_loc, ori_val, loc, opcode):-
|
||||
op_ORIGIN(_, ori_var, ori_loc, 1, _),
|
||||
!Step1(ori_loc, _ ,_ , _ ,_, _),
|
||||
(op_JUMPI(_, _, var, loc, 1, _);
|
||||
op_SSTORE(_, _, var, loc, 1, _)),
|
||||
loc> ori_loc,
|
||||
depends(var, ori_var),
|
||||
value(ori_var, ori_val),
|
||||
op(_, opcode, loc).
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Note: Suicidal contract
|
||||
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
|
||||
//Call depth is restricted to 1.
|
||||
.decl Step1(self_loc:number, caller_loc:number, jumpi_loc:number)
|
||||
.output Step1
|
||||
Step1(self_loc, caller_loc, jumpi_loc) :-
|
||||
op_SELFDESTRUCT(_, _, self_loc, 1, _),
|
||||
op_CALLER(_, caller_addr, caller_loc, 1, _),
|
||||
op_JUMPI(_, _, jumpi_cond_var, jumpi_loc, 1, _),
|
||||
jumpi_loc > caller_loc,
|
||||
self_loc > jumpi_loc,
|
||||
depends(jumpi_cond_var, caller_addr).
|
||||
|
||||
.decl SuicidalResult(benefit_val:Value, self_loc:number)
|
||||
.output SuicidalResult
|
||||
SuicidalResult(benefit_val, self_loc):-
|
||||
op_SELFDESTRUCT(_, benefit_addr, self_loc, 1, _),
|
||||
!Step1(self_loc, _, _),
|
||||
value(benefit_addr, benefit_val).
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "types.dl"
|
||||
#include "opcode.dl"
|
||||
|
||||
.decl depends(x:Variable, y:Variable)
|
||||
depends(x, x) :-
|
||||
use(x, _, _, _, _, _).
|
||||
|
||||
depends(x, x) :-
|
||||
def(x, _, _, _, _).
|
||||
|
||||
depends(x, y) :-
|
||||
def(x, stmt, _, x_cd, x_cn),
|
||||
use(y, stmt, _, _, x_cd, x_cn).
|
||||
|
||||
depends(x, z) :-
|
||||
depends(x, y),
|
||||
depends(y, z).
|
||||
|
||||
//Helper function: gas-dependent.
|
||||
//This line of code is considered gas dependent if it uses some variables that depend on the gas operation.
|
||||
.decl GasDep(gas_var:Variable, loc:number)
|
||||
GasDep(gas_var, loc) :-
|
||||
use(var, _, _, loc, _, _),
|
||||
depends(var, gas_var),
|
||||
def(gas_var, _, gas_loc, _, _),
|
||||
op(_, "GAS", gas_loc).
|
||||
|
||||
//Requirement 1. There is an Ether transfer. In the transaction, there should be at least one CALL related opcode, whose Ether value is greater than zero and call return value is `success`.
|
||||
.decl CallOperator(gas_var:Variable, value_var:Variable, call_loc:number, call_cd:number, call_cn:number)
|
||||
CallOperator(gas_var, value_var, call_loc, call_cd, call_cn) :-
|
||||
(op_CALL(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
|
||||
op_STATICCALL(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
|
||||
op_DELEGATE(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn);
|
||||
op_CALLCODE(_, gas_var, _, value_var, _, _, _, _, success_var, call_loc, call_cd, call_cn)),
|
||||
value(success_var, "0x1"),
|
||||
!value(value_var, "0x0").
|
||||
|
||||
//Requirement 2: There is an SSTORE after the external call returns, i.e., there is a state change (i.e., updates of a storage variable) after the call. Besides, the value of ether transferred should depend on the storage variable.
|
||||
.decl NormalReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
|
||||
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
|
||||
CallOperator(gas_var, value_var, call_loc, call_cd, call_cn),
|
||||
op_SSTORE(_, sstore_addr_var, _, sstore_loc, sstore_depth, sstore_cn),
|
||||
call_cd = sstore_depth,
|
||||
call_cn = sstore_cn,
|
||||
sstore_loc > call_loc,
|
||||
depends(value_var, sstore_addr_var).
|
||||
|
||||
//Gas-dependent Reentrancy
|
||||
//Requirement 3.1 : gas-dependent
|
||||
.decl GasDepReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
|
||||
.output GasDepReen
|
||||
GasDepReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
|
||||
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn),
|
||||
GasDep(gas_var, call_loc).
|
||||
|
||||
//Reentrancy with Constant Gas
|
||||
//Requirement 3.2 : with constant gas
|
||||
.decl GasConstantReen(gas_var:Variable, call_loc:number, sstore_loc:number, call_cd:number, call_cn:number)
|
||||
.output GasConstantReen
|
||||
GasConstantReen(gas_var, call_loc, sstore_loc, call_cd, call_cn) :-
|
||||
NormalReen(gas_var, call_loc, sstore_loc, call_cd, call_cn),
|
||||
!GasDep(gas_var, call_loc).
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// INPUT
|
||||
|
||||
.decl edge(h:Statement, t:Statement)
|
||||
.decl def(var:Variable, stmt:Statement, loc:number, cd:number, cn:number)
|
||||
.decl use(var:Variable, stmt:Statement, i:number, loc:number, cd:number, cn:number)
|
||||
.decl op(stmt:Statement, op:Opcode, loc:number)
|
||||
.decl value(var:Variable, val:Value)
|
||||
|
||||
.decl op_SLOAD(stmt:Statement, arg_loc:Variable, val:Variable, loc:number, call_depth: number, call_number:number)
|
||||
|
||||
.decl op_JUMPI(stmt:Statement, dest:Variable, cond:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_SSTORE(stmt:Statement, arg_addr:Variable, arg_val:Variable, loc:number, call_depth: number, call_number:number)
|
||||
|
||||
.decl op_LT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_GT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_SLT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_SGT(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_EQ(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_SUB(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_ADD(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_MUL(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_EXP(stmt:Statement, left_var:Variable, right_var:Variable, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_CALLDATALOAD(stmt:Statement, index:Variable, res:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_EXTCODESIZE(stmt:Statement, addr:Variable, size:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_TIMESTAMP(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_NUMBER(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_COINBASE(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_DIFFICULTY(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_GASLIMIT(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_ORIGIN(stmt:Statement, res_var:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_CALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_CALLCODE(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_DELEGATECALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_DELEGATE(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
//.decl op_STATICCALL(stmt:Statement, gas:Variable, target:Variable, value:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number)
|
||||
.decl op_STATICCALL(stmt:Statement, gas:Variable, target:Variable, data_start:Variable, data_length:Variable, return_start:Variable, return_length:Variable, success:Variable, loc:number, call_depth:number, call_number:number) // zoey - op_STATICCALL for testing
|
||||
|
||||
.decl op_SELFDESTRUCT(stmt:Statement, addr:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_REVERT(stmt:Statement, var1:Variable, var2:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl sc_addr(address:Value)
|
||||
|
||||
.decl op_STOP(stmt:Statement, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_RETURN(stmt:Statement, offset:Variable, length:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.decl op_CALLER(stmt:Statement, caller:Variable, loc:number, call_depth:number, call_number:number)
|
||||
|
||||
.input edge
|
||||
.input def
|
||||
.input use
|
||||
.input op
|
||||
.input value
|
||||
.input op_SLOAD
|
||||
.input op_JUMPI
|
||||
.input op_SSTORE
|
||||
.input op_LT
|
||||
.input op_GT
|
||||
.input op_SLT
|
||||
.input op_SGT
|
||||
.input op_EQ
|
||||
|
||||
.input op_SUB
|
||||
.input op_ADD
|
||||
.input op_MUL
|
||||
.input op_EXP
|
||||
.input op_CALLDATALOAD
|
||||
|
||||
.input op_EXTCODESIZE
|
||||
|
||||
.input op_TIMESTAMP
|
||||
.input op_NUMBER
|
||||
.input op_COINBASE
|
||||
.input op_DIFFICULTY
|
||||
.input op_GASLIMIT
|
||||
.input op_ORIGIN
|
||||
.input op_CALL
|
||||
.input op_CALLCODE
|
||||
.input op_DELEGATECALL
|
||||
.input op_DELEGATE
|
||||
.input op_STATICCALL
|
||||
.input op_SELFDESTRUCT
|
||||
.input op_REVERT
|
||||
.input sc_addr
|
||||
.input op_STOP
|
||||
.input op_RETURN
|
||||
.input op_CALLER
|
||||
@@ -0,0 +1,33 @@
|
||||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2016, 2017, The University of Sydney. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
.type Statement
|
||||
.type Variable
|
||||
.type Opcode
|
||||
.type Value
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""cfg.py: Base classes for representing Control Flow Graphs (CFGs)"""
|
||||
|
||||
"""控制流图抽象基类。
|
||||
类定义了许多方法,可以添加和删除基本块,添加和删除控制流边,查找基本块等。
|
||||
这个类还定义了一个虚拟的“访问者”接口,使得可以对控制流图进行遍历。
|
||||
|
||||
这个类的属性包括:
|
||||
blocks:存储所有基本块对象的列表。
|
||||
root:指向控制流图的根基本块对象的引用。
|
||||
|
||||
这个类的方法包括:
|
||||
remove_block():从图中删除一个基本块,并断开所有与其相连的边。
|
||||
add_block():向图中添加一个基本块。
|
||||
has_edge():判断一个从一个基本块到另一个基本块的边是否存在。
|
||||
remove_edge():删除一个从一个基本块到另一个基本块的边。
|
||||
add_edge():添加一个从一个基本块到另一个基本块的边。
|
||||
get_blocks_by_pc():返回包含给定程序计数器值的基本块列表。
|
||||
get_block_by_ident():返回具有指定标识符的基本块(如果存在)。
|
||||
recalc_preds():重新计算基本块的前继列表。
|
||||
reaches():确定一个基本块是否可以到达任何给定的目标基本块。
|
||||
transitive_closure():返回从输入地址可到达的块列表。
|
||||
remove_unreachable_blocks():删除从程序入口点不可达的所有块。
|
||||
edge_list():返回一个包含图中所有边的元组列表。"""
|
||||
|
||||
|
||||
import abc
|
||||
import typing as t
|
||||
|
||||
import src.patterns as patterns
|
||||
|
||||
|
||||
class ControlFlowGraph(patterns.Visitable):
|
||||
"""Abstract base class for a Control Flow Graph (CFG)"""
|
||||
|
||||
__STR_SEP = "\n\n-----\n\n"
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self):
|
||||
"""Create a new empty ControlFlowGraph"""
|
||||
|
||||
self.blocks = []
|
||||
"""List of BasicBlock objects"""
|
||||
|
||||
self.root = None
|
||||
"""The root BasicBlock object, or None for the empty graph"""
|
||||
|
||||
def __len__(self):
|
||||
return len(self.blocks)
|
||||
|
||||
def __str__(self):
|
||||
return self.__STR_SEP.join(str(b) for b in self.blocks)
|
||||
|
||||
def remove_block(self, block: 'BasicBlock') -> None:
|
||||
"""
|
||||
Remove the given block from the graph, disconnecting all incident edges.
|
||||
"""
|
||||
if block == self.root:
|
||||
self.root = None
|
||||
|
||||
for p in list(block.preds):
|
||||
self.remove_edge(p, block)
|
||||
for s in list(block.succs):
|
||||
self.remove_edge(block, s)
|
||||
|
||||
self.blocks.remove(block)
|
||||
|
||||
def add_block(self, block: 'BasicBlock') -> None:
|
||||
"""
|
||||
Add the given block to the graph, assuming it does not already exist.
|
||||
"""
|
||||
if block not in self.blocks:
|
||||
self.blocks.append(block)
|
||||
|
||||
def has_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> bool:
|
||||
"""
|
||||
True iff the edge between head and tail exists in the graph.
|
||||
"""
|
||||
return tail in head.succs
|
||||
|
||||
def remove_edge(self, head: 'BasicBlock', tail: 'BasicBlock') -> None:
|
||||
"""Remove the CFG edge that goes from head to tail."""
|
||||
if tail in head.succs:
|
||||
head.succs.remove(tail)
|
||||
if head in tail.preds:
|
||||
tail.preds.remove(head)
|
||||
|
||||
def add_edge(self, head: 'BasicBlock', tail: 'BasicBlock'):
|
||||
"""Add a CFG edge that goes from head to tail."""
|
||||
if tail not in head.succs:
|
||||
head.succs.append(tail)
|
||||
if head not in tail.preds:
|
||||
tail.preds.append(head)
|
||||
|
||||
def get_blocks_by_pc(self, pc: int) -> t.List['BasicBlock']:
|
||||
"""Return the blocks whose spans include the given program counter value."""
|
||||
blocks = []
|
||||
for block in self.blocks:
|
||||
if block.entry <= pc <= block.exit:
|
||||
blocks.append(block)
|
||||
return blocks
|
||||
|
||||
def get_block_by_ident(self, ident: str) -> 'BasicBlock':
|
||||
"""Return the block with the specified identifier, if it exists."""
|
||||
for block in self.blocks:
|
||||
if block.ident() == ident:
|
||||
return block
|
||||
return None
|
||||
|
||||
def recalc_preds(self) -> None:
|
||||
"""
|
||||
Given a cfg where block successor lists are populated,
|
||||
also repopulate the predecessor lists, after emptying them.
|
||||
"""
|
||||
for block in self.blocks:
|
||||
block.preds = []
|
||||
for block in self.blocks:
|
||||
for successor in block.succs:
|
||||
successor.preds.append(block)
|
||||
|
||||
def reaches(self, block: 'BasicBlock', dests: t.Iterable['BasicBlock']) -> bool:
|
||||
"""
|
||||
Determines if a block can reach any of the given destination blocks
|
||||
|
||||
Args:
|
||||
block: Any block that is part of the tac_cfg the class was initialised with
|
||||
dests: A list of dests to check reachability with
|
||||
"""
|
||||
if block in dests:
|
||||
return True
|
||||
queue = [block]
|
||||
traversed = []
|
||||
while queue:
|
||||
curr_block = queue.pop()
|
||||
traversed.append(curr_block)
|
||||
for b in dests:
|
||||
if b in curr_block.succs:
|
||||
return True
|
||||
for b in curr_block.succs:
|
||||
if b not in traversed:
|
||||
queue.append(b)
|
||||
return False
|
||||
|
||||
def transitive_closure(self, origin_addresses: t.Iterable[int]) \
|
||||
-> t.Iterable['BasicBlock']:
|
||||
"""
|
||||
Return a list of blocks reachable from the input addresses.
|
||||
|
||||
Args:
|
||||
origin_addresses: the input addresses blocks from which are reachable
|
||||
to be returned.
|
||||
"""
|
||||
|
||||
# Populate the work queue with the origin blocks for the transitive closure.
|
||||
queue = []
|
||||
for address in origin_addresses:
|
||||
for block in self.get_blocks_by_pc(address):
|
||||
if block not in queue:
|
||||
queue.append(block)
|
||||
reached = []
|
||||
|
||||
# Follow all successor edges until we can find no more new blocks.
|
||||
while queue:
|
||||
block = queue.pop()
|
||||
reached.append(block)
|
||||
for succ in block.succs:
|
||||
if succ not in queue and succ not in reached:
|
||||
queue.append(succ)
|
||||
|
||||
return reached
|
||||
|
||||
def remove_unreachable_blocks(self, origin_addresses: t.Iterable[int] = [0]) \
|
||||
-> t.Iterable['BasicBlock']:
|
||||
"""
|
||||
Remove all blocks not reachable from the program entry point.
|
||||
|
||||
NB: if not all jumps have been resolved, unreached blocks may actually
|
||||
be reachable.
|
||||
|
||||
Args:
|
||||
origin_addresses: default value: [0], entry addresses, blocks from which
|
||||
are unreachable to be deleted.
|
||||
|
||||
Returns:
|
||||
An iterable of the blocks which were removed.
|
||||
"""
|
||||
|
||||
reached = self.transitive_closure(origin_addresses)
|
||||
removed = []
|
||||
for block in list(self.blocks):
|
||||
if block not in reached:
|
||||
removed.append(block)
|
||||
self.remove_block(block)
|
||||
return removed
|
||||
|
||||
def edge_list(self) -> t.Iterable[t.Tuple['BasicBlock', 'BasicBlock']]:
|
||||
"""
|
||||
Returns:
|
||||
a list of the CFG's edges, with each edge in the form
|
||||
`(pred, succ)` where pred and succ are object references.
|
||||
"""
|
||||
return [(p, s) for p in self.blocks for s in p.succs]
|
||||
|
||||
def sorted_traversal(self, key=lambda b: b.entry, reverse=False) -> t.Generator['BasicBlock', None, None]:
|
||||
"""
|
||||
Generator for a sorted shallow copy of BasicBlocks contained in this graph.
|
||||
|
||||
Args:
|
||||
key: A function of one argument that is used to extract a comparison key
|
||||
from each block. By default, the comparison key is
|
||||
:obj:`BasicBlock.entry`.
|
||||
reverse: If set to `True`, then the blocks are sorted as if each
|
||||
comparison were reversed. Default is `False`.
|
||||
|
||||
Returns:
|
||||
A generator of :obj:`BasicBlock` objects, yielded in order according to
|
||||
`key` and `reverse`.
|
||||
"""
|
||||
# Create a new list of sorted blocks and yield from it
|
||||
yield from sorted(self.blocks, key=key, reverse=reverse)
|
||||
|
||||
def accept(self, visitor: patterns.Visitor,
|
||||
generator: t.Generator['BasicBlock', None, None] = None):
|
||||
"""
|
||||
Visitor design pattern: accepts a Visitor instance and visits every node
|
||||
in the CFG in an arbitrary order.
|
||||
|
||||
Args:
|
||||
visitor: instance of a Visitor
|
||||
generator: generator from which :obj:`BasicBlock` objects will be
|
||||
retrieved when recursing. By default the blocks are recursed in
|
||||
an arbitrary order.
|
||||
"""
|
||||
super().accept(visitor)
|
||||
|
||||
generator = generator or self.blocks
|
||||
|
||||
if len(self.blocks) > 0 and visitor.can_visit(type(self.blocks[0])):
|
||||
for b in generator:
|
||||
b.accept(visitor)
|
||||
|
||||
@property
|
||||
def has_unresolved_jump(self) -> bool:
|
||||
"""True iff any block in this cfg contains an unresolved jump."""
|
||||
return any(b.has_unresolved_jump for b in self.blocks)
|
||||
|
||||
|
||||
class BasicBlock(patterns.Visitable):
|
||||
"""
|
||||
Abstract base class for a single basic block (node) in a CFG. Each block has
|
||||
references to its predecessor and successor nodes in the graph structure.
|
||||
|
||||
A BasicBlock must contain exactly one entry point at the start and
|
||||
exactly one exit point at the end, with no branching in between.
|
||||
That is, program flow must be linear/sequential within a basic block.
|
||||
|
||||
Args:
|
||||
entry (int, default None): entry index.
|
||||
exit (int, default None): exit index.
|
||||
|
||||
Raises:
|
||||
ValueError: if entry or exit is a negative int.
|
||||
"""
|
||||
|
||||
_STR_SEP = "---"
|
||||
|
||||
@abc.abstractmethod
|
||||
def __init__(self, entry: int = None, exit: int = None):
|
||||
if entry is not None and entry < 0:
|
||||
raise ValueError("entry must be a positive integer or zero")
|
||||
|
||||
if exit is not None and exit < 0:
|
||||
raise ValueError("exit must be a positive integer or zero")
|
||||
|
||||
self.entry = entry
|
||||
"""Index of the first operation contained in this node."""
|
||||
|
||||
self.exit = exit
|
||||
"""Index of the last operation contained in this node."""
|
||||
|
||||
self.preds = []
|
||||
"""List of nodes which pass control to this node (predecessors)."""
|
||||
|
||||
self.succs = []
|
||||
"""List of nodes which receive control from this node (successors)."""
|
||||
|
||||
self.has_unresolved_jump = False
|
||||
"""True if the node contains a jump whose destination is a variable."""
|
||||
|
||||
self.ident_suffix = ""
|
||||
"""
|
||||
Extra information to be appended to this block's identifier.
|
||||
Used, for example, to differentiate duplicated blocks.
|
||||
"""
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the number of lines of code contained within this block."""
|
||||
if self.exit is None or self.entry is None:
|
||||
return 0
|
||||
return self.exit - self.entry
|
||||
|
||||
def __str__(self):
|
||||
entry, exit = map(lambda n: hex(n) if n is not None else 'Unknown',
|
||||
(self.entry, self.exit))
|
||||
b_id = self.ident() if self.entry is not None else "Unidentified"
|
||||
head = "Block {}\n[{}:{}]".format(b_id, entry, exit)
|
||||
pred = "Predecessors: [{}]".format(", ".join(b.ident() for b in sorted(self.preds)))
|
||||
succ = "Successors: [{}]".format(", ".join(b.ident() for b in sorted(self.succs)))
|
||||
unresolved = "\nHas unresolved jump." if self.has_unresolved_jump else ""
|
||||
return "\n".join([head, self._STR_SEP, pred, succ]) + unresolved
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
Compare BasicBlocks based on their entry program counter values.
|
||||
"""
|
||||
if self.entry is None or other.entry is None:
|
||||
return False
|
||||
return (self.entry < other.entry) or \
|
||||
(self.entry == other.entry and self.ident_suffix < other.ident_suffix)
|
||||
|
||||
def ident(self) -> str:
|
||||
"""
|
||||
Returns this block's unique identifier, which is its entry value.
|
||||
|
||||
Raises:
|
||||
ValueError if the block's entry is None.
|
||||
"""
|
||||
if self.entry is None:
|
||||
raise ValueError("Can't compute ident() for block with unknown entry")
|
||||
return hex(self.entry) + self.ident_suffix
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""opcodes.py: Definitions of all EVM opcodes, and related utility functions."""
|
||||
|
||||
|
||||
class OpCode:
|
||||
"""An EVM opcode."""
|
||||
|
||||
def __init__(self, name: str, code: int, pop: int, push: int):
|
||||
"""
|
||||
Args:
|
||||
name (str): Human-readable opcode, such as opcode PUSH1.
|
||||
code (int): The instruction byte itself.
|
||||
pop (int): The number of stack elements this op pops.
|
||||
push (int): The number of stack elements this op pushes.
|
||||
"""
|
||||
self.name = name
|
||||
self.code = code
|
||||
self.pop = pop
|
||||
self.push = push
|
||||
|
||||
def stack_delta(self) -> int:
|
||||
"""Return the net effect on the stack size of running this operation."""
|
||||
return self.push - self.pop
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{0} object {1}, {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.__str__()
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return self.code == other.code
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self.code.__hash__()
|
||||
|
||||
# Special cases for kind one, such as CALLVALUE
|
||||
# Those opcodes do not need anything from stack, but will give related dynamic info
|
||||
def is_kind_one(self) -> bool:
|
||||
special = {ADDRESS.name, ORIGIN.name, CALLER.name, CALLVALUE.name, CALLDATASIZE.name,
|
||||
CODESIZE.name, GASPRICE.name, RETURNDATASIZE.name, COINBASE.name, TIMESTAMP.name,
|
||||
NUMBER.name, DIFFICULTY.name, GASLIMIT.name, PC.name, MSIZE.name, GAS.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for kind two, such as CALLDATALOAD
|
||||
# Need one or more stack arguments and related dynamic info
|
||||
def is_kind_two(self) -> bool:
|
||||
special = {SHA3.name, BALANCE.name, CALLDATALOAD.name, EXTCODESIZE.name, BLOCKHASH.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for part of kind three load, SLOAD
|
||||
def is_kind_three_load(self) -> bool:
|
||||
special = {SLOAD.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for part of kind three store, like MSTORE
|
||||
def is_kind_three_store_one(self) -> bool:
|
||||
special = {MSTORE.name, MSTORE8.name, SSTORE.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for some other store operations, they are special
|
||||
# since they do need some arguments from the stack and then get the related data
|
||||
# to store them into the memory
|
||||
def is_kind_three_store_two(self) -> bool:
|
||||
special = {CALLDATACOPY.name, CODECOPY.name, EXTCODECOPY.name, RETURNDATACOPY.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for four call opcodes
|
||||
def is_kind_four(self) -> bool:
|
||||
special = {CALL.name, CALLCODE.name, DELEGATECALL.name, STATICCALL.name}
|
||||
return self.name in special
|
||||
|
||||
def is_kind_five(self) -> bool:
|
||||
special = {CREATE.name, CREATE2.name}
|
||||
return self.name in special
|
||||
|
||||
def op_pc_gap(self) -> int:
|
||||
if self.is_push():
|
||||
return int(self.name[4]) + 1
|
||||
else:
|
||||
return 1
|
||||
|
||||
def is_push(self) -> bool:
|
||||
"""Predicate: opcode is a push operation."""
|
||||
return PUSH1.code <= self.code <= PUSH32.code
|
||||
|
||||
def is_swap(self) -> bool:
|
||||
"""Predicate: opcode is a swap operation."""
|
||||
return SWAP1.code <= self.code <= SWAP16.code
|
||||
|
||||
def is_dup(self) -> bool:
|
||||
"""Predicate: opcode is a dup operation."""
|
||||
return DUP1.code <= self.code <= DUP16.code
|
||||
|
||||
def is_log(self) -> bool:
|
||||
"""Predicate: opcode is a log operation."""
|
||||
return LOG0.code <= self.code <= LOG4.code
|
||||
|
||||
def is_missing(self) -> bool:
|
||||
return self.code not in BYTECODES
|
||||
|
||||
def is_invalid(self) -> bool:
|
||||
return (self.code == INVALID.code) or self.is_missing()
|
||||
|
||||
# Zoey - adding shift operation
|
||||
# def is_shift(self) -> bool:
|
||||
# """Predicate: opcode is a shift operation."""
|
||||
# return SHL.code <= self.code <= SAR.code
|
||||
|
||||
def is_arithmetic(self) -> bool:
|
||||
"""Predicate: opcode's result can be calculated from its inputs alone."""
|
||||
return (ADD.code <= self.code <= SIGNEXTEND.code) or \
|
||||
(LT.code <= self.code <= BYTE.code) or \
|
||||
(SHA3.code <= self.code <= SHA3.code) \
|
||||
# or self.is_shift() # Zoey - adding shift operation
|
||||
|
||||
|
||||
|
||||
def is_memory(self) -> bool:
|
||||
"""Predicate: opcode operates on memory"""
|
||||
return MLOAD.code <= self.code <= MSTORE8.code
|
||||
|
||||
def is_storage(self) -> bool:
|
||||
"""Predicate: opcode operates on storage ('the tape')"""
|
||||
return SLOAD.code <= self.code <= SSTORE.code
|
||||
|
||||
def is_call(self) -> bool:
|
||||
"""Predicate: opcode calls an external contract"""
|
||||
return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,)
|
||||
|
||||
def alters_flow(self) -> bool:
|
||||
"""Predicate: opcode alters EVM control flow."""
|
||||
return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts()
|
||||
|
||||
def is_exception(self) -> bool:
|
||||
"""Predicate: opcode causes the EVM to throw an exception."""
|
||||
return (self.code in (THROW.code, THROWI.code, REVERT.code)) \
|
||||
or self.is_invalid()
|
||||
|
||||
def halts(self) -> bool:
|
||||
"""Predicate: opcode causes the EVM to halt."""
|
||||
halt_codes = (
|
||||
STOP.code,
|
||||
RETURN.code,
|
||||
SELFDESTRUCT.code,
|
||||
THROW.code,
|
||||
REVERT.code,
|
||||
)
|
||||
return (self.code in halt_codes) or self.is_invalid()
|
||||
|
||||
def possibly_halts(self) -> bool:
|
||||
"""Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)"""
|
||||
return self.halts() or self.code == THROWI.code
|
||||
|
||||
def push_len(self) -> int:
|
||||
"""Return the number of bytes the given PUSH instruction pushes."""
|
||||
return self.code - PUSH1.code + 1 if self.is_push() else 0
|
||||
|
||||
def log_len(self) -> int:
|
||||
"""Return the number of topics the given LOG instruction includes."""
|
||||
return self.code - LOG0.code if self.is_log() else 0
|
||||
|
||||
|
||||
# Construct all EVM opcodes
|
||||
# Arithmetic Ops and STOP
|
||||
STOP = OpCode("STOP", 0x00, 0, 0)
|
||||
ADD = OpCode("ADD", 0x01, 2, 1)
|
||||
MUL = OpCode("MUL", 0x02, 2, 1)
|
||||
SUB = OpCode("SUB", 0x03, 2, 1)
|
||||
DIV = OpCode("DIV", 0x04, 2, 1)
|
||||
SDIV = OpCode("SDIV", 0x05, 2, 1)
|
||||
MOD = OpCode("MOD", 0x06, 2, 1)
|
||||
SMOD = OpCode("SMOD", 0x07, 2, 1)
|
||||
ADDMOD = OpCode("ADDMOD", 0x08, 3, 1)
|
||||
MULMOD = OpCode("MULMOD", 0x09, 3, 1)
|
||||
EXP = OpCode("EXP", 0x0a, 2, 1)
|
||||
SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1)
|
||||
|
||||
# Comparison and Bitwise Logic
|
||||
LT = OpCode("LT", 0x10, 2, 1)
|
||||
GT = OpCode("GT", 0x11, 2, 1)
|
||||
SLT = OpCode("SLT", 0x12, 2, 1)
|
||||
SGT = OpCode("SGT", 0x13, 2, 1)
|
||||
EQ = OpCode("EQ", 0x14, 2, 1)
|
||||
ISZERO = OpCode("ISZERO", 0x15, 1, 1)
|
||||
AND = OpCode("AND", 0x16, 2, 1)
|
||||
OR = OpCode("OR", 0x17, 2, 1)
|
||||
XOR = OpCode("XOR", 0x18, 2, 1)
|
||||
NOT = OpCode("NOT", 0x19, 1, 1)
|
||||
BYTE = OpCode("BYTE", 0x1a, 2, 1)
|
||||
|
||||
SHA3 = OpCode("SHA3", 0x20, 2, 1)
|
||||
# ZOEY - adding shift operation
|
||||
SHL = OpCode("SHL", 0x1b, 2, 1) # 0x1b is the opcode for SHL
|
||||
SHR = OpCode("SHR", 0x1c, 2, 1) # 0x1c is the opcode for SHR
|
||||
SAR = OpCode("SAR", 0x1d, 2, 1) # 0x1d is the opcode for SAR
|
||||
# Update OPCODES dictionary
|
||||
|
||||
|
||||
# Environmental Information
|
||||
ADDRESS = OpCode("ADDRESS", 0x30, 0, 1)
|
||||
BALANCE = OpCode("BALANCE", 0x31, 1, 1)
|
||||
ORIGIN = OpCode("ORIGIN", 0x32, 0, 1)
|
||||
CALLER = OpCode("CALLER", 0x33, 0, 1)
|
||||
CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1)
|
||||
CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1)
|
||||
CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1)
|
||||
CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0)
|
||||
CODESIZE = OpCode("CODESIZE", 0x38, 0, 1)
|
||||
CODECOPY = OpCode("CODECOPY", 0x39, 3, 0)
|
||||
GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1)
|
||||
EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1)
|
||||
EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0)
|
||||
|
||||
# Block Information
|
||||
BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1)
|
||||
COINBASE = OpCode("COINBASE", 0x41, 0, 1)
|
||||
TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1)
|
||||
NUMBER = OpCode("NUMBER", 0x43, 0, 1)
|
||||
DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1)
|
||||
GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1)
|
||||
|
||||
# Stack, Memory, Storage, Flow
|
||||
POP = OpCode("POP", 0x50, 1, 0)
|
||||
MLOAD = OpCode("MLOAD", 0x51, 1, 1)
|
||||
MSTORE = OpCode("MSTORE", 0x52, 2, 0)
|
||||
MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0)
|
||||
SLOAD = OpCode("SLOAD", 0x54, 1, 1)
|
||||
SSTORE = OpCode("SSTORE", 0x55, 2, 0)
|
||||
JUMP = OpCode("JUMP", 0x56, 1, 0)
|
||||
JUMPI = OpCode("JUMPI", 0x57, 2, 0)
|
||||
PC = OpCode("PC", 0x58, 0, 1)
|
||||
MSIZE = OpCode("MSIZE", 0x59, 0, 1)
|
||||
GAS = OpCode("GAS", 0x5a, 0, 1)
|
||||
JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0)
|
||||
|
||||
PUSH1 = OpCode("PUSH1", 0x60, 0, 1)
|
||||
PUSH2 = OpCode("PUSH2", 0x61, 0, 1)
|
||||
PUSH3 = OpCode("PUSH3", 0x62, 0, 1)
|
||||
PUSH4 = OpCode("PUSH4", 0x63, 0, 1)
|
||||
PUSH5 = OpCode("PUSH5", 0x64, 0, 1)
|
||||
PUSH6 = OpCode("PUSH6", 0x65, 0, 1)
|
||||
PUSH7 = OpCode("PUSH7", 0x66, 0, 1)
|
||||
PUSH8 = OpCode("PUSH8", 0x67, 0, 1)
|
||||
PUSH9 = OpCode("PUSH9", 0x68, 0, 1)
|
||||
PUSH10 = OpCode("PUSH10", 0x69, 0, 1)
|
||||
PUSH11 = OpCode("PUSH11", 0x6a, 0, 1)
|
||||
PUSH12 = OpCode("PUSH12", 0x6b, 0, 1)
|
||||
PUSH13 = OpCode("PUSH13", 0x6c, 0, 1)
|
||||
PUSH14 = OpCode("PUSH14", 0x6d, 0, 1)
|
||||
PUSH15 = OpCode("PUSH15", 0x6e, 0, 1)
|
||||
PUSH16 = OpCode("PUSH16", 0x6f, 0, 1)
|
||||
PUSH17 = OpCode("PUSH17", 0x70, 0, 1)
|
||||
PUSH18 = OpCode("PUSH18", 0x71, 0, 1)
|
||||
PUSH19 = OpCode("PUSH19", 0x72, 0, 1)
|
||||
PUSH20 = OpCode("PUSH20", 0x73, 0, 1)
|
||||
PUSH21 = OpCode("PUSH21", 0x74, 0, 1)
|
||||
PUSH22 = OpCode("PUSH22", 0x75, 0, 1)
|
||||
PUSH23 = OpCode("PUSH23", 0x76, 0, 1)
|
||||
PUSH24 = OpCode("PUSH24", 0x77, 0, 1)
|
||||
PUSH25 = OpCode("PUSH25", 0x78, 0, 1)
|
||||
PUSH26 = OpCode("PUSH26", 0x79, 0, 1)
|
||||
PUSH27 = OpCode("PUSH27", 0x7a, 0, 1)
|
||||
PUSH28 = OpCode("PUSH28", 0x7b, 0, 1)
|
||||
PUSH29 = OpCode("PUSH29", 0x7c, 0, 1)
|
||||
PUSH30 = OpCode("PUSH30", 0x7d, 0, 1)
|
||||
PUSH31 = OpCode("PUSH31", 0x7e, 0, 1)
|
||||
PUSH32 = OpCode("PUSH32", 0x7f, 0, 1)
|
||||
|
||||
DUP1 = OpCode("DUP1", 0x80, 1, 2)
|
||||
DUP2 = OpCode("DUP2", 0x81, 2, 3)
|
||||
DUP3 = OpCode("DUP3", 0x82, 3, 4)
|
||||
DUP4 = OpCode("DUP4", 0x83, 4, 5)
|
||||
DUP5 = OpCode("DUP5", 0x84, 5, 6)
|
||||
DUP6 = OpCode("DUP6", 0x85, 6, 7)
|
||||
DUP7 = OpCode("DUP7", 0x86, 7, 8)
|
||||
DUP8 = OpCode("DUP8", 0x87, 8, 9)
|
||||
DUP9 = OpCode("DUP9", 0x88, 9, 10)
|
||||
DUP10 = OpCode("DUP10", 0x89, 10, 11)
|
||||
DUP11 = OpCode("DUP11", 0x8a, 11, 12)
|
||||
DUP12 = OpCode("DUP12", 0x8b, 12, 13)
|
||||
DUP13 = OpCode("DUP13", 0x8c, 13, 14)
|
||||
DUP14 = OpCode("DUP14", 0x8d, 14, 15)
|
||||
DUP15 = OpCode("DUP15", 0x8e, 15, 16)
|
||||
DUP16 = OpCode("DUP16", 0x8f, 16, 17)
|
||||
|
||||
SWAP1 = OpCode("SWAP1", 0x90, 2, 2)
|
||||
SWAP2 = OpCode("SWAP2", 0x91, 3, 3)
|
||||
SWAP3 = OpCode("SWAP3", 0x92, 4, 4)
|
||||
SWAP4 = OpCode("SWAP4", 0x93, 5, 5)
|
||||
SWAP5 = OpCode("SWAP5", 0x94, 6, 6)
|
||||
SWAP6 = OpCode("SWAP6", 0x95, 7, 7)
|
||||
SWAP7 = OpCode("SWAP7", 0x96, 8, 8)
|
||||
SWAP8 = OpCode("SWAP8", 0x97, 9, 9)
|
||||
SWAP9 = OpCode("SWAP9", 0x98, 10, 10)
|
||||
SWAP10 = OpCode("SWAP10", 0x99, 11, 11)
|
||||
SWAP11 = OpCode("SWAP11", 0x9a, 12, 12)
|
||||
SWAP12 = OpCode("SWAP12", 0x9b, 13, 13)
|
||||
SWAP13 = OpCode("SWAP13", 0x9c, 14, 14)
|
||||
SWAP14 = OpCode("SWAP14", 0x9d, 15, 15)
|
||||
SWAP15 = OpCode("SWAP15", 0x9e, 16, 16)
|
||||
SWAP16 = OpCode("SWAP16", 0x9f, 17, 17)
|
||||
|
||||
# Logging
|
||||
LOG0 = OpCode("LOG0", 0xa0, 2, 0)
|
||||
LOG1 = OpCode("LOG1", 0xa1, 3, 0)
|
||||
LOG2 = OpCode("LOG2", 0xa2, 4, 0)
|
||||
LOG3 = OpCode("LOG3", 0xa3, 5, 0)
|
||||
LOG4 = OpCode("LOG4", 0xa4, 6, 0)
|
||||
|
||||
# System Operations
|
||||
CREATE = OpCode("CREATE", 0xf0, 3, 1)
|
||||
CREATE2 = OpCode("CREATE2", 0xf5, 4, 1)
|
||||
CALL = OpCode("CALL", 0xf1, 7, 1)
|
||||
CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1)
|
||||
RETURN = OpCode("RETURN", 0xf3, 2, 0)
|
||||
DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1)
|
||||
INVALID = OpCode("INVALID", 0xfe, 0, 0)
|
||||
SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0)
|
||||
|
||||
# New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM
|
||||
REVERT = OpCode("REVERT", 0xfd, 2, 0)
|
||||
RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1)
|
||||
RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0)
|
||||
STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1)
|
||||
|
||||
# TAC Operations
|
||||
# These are not EVM opcodes, but they are used by the three-address code
|
||||
NOP = OpCode("NOP", -1, 0, 0)
|
||||
CONST = OpCode("CONST", -2, 0, 0)
|
||||
LOG = OpCode("LOG", -3, 0, 0)
|
||||
THROW = OpCode("THROW", -4, 0, 0)
|
||||
THROWI = OpCode("THROWI", -5, 0, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
# Produce mappings from names and instruction codes to opcode objects
|
||||
OPCODES = {
|
||||
code.name: code
|
||||
for code in globals().values()
|
||||
if isinstance(code, OpCode)
|
||||
}
|
||||
"""Dictionary mapping of opcode string names to EVM OpCode objects"""
|
||||
|
||||
# Handle incorrect opcode name from go-ethereum disasm
|
||||
OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"]
|
||||
|
||||
BYTECODES = {code.code: code for code in OPCODES.values()}
|
||||
"""Dictionary mapping of byte values to EVM OpCode objects"""
|
||||
|
||||
|
||||
def opcode_by_name(name: str) -> OpCode:
|
||||
"""
|
||||
Mapping: Retrieves the named OpCode object (case-insensitive).
|
||||
|
||||
Throws:
|
||||
LookupError: if there is no opcode defined with the given name.
|
||||
"""
|
||||
name = name.upper()
|
||||
if name not in OPCODES:
|
||||
raise LookupError("No opcode named '{}'.".format(name))
|
||||
return OPCODES[name]
|
||||
|
||||
|
||||
def opcode_by_value(val: int) -> OpCode:
|
||||
"""
|
||||
Mapping: Retrieves the OpCode object with the given value.
|
||||
|
||||
Throws:
|
||||
LookupError: if there is no opcode defined with the given value.
|
||||
"""
|
||||
if val not in BYTECODES:
|
||||
raise LookupError("No opcode with value '0x{:02X}'.".format(val))
|
||||
return BYTECODES[val]
|
||||
|
||||
|
||||
def missing_opcode(val: int) -> OpCode:
|
||||
"""
|
||||
Produces a new OpCode with the given value, as long as that is
|
||||
an unknown code.
|
||||
|
||||
Throws:
|
||||
ValueError: if there is an opcode defined with the given value.
|
||||
"""
|
||||
if val in BYTECODES:
|
||||
raise ValueError("Opcode {} exists.")
|
||||
return OpCode("MISSING", val, 0, 0)
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""evm_efg.py: parse the transaction trace and build a execution flow graph (efg)"""
|
||||
"""解析以太坊交易跟踪(trace)并构建执行流图(Execution Flow Graph,EFG)的代码。
|
||||
|
||||
该代码由以下几个类组成:
|
||||
EVMBasicBlock:表示执行流图中的一个基本块,包括图结构中其父节点和子节点。
|
||||
EVMBasicBlock类继承自 cfg.BasicBlock 类,它表示控制流图中的一个基本块,包括其入口和出口,以及其父和子节点。
|
||||
EVMOp:表示一个EVM(以太坊虚拟机)操作。
|
||||
blocks_from_ops:从一系列 EVM 操作中生成基本块。
|
||||
|
||||
解析以太坊交易跟踪后,可以通过 blocks_from_ops 函数得到一个基本块的列表。
|
||||
每个基本块都包含一些 EVM 操作(以太坊虚拟机操作),这些操作被认为是连续的。
|
||||
每个操作都由一个EVMOp对象表示。
|
||||
|
||||
EVMOp类表示一个EVM操作,<PC,opcode, value, alue_extra, depth, loc,call_number>:
|
||||
程序计数器(PC):一个指向操作代码的指针。
|
||||
操作码(opcode):操作的类型,如 ADD、MUL、JUMP 等。
|
||||
值(value):操作数,如果是 PUSH 操作,它就是一个常量整数值。
|
||||
扩展值(value_extra):特定类型的操作的附加参数,如 CALL、CALLCODE、DELEGATECALL 和 STATICCALL。
|
||||
调用深度(call_depth):调用的智能合约的深度。
|
||||
操作码计数器(loc):操作的位置。
|
||||
调用编号(call_number):在 EVM 执行过程中,每个调用都分配了一个唯一的编号。
|
||||
|
||||
blocks_from_ops 函数将一系列 EVMOp 对象转换成 EVMBasicBlock 对象,并返回一个包含所有基本块的列表。
|
||||
函数遍历一系列 EVMOp 对象,每当它遇到 CALL 操作时,就增加 call_number。
|
||||
每个操作都将自己分配给一个基本块(EVMBasicBlock),该基本块由变量 current 表示。每个基本块将操作作为其成员存储在其 evm_ops 属性中。
|
||||
"""
|
||||
|
||||
|
||||
import typing as t
|
||||
|
||||
import src.cfg as cfg
|
||||
import src.opcodes as opcodes
|
||||
|
||||
|
||||
class EVMBasicBlock(cfg.BasicBlock):
|
||||
"""
|
||||
Represents a single basic block in the Execution Flow Graph (EFG), including
|
||||
its parent and child nodes in the graph structure.
|
||||
"""
|
||||
|
||||
def __init__(self, entry: int = None, exit: int = None,
|
||||
evm_ops: t.List['EVMOp'] = None):
|
||||
"""
|
||||
Creates a new basic block containing operations between the
|
||||
specified entry and exit instruction counters (inclusive).
|
||||
|
||||
Args:
|
||||
entry: block entry point program counter
|
||||
exit: block exit point program counter
|
||||
evm_ops: a sequence of operations that constitute this BasicBlock's code. Default empty.
|
||||
"""
|
||||
super().__init__(entry, exit)
|
||||
|
||||
self.evm_ops = evm_ops if evm_ops is not None else []
|
||||
"""List of EVMOps contained within this EVMBasicBlock"""
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""Returns a string representation of this block and all ops in it."""
|
||||
super_str = super().__str__()
|
||||
op_seq = "\n".join(str(op) for op in self.evm_ops)
|
||||
return "\n".join([super_str, self._STR_SEP, op_seq])
|
||||
|
||||
def split(self, entry: int) -> 'EVMBasicBlock':
|
||||
"""
|
||||
Splits current block into a new block, starting at the specified
|
||||
entry op index. Returns a new EVMBasicBlock with no preds or succs.
|
||||
|
||||
Args:
|
||||
entry: unique index of EVMOp from which the block should be split. The
|
||||
EVMOp at this index will become the first EVMOp of the new BasicBlock.
|
||||
"""
|
||||
# Create the new block.
|
||||
new = type(self)(entry, self.exit, self.evm_ops[entry - self.entry:])
|
||||
|
||||
# Update the current node.
|
||||
self.exit = entry - 1
|
||||
self.evm_ops = self.evm_ops[:entry - self.entry]
|
||||
|
||||
# Update the block pointer in each line object
|
||||
self.__update_evmop_refs()
|
||||
new.__update_evmop_refs()
|
||||
|
||||
return new
|
||||
|
||||
def __update_evmop_refs(self):
|
||||
# Update references back to parent block for each opcode
|
||||
# This needs to be done when a block is split
|
||||
for op in self.evm_ops:
|
||||
op.block = self
|
||||
|
||||
|
||||
class EVMOp:
|
||||
"""
|
||||
Represents a single EVM operation.
|
||||
"""
|
||||
def __init__(self, pc: int, opcode: opcodes.OpCode, value: int = None, value_extra: int = None,
|
||||
call_depth: int = None, loc: int = None, call_number: int = None):
|
||||
"""
|
||||
Create a new EVMOp object from the given params which should correspond to
|
||||
disasm output.
|
||||
"""
|
||||
|
||||
# Programming counter
|
||||
self.pc = pc
|
||||
|
||||
# VM operation code
|
||||
self.opcode = opcode
|
||||
|
||||
# Constant int value or None. Not only PUSH opcode has this value
|
||||
self.value = value
|
||||
|
||||
# Four call opcodes has a special type: 0,1, they need extra type
|
||||
# value_extra is used to store more arguments for call, callcode, delegatecall, staticcall
|
||||
self.value_extra = value_extra
|
||||
|
||||
# Call depth is the depth of the called smart contracts
|
||||
self.call_depth = call_depth
|
||||
|
||||
# Loc is the programming opcode counter
|
||||
self.loc = loc
|
||||
|
||||
# Call number, if you are interested in the details, please refer to our paper
|
||||
self.call_number = call_number
|
||||
|
||||
|
||||
# The default string of the opcodes
|
||||
def __str__(self):
|
||||
if self.value is None:
|
||||
return "{0} {1}".format(self.pc, self.opcode)
|
||||
else:
|
||||
if self.value_extra is None:
|
||||
return "{0} {1} {2}".format(self.pc, self.opcode, hex(self.value))
|
||||
else:
|
||||
return "{0} {1} {2} {3}".format(self.pc, self.opcode, hex(self.value), hex(self.value_extra))
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} object {1}: {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.__str__()
|
||||
)
|
||||
|
||||
|
||||
# Convert the trace (sequence of opcodes) into basic blocks
|
||||
def blocks_from_ops(ops: t.Iterable[EVMOp]) -> t.Iterable[EVMBasicBlock]:
|
||||
"""
|
||||
Process a sequence of EVMOps and create a sequence of EVMBasicBlocks.
|
||||
|
||||
Args:
|
||||
ops: sequence of EVMOps to be put into blocks.
|
||||
|
||||
Returns:
|
||||
List of BasicBlocks from the input opcodes.
|
||||
"""
|
||||
blocks = []
|
||||
|
||||
# details for block currently being processed
|
||||
entry, exit = (0, len(ops) - 1) if len(ops) > 0 \
|
||||
else (None, None)
|
||||
current = EVMBasicBlock(entry, exit)
|
||||
|
||||
call_number = 0
|
||||
for i, op in enumerate(ops):
|
||||
if op.pc == 0 and i != 0:
|
||||
call_number += 1
|
||||
op.call_number = call_number
|
||||
|
||||
# Linear scan of all EVMOps to create initial EVMBasicBlocks
|
||||
call_depth = 0
|
||||
for i, op in enumerate(ops):
|
||||
op.block = current
|
||||
current.evm_ops.append(op)
|
||||
|
||||
# Remove all the intra blocks and only focus on the inter edges
|
||||
# add a condition to create a new block when encountering the new contract 0;
|
||||
if op.pc == 0 and i == 0:
|
||||
call_depth = 1
|
||||
elif op.pc == 0 and i != 0:
|
||||
call_depth += 1
|
||||
new = current.split(i)
|
||||
blocks.append(current)
|
||||
current = new
|
||||
|
||||
# Add CREATE and CREATE2
|
||||
elif op.opcode.is_kind_four() or op.opcode.is_kind_five():
|
||||
# Make sure conditions such as 238;ADD 239;CALL will not be split
|
||||
if ops[i-1].call_number == op.call_number \
|
||||
and op.pc - ops[i - 1].pc == ops[i - 1].opcode.op_pc_gap() \
|
||||
and not ops[i-1].opcode.possibly_halts():
|
||||
pass
|
||||
else:
|
||||
call_depth -= 1
|
||||
new = current.split(i)
|
||||
blocks.append(current)
|
||||
current = new
|
||||
|
||||
# Always add last block if its last instruction does not alter flow
|
||||
elif i == len(ops) - 1:
|
||||
blocks.append(current)
|
||||
|
||||
op.loc = i
|
||||
op.call_depth = call_depth
|
||||
|
||||
return blocks
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""exporter.py: abstract classes for exporting decompiler state"""
|
||||
|
||||
import abc
|
||||
import csv
|
||||
import logging
|
||||
import os
|
||||
|
||||
import src.cfg as efg
|
||||
import src.function as function
|
||||
import src.opcodes as opcodes
|
||||
import src.patterns as patterns
|
||||
import src.tac_efg as tac_efg
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class Exporter(abc.ABC):
|
||||
def __init__(self, source: object):
|
||||
"""
|
||||
Args:
|
||||
source: object instance to be exported
|
||||
"""
|
||||
self.source = source
|
||||
|
||||
@abc.abstractmethod
|
||||
def export(self):
|
||||
"""
|
||||
Exports the source object to an implementation-specific format.
|
||||
"""
|
||||
|
||||
|
||||
class EFGTsvExporter(Exporter, patterns.DynamicVisitor):
|
||||
"""
|
||||
Writes logical relations of the given TAC EFG to local directory.
|
||||
|
||||
Args:
|
||||
efg: the graph to be written to logical relations.
|
||||
"""
|
||||
|
||||
def __init__(self, efg: tac_efg.TACGraph):
|
||||
"""
|
||||
Generates .facts files of the given TAC EFG to local directory.
|
||||
|
||||
Args:
|
||||
efg: source TAC EFG to be exported to separate fact files.
|
||||
"""
|
||||
super().__init__(efg)
|
||||
|
||||
self.defined = []
|
||||
"""
|
||||
A list of pairs (op.pc, variable) that specify variable definition sites.
|
||||
"""
|
||||
|
||||
self.reads = []
|
||||
"""
|
||||
A list of pairs (op.pc, variable) that specify all usage sites.
|
||||
"""
|
||||
|
||||
self.writes = []
|
||||
"""
|
||||
A list of pairs (op.pc, variable) that specify all write locations.
|
||||
"""
|
||||
|
||||
self.__output_dir = None
|
||||
|
||||
def __generate(self, filename, entries):
|
||||
path = os.path.join(self.__output_dir, filename)
|
||||
with open(path, 'w') as f:
|
||||
writer = csv.writer(f, delimiter='\t', lineterminator='\n')
|
||||
for e in entries:
|
||||
writer.writerow(e)
|
||||
|
||||
def __generate_blocks_ops(self, out_opcodes):
|
||||
# Write a mapping from operation addresses to corresponding opcode names;
|
||||
# a mapping from operation addresses to the block they inhabit;
|
||||
# any specified opcode listings.
|
||||
ops = []
|
||||
# block_nums = []
|
||||
# initialization, empty
|
||||
op_rels = {opcode: list() for opcode in out_opcodes}
|
||||
# Facts pattern: pc + arguments + value name (value is like CALLVALUE's value that will be pushed into stack)
|
||||
for block in self.source.blocks:
|
||||
for op in block.tac_ops:
|
||||
ops.append((hex(op.pc), op.opcode.name, op.loc))
|
||||
if op.opcode.name in out_opcodes:
|
||||
if op.has_lhs():
|
||||
output_tuple = tuple([hex(op.pc)] + [arg.value.name for arg in op.args] +
|
||||
[op.lhs.name] + [op.loc] + [op.call_depth] + [op.call_number])
|
||||
else:
|
||||
output_tuple = tuple([hex(op.pc)] + [arg.value.name for arg in op.args] +
|
||||
[op.loc] + [op.call_depth] + [op.call_number])
|
||||
op_rels[op.opcode.name].append(output_tuple)
|
||||
|
||||
# op.facts contain all the opcodes from the bytecode
|
||||
self.__generate("op.facts", ops)
|
||||
|
||||
# generate all the needed opcode.facts
|
||||
for opcode in op_rels:
|
||||
self.__generate("op_{}.facts".format(opcode), op_rels[opcode])
|
||||
|
||||
def __generate_def_use_value(self):
|
||||
# get the exact values of all the variables
|
||||
# Mapping from variable names to the addresses they were defined at.
|
||||
define = []
|
||||
# Mapping from variable names to the addresses they were used at.
|
||||
use = []
|
||||
# Mapping from variable names to their possible values.
|
||||
value = []
|
||||
for block in self.source.blocks:
|
||||
for op in block.tac_ops:
|
||||
# If it's an assignment op, we have a def site
|
||||
if isinstance(op, tac_efg.TACAssignOp):
|
||||
define.append((op.lhs.name, hex(op.pc), op.loc, op.call_depth, op.call_number))
|
||||
# And we can also find its values here.
|
||||
if op.lhs.values.is_finite:
|
||||
for val in op.lhs.values:
|
||||
value.append((op.lhs.name, hex(val)))
|
||||
|
||||
# Special cases for kind one, such as CALLVALUE
|
||||
# if op.opcode != opcodes.CONST:
|
||||
if op.opcode != opcodes.CONST:
|
||||
# The args constitute use sites.
|
||||
for i, arg in enumerate(op.args):
|
||||
name = arg.value.name
|
||||
use.append((name, hex(op.pc), i + 1, op.loc, op.call_depth, op.call_number))
|
||||
|
||||
# Finally, note where each stack variable might have been defined,
|
||||
# and what values it can take on.
|
||||
# This includes some duplication for stack variables with multiple def
|
||||
# sites. This can be done marginally more efficiently.
|
||||
for var in block.entry_stack:
|
||||
if not var.def_sites.is_const and var.def_sites.is_finite:
|
||||
name = block.ident() + ":" + var.name
|
||||
for loc in var.def_sites:
|
||||
define.append((name, hex(loc.pc), op.loc, loc.call_depth, loc.call_number))
|
||||
|
||||
if var.values.is_finite:
|
||||
for val in var.values:
|
||||
value.append((name, hex(val)))
|
||||
|
||||
self.__generate("def.facts", define)
|
||||
self.__generate("use.facts", use)
|
||||
self.__generate("value.facts", value)
|
||||
|
||||
def export(self, output_dir: str = "", out_opcodes=[]):
|
||||
"""
|
||||
Args:
|
||||
output_dir: location to write the output to.
|
||||
out_opcodes: a list of opcode names all occurences thereof to output,
|
||||
with the names of all argument variables.
|
||||
"""
|
||||
if output_dir != "":
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
self.__output_dir = output_dir
|
||||
|
||||
# Generate facts for the interested opcodes, such as MSTORE ...
|
||||
self.__generate_blocks_ops(out_opcodes)
|
||||
|
||||
# Generate facts for the def.facts (where the V1 V2 ... is defined)
|
||||
# use.fats stores where the V1 V2 ... used; value stores what the V1 V2 ... values are
|
||||
# Previous code
|
||||
self.__generate_def_use_value()
|
||||
# change code - zoey to add a timeout seting
|
||||
# start_time = time.time()
|
||||
|
||||
# if output_dir != "":
|
||||
# os.makedirs(output_dir, exist_ok=True)
|
||||
# self.__output_dir = output_dir
|
||||
|
||||
# try:
|
||||
# # 检查是否超时
|
||||
# if time.time() - start_time > timeout_seconds:
|
||||
# raise TimeoutError("Export process exceeded time limit.")
|
||||
|
||||
# # 以下为您原有的 export 逻辑
|
||||
# self.__generate_blocks_ops(out_opcodes)
|
||||
# self.__generate_def_use_value()
|
||||
|
||||
# except TimeoutError as te:
|
||||
# # 处理超时的逻辑
|
||||
# print(f"Export process exceeded time limit: {te}")
|
||||
|
||||
# # 除超时外的其他异常处理
|
||||
# except Exception as e:
|
||||
# print(f"An error occurred during export: {e}")
|
||||
|
||||
|
||||
class EFGStringExporter(Exporter, patterns.DynamicVisitor):
|
||||
"""
|
||||
Prints a textual representation of the given EFG to stdout.
|
||||
|
||||
Args:
|
||||
efg: source EFG to be printed.
|
||||
ordered: if True (default), print BasicBlocks in order of entry.
|
||||
"""
|
||||
|
||||
__BLOCK_SEP = "\n\n================================\n\n"
|
||||
|
||||
def __init__(self, efg: efg.ControlFlowGraph, ordered: bool = True):
|
||||
super().__init__(efg)
|
||||
self.ordered = ordered
|
||||
self.blocks = []
|
||||
self.source.accept(self)
|
||||
|
||||
def visit_ExecutionFlowGraph(self, efg):
|
||||
"""
|
||||
Visit the EFG root
|
||||
"""
|
||||
pass
|
||||
|
||||
def visit_BasicBlock(self, block):
|
||||
"""
|
||||
Visit a BasicBlock in the EFG
|
||||
"""
|
||||
self.blocks.append((block.entry, str(block)))
|
||||
|
||||
def export(self):
|
||||
"""
|
||||
Print a textual representation of the input EFG to stdout.
|
||||
"""
|
||||
if self.ordered:
|
||||
self.blocks.sort(key=lambda n: n[0])
|
||||
blocks = self.__BLOCK_SEP.join(n[1] for n in self.blocks)
|
||||
functions = ""
|
||||
if self.source.function_extractor is not None:
|
||||
functions = self.__BLOCK_SEP + str(self.source.function_extractor)
|
||||
|
||||
return blocks + functions
|
||||
|
||||
|
||||
class EFGDotExporter(Exporter):
|
||||
"""
|
||||
Generates a dot file for drawing a pretty picture of the given EFG.
|
||||
|
||||
Args:
|
||||
efg: source EFG to be exported to dot format.
|
||||
"""
|
||||
|
||||
def __init__(self, efg: efg.ControlFlowGraph):
|
||||
super().__init__(efg)
|
||||
|
||||
def export(self, out_filename: str = "efg.dot"):
|
||||
"""
|
||||
Export the EFG to a dot file.
|
||||
|
||||
Certain blocks will have coloured outlines:
|
||||
Green: contains a RETURN operation;
|
||||
Blue: contains a STOP operation;
|
||||
Red: contains a THROW, THROWI, INVALID, or missing operation;
|
||||
Purple: contains a SELFDESTRUCT operation;
|
||||
Orange: contains a CALL, CALLCODE, or DELEGATECALL operation;
|
||||
Brown: contains a CREATE operation.
|
||||
|
||||
A node with a red fill indicates that its stack size is large.
|
||||
|
||||
Args:
|
||||
out_filename: path to the file where dot output should be written.
|
||||
If the file extension is a supported image format,
|
||||
attempt to generate an image using the `dot` program,
|
||||
if it is in the user's `$PATH`.
|
||||
"""
|
||||
import networkx as nx
|
||||
|
||||
efg = self.source
|
||||
G = efg.nx_graph()
|
||||
# Annotate each node with its basic block's internal data for later display
|
||||
# if rendered in html.
|
||||
nx.set_node_attributes(G, "id", {i: i
|
||||
for i, block in enumerate(efg.blocks)})
|
||||
|
||||
block_strings = {}
|
||||
for i, block in enumerate(efg.blocks):
|
||||
block_string = str(block)
|
||||
def_site_string = "\n\nDef sites:\n"
|
||||
for v in block.entry_stack.value:
|
||||
def_site_string += str(v) \
|
||||
+ ": {" \
|
||||
+ ", ".join(str(d) for d in v.def_sites) \
|
||||
+ "}\n"
|
||||
block_strings[i] = block_string + def_site_string
|
||||
|
||||
nx.set_node_attributes(G, "tooltip", block_strings)
|
||||
|
||||
# Write non-dot files using pydot and Graphviz
|
||||
if "." in out_filename and not out_filename.endswith(".dot"):
|
||||
pdG = nx.nx_pydot.to_pydot(G)
|
||||
extension = out_filename.split(".")[-1]
|
||||
|
||||
# If we're producing an html file, write a temporary svg to build it from
|
||||
# and then delete it.
|
||||
if extension == "html":
|
||||
html = svg_to_html(pdG.create_svg().decode("utf-8"), efg.function_extractor)
|
||||
if not out_filename.endswith(".html"):
|
||||
out_filename += ".html"
|
||||
with open(out_filename, 'w') as page:
|
||||
logging.info("Drawing EFG image to '%s'.", out_filename)
|
||||
page.write(html)
|
||||
else:
|
||||
pdG.set_margin(0)
|
||||
pdG.write(out_filename, format=extension)
|
||||
|
||||
# Otherwise, write a regular dot file using pydot
|
||||
else:
|
||||
try:
|
||||
if out_filename == "":
|
||||
out_filename = "efg.html"
|
||||
nx.nx_pydot.write_dot(G, out_filename)
|
||||
logging.info("Drawing EFG image to '%s'.", out_filename)
|
||||
except:
|
||||
logging.info("Graphviz missing. Falling back to dot.")
|
||||
if out_filename == "":
|
||||
out_filename = "efg.dot"
|
||||
nx.nx_pydot.write_dot(G, out_filename)
|
||||
logging.info("Drawing EFG image to '%s'.", out_filename)
|
||||
|
||||
|
||||
def svg_to_html(svg: str, function_extractor: function.FunctionExtractor = None) -> str:
|
||||
"""
|
||||
Produces an interactive html page from an svg image of a EFG.
|
||||
|
||||
Args:
|
||||
svg: the string of the SVG to process
|
||||
function_extractor: a FunctionExtractor object containing functions
|
||||
to annotate the graph with.
|
||||
|
||||
Returns:
|
||||
HTML string of interactive web page source for the given EFG.
|
||||
"""
|
||||
|
||||
lines = svg.split("\n")
|
||||
page = []
|
||||
|
||||
page.append("""
|
||||
<html>
|
||||
<body>
|
||||
<style>
|
||||
.node
|
||||
{
|
||||
transition: all 0.05s ease-out;
|
||||
}
|
||||
.node:hover
|
||||
{
|
||||
stroke-width: 1.5;
|
||||
cursor:pointer
|
||||
}
|
||||
.node:hover
|
||||
ellipse
|
||||
{
|
||||
fill: #EEE;
|
||||
}
|
||||
textarea#infobox {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.dropbutton {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
}
|
||||
.dropbutton:hover, .dropbutton:focus {
|
||||
background-color: #777777;
|
||||
}
|
||||
.dropdown {
|
||||
margin-right: 5px;
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
}
|
||||
.dropdown-content {
|
||||
background-color: white;
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 70px;
|
||||
box-shadow: 0px 5px 10px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 8px 10px;
|
||||
text-decoration: none;
|
||||
font-size: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover { background-color: #f1f1f1; }
|
||||
|
||||
.show { display:block; }
|
||||
</style>
|
||||
""")
|
||||
|
||||
for line in lines[3:]:
|
||||
page.append(line)
|
||||
|
||||
page.append("""<textarea id="infobox" disabled=true rows=40 cols=80></textarea>""")
|
||||
|
||||
# Create a dropdown list of functions if there are any.
|
||||
if function_extractor is not None:
|
||||
page.append("""<div class="dropdown">
|
||||
<button onclick="showDropdown()" class="dropbutton">Functions</button>
|
||||
<div id="func-list" class="dropdown-content">""")
|
||||
|
||||
for i, f in enumerate(function_extractor.functions):
|
||||
if f.is_private:
|
||||
page.append('<a id=f_{0} href="javascript:highlightFunction({0})">private #{0}</a>'.format(i))
|
||||
else:
|
||||
if f.signature:
|
||||
page.append(
|
||||
'<a id=f_{0} href="javascript:highlightFunction({0})">public {1}</a>'.format(i, f.signature))
|
||||
else:
|
||||
page.append('<a id=f_{0} href="javascript:highlightFunction({0})">fallback</a>'.format(i))
|
||||
page.append("</div></div>")
|
||||
|
||||
page.append("""<script>""")
|
||||
|
||||
if function_extractor is not None:
|
||||
func_map = {i: [b.ident() for b in f.body]
|
||||
for i, f in enumerate(function_extractor.functions)}
|
||||
page.append("var func_map = {};".format(func_map))
|
||||
page.append("var highlight = new Array({}).fill(0);".format(len(func_map)))
|
||||
|
||||
page.append("""
|
||||
// Set info textbox contents to the title of the given element, with line endings replaced suitably.
|
||||
function setInfoContents(element){
|
||||
document.getElementById('infobox').value = element.getAttribute('xlink:title').replace(/\\\\n/g, '\\n');
|
||||
}
|
||||
|
||||
// Make all node anchor tags in the svg clickable.
|
||||
for (var el of Array.from(document.querySelectorAll(".node a"))) {
|
||||
el.setAttribute("onclick", "setInfoContents(this);");
|
||||
}
|
||||
|
||||
const svg = document.querySelector('svg')
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
const defs = document.createElementNS( NS, "defs" );
|
||||
|
||||
// IIFE add filter to svg to allow shadows to be added to nodes within it
|
||||
(function(){
|
||||
defs.innerHTML = makeShadowFilter()
|
||||
svg.insertBefore(defs,svg.children[0])
|
||||
})()
|
||||
|
||||
function colorToID(color){
|
||||
return color.replace(/[^a-zA-Z0-9]/g,'_')
|
||||
}
|
||||
|
||||
function makeShadowFilter({color = 'black',x = 0,y = 0, blur = 3} = {}){
|
||||
return `
|
||||
<filter id="filter_${colorToID(color)}" x="-40%" y="-40%" width="250%" height="250%">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="${blur}"/>
|
||||
<feOffset dx="${x}" dy="${y}" result="offsetblur"/>
|
||||
<feFlood flood-color="${color}"/>
|
||||
<feComposite in2="offsetblur" operator="in"/>
|
||||
<feMerge>
|
||||
<feMergeNode/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
`
|
||||
}
|
||||
|
||||
// Shadow toggle functions, with filter caching
|
||||
function addShadow(el, {color = 'black', x = 0, y = 0, blur = 3}){
|
||||
const id = colorToID(color);
|
||||
if(!defs.querySelector(`#filter_${id}`)){
|
||||
const d = document.createElementNS(NS, 'div');
|
||||
d.innerHTML = makeShadowFilter({color, x, y, blur});
|
||||
defs.appendChild(d.children[0]);
|
||||
}
|
||||
el.style.filter = `url(#filter_${id})`
|
||||
}
|
||||
|
||||
function removeShadow(el){
|
||||
el.style.filter = ''
|
||||
}
|
||||
|
||||
function hash(n) {
|
||||
var str = n + "rainbows" + n + "please" + n;
|
||||
var hash = 0;
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
hash = (((hash << 5) - hash) + str.charCodeAt(i)) | 0;
|
||||
}
|
||||
return hash > 0 ? hash : -hash;
|
||||
};
|
||||
|
||||
function getColor(n, sat="80%", light="50%") {
|
||||
const hue = hash(n) % 360;
|
||||
return `hsl(${hue}, ${sat}, ${light})`;
|
||||
}
|
||||
|
||||
// Add shadows to function body nodes, and highlight functions in the dropdown list
|
||||
function highlightFunction(i) {
|
||||
for (var n of Array.from(document.querySelectorAll(".node ellipse"))) {
|
||||
removeShadow(n);
|
||||
}
|
||||
|
||||
highlight[i] = !highlight[i];
|
||||
const entry = document.querySelector(`.dropdown-content a[id='f_${i}']`)
|
||||
if (entry.style.backgroundColor) {
|
||||
entry.style.backgroundColor = null;
|
||||
} else {
|
||||
entry.style.backgroundColor = getColor(i, "60%", "90%");
|
||||
}
|
||||
|
||||
for (var j = 0; j < highlight.length; j++) {
|
||||
if (highlight[j]) {
|
||||
const col = getColor(j);
|
||||
for (var id of func_map[j]) {
|
||||
var n = document.querySelector(`.node[id='${id}'] ellipse`);
|
||||
addShadow(n, {color:`${col}`});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the dropdown elements when it's clicked.
|
||||
function showDropdown() {
|
||||
document.getElementById("func-list").classList.toggle("show");
|
||||
}
|
||||
window.onclick = function(event) {
|
||||
if (!event.target.matches('.dropbutton')) {
|
||||
var items = Array.from(document.getElementsByClassName("dropdown-content"));
|
||||
for (var item of items) {
|
||||
item.classList.remove('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
</body>
|
||||
""")
|
||||
|
||||
return "\n".join(page)
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""function.py:
|
||||
Classes for identifying and exporting functions in the control flow graph.
|
||||
Tested and developed on Solidity version 0.4.11"""
|
||||
|
||||
import typing as t
|
||||
|
||||
import src.memtypes as memtypes
|
||||
import src.opcodes as opcodes
|
||||
import src.tac_efg as tac_cfg
|
||||
|
||||
# 用于提取solidity合约中的函数
|
||||
"""
|
||||
Function 类表示一个 Solidity 函数及其相关的元数据, FunctionExtractor 类用于从一个已经生成的 TAC cfg 中提取 Solidity 函数。
|
||||
|
||||
Function 类具有以下属性:
|
||||
|
||||
body: 一个列表,表示该函数的主体部分由哪些 TAC 基本块组成。
|
||||
start_block: 一个指向 TAC 基本块的指针,表示该函数的起始块。
|
||||
end_block: 一个指向 TAC 基本块的指针,表示该函数的结束块。
|
||||
mapping: 一个字典,将函数主体中的前驱基本块映射到后继基本块。
|
||||
signature: 一个字符串,表示函数的签名。
|
||||
is_private: 一个布尔值,表示该函数是否为私有函数。
|
||||
FunctionExtractor 类具有以下属性:
|
||||
|
||||
cfg: 一个 TAC cfg,表示需要提取函数的 cfg。
|
||||
public_functions: 一个列表,表示公共函数列表。
|
||||
private_functions: 一个列表,表示私有函数列表。
|
||||
invoc_pairs: 一个字典,将调用站点映射到返回地址。
|
||||
mark_body(): 一个静态方法,用于标记函数主体中的基本块。
|
||||
functions: 返回所有函数列表。
|
||||
extract(): 提取私有和公共函数。
|
||||
mark_functions(): 将提取的函数主体标记为具有唯一标识符后缀。
|
||||
extract_public_functions(): 提取暴露给合约的 Solidity 公共函数。
|
||||
get_public_function(): 确定从给定块开始的函数。
|
||||
"""
|
||||
|
||||
class Function:
|
||||
"""
|
||||
A representation of a Solidity function with associated metadata.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.body = []
|
||||
self.start_block = None
|
||||
self.end_block = None
|
||||
self.mapping = {} # a mapping of preds to succs of the function body.
|
||||
self.signature = ""
|
||||
self.is_private = False
|
||||
|
||||
def __str__(self):
|
||||
if self.is_private:
|
||||
sig = "Private function"
|
||||
else:
|
||||
if self.signature:
|
||||
sig = "Public function signature: " + self.signature
|
||||
else:
|
||||
sig = "Public fallback function"
|
||||
entry_block = "Entry block: " + self.start_block.ident()
|
||||
if self.end_block is not None:
|
||||
exit_block = "Exit block: " + self.end_block.ident()
|
||||
else:
|
||||
exit_block = "Exit block: None identified"
|
||||
body = "Body: " + ", ".join(b.ident() for b in sorted(self.body))
|
||||
return "\n".join([sig, entry_block, exit_block, body])
|
||||
|
||||
|
||||
class FunctionExtractor:
|
||||
"""A class for extracting Solidity functions from an already generated TAC cfg."""
|
||||
|
||||
def __init__(self, cfg: tac_cfg.TACGraph):
|
||||
self.cfg = cfg # the tac_cfg that this operates on
|
||||
self.public_functions = []
|
||||
self.private_functions = []
|
||||
self.invoc_pairs = {} # a mapping from invocation sites to return addresses
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Returns a string representation of all the functions in the graph
|
||||
after extraction has been performed.
|
||||
|
||||
Returns:
|
||||
A string summary of all the functions in the control flow graph
|
||||
"""
|
||||
|
||||
return "\n".join(["Function {}:\n{}\n".format(str(i), str(func))
|
||||
for i, func in enumerate(self.functions)])
|
||||
|
||||
@staticmethod
|
||||
def mark_body(path: t.List[tac_cfg.TACBasicBlock], num: int) -> None:
|
||||
"""
|
||||
Marks every block in the path with the given number.
|
||||
Used for marking function bodies.
|
||||
"""
|
||||
for block in path:
|
||||
block.ident_suffix += "_F" + str(num)
|
||||
|
||||
@property
|
||||
def functions(self):
|
||||
return self.public_functions + self.private_functions
|
||||
|
||||
def extract(self) -> None:
|
||||
"""Extracts private and public functions"""
|
||||
self.private_functions.extend(self.extract_private_functions())
|
||||
self.public_functions.extend(self.extract_public_functions())
|
||||
|
||||
def mark_functions(self) -> None:
|
||||
"""Mark extracted function bodies with unique identifier suffixes."""
|
||||
for i, func in enumerate(self.functions):
|
||||
self.mark_body(func.body, i)
|
||||
|
||||
def extract_public_functions(self) -> t.Iterable[Function]:
|
||||
"""
|
||||
Return a list of the solidity public functions exposed by the contract of this cfg.
|
||||
|
||||
Returns:
|
||||
A list of the extracted functions.
|
||||
"""
|
||||
|
||||
# Find the function signature variable holding call data 0,
|
||||
# at the earliest query to that location in the program.
|
||||
load_list = []
|
||||
load_block = None
|
||||
|
||||
for block in sorted(self.cfg.blocks):
|
||||
load_list = [op for op in block.tac_ops
|
||||
if op.opcode == opcodes.CALLDATALOAD
|
||||
and op.args[0].value.const_value == 0]
|
||||
if len(load_list) != 0:
|
||||
load_block = block
|
||||
break
|
||||
|
||||
if len(load_list) == 0:
|
||||
return []
|
||||
sig_var = load_list[0].lhs
|
||||
|
||||
# Follow the signature until it's transformed into its final shape.
|
||||
for o in load_block.tac_ops:
|
||||
if not isinstance(o, tac_cfg.TACAssignOp) or id(sig_var) not in [id(a.value) for a in o.args]:
|
||||
continue
|
||||
if o.opcode == opcodes.EQ:
|
||||
break
|
||||
sig_var = o.lhs
|
||||
|
||||
# Find all the places the function signature is compared to a constant
|
||||
func_sigs = []
|
||||
fallthroughs = []
|
||||
|
||||
for b in self.cfg.blocks:
|
||||
for o in b.tac_ops:
|
||||
if not isinstance(o, tac_cfg.TACAssignOp) or id(sig_var) not in [id(a.value) for a in o.args]:
|
||||
continue
|
||||
if o.opcode == opcodes.EQ:
|
||||
sig = [a.value for a in o.args if id(a.value) != id(sig_var)][0]
|
||||
|
||||
# Append the non-fallthrough successor to the function sig list
|
||||
for succ in [s for s in b.succs if s != b.fallthrough]:
|
||||
func_sigs.append((succ, hex(sig.const_value)))
|
||||
|
||||
# Save the fallthrough location so the last one can be added as
|
||||
# the fallback function
|
||||
if b.fallthrough is not None:
|
||||
fallthroughs.append(b.fallthrough)
|
||||
|
||||
# Add the fallback function
|
||||
if fallthroughs:
|
||||
func_sigs.append((fallthroughs[-1], ""))
|
||||
|
||||
return [self.get_public_function(s[0], signature=s[1]) for s in func_sigs]
|
||||
|
||||
def get_public_function(self, block: tac_cfg.TACBasicBlock,
|
||||
signature: str = "") -> Function:
|
||||
"""
|
||||
Identifies the function starting with the given block
|
||||
|
||||
Args:
|
||||
block: A BasicBlock to be used as the starting block for the function.
|
||||
signature: The solidity hashed function signature associated with the function to be extracted.
|
||||
|
||||
Returns:
|
||||
A Function object containing the blocks composing the function body.
|
||||
"""
|
||||
body = []
|
||||
queue = [block]
|
||||
end_block = None
|
||||
cur_block = None # A placeholder for prev_block
|
||||
jump = False # Keeps track of whether we have just jumped or not
|
||||
pre_jump_block = block
|
||||
while len(queue) > 0:
|
||||
prev_block = cur_block
|
||||
cur_block = queue.pop(0)
|
||||
|
||||
# jump over function bodies
|
||||
for f in self.functions:
|
||||
if cur_block in f.body and not jump:
|
||||
cur_block = self.__jump_to_next_loc(cur_block, body,
|
||||
prev_block.exit_stack)
|
||||
if cur_block in f.body and jump:
|
||||
# If we jumped, the previous block is actually from before the jump
|
||||
cur_block = self.__jump_to_next_loc(cur_block, body,
|
||||
pre_jump_block.exit_stack)
|
||||
jump = False
|
||||
# In case we didn't find a new block to jump to
|
||||
if cur_block is None:
|
||||
continue
|
||||
# Jump over other invocation sites after adding them to body
|
||||
if cur_block in self.invoc_pairs:
|
||||
jump = True
|
||||
pre_jump_block = cur_block
|
||||
if cur_block not in body:
|
||||
body.append(cur_block)
|
||||
cur_block = self.invoc_pairs[cur_block]
|
||||
# Since an invocation site always has a successor,
|
||||
# it can't be an end block for the function
|
||||
|
||||
if len(cur_block.succs) == 0:
|
||||
end_block = cur_block
|
||||
if cur_block not in body:
|
||||
body.append(cur_block)
|
||||
for b in cur_block.succs:
|
||||
queue.append(b)
|
||||
f = Function()
|
||||
f.body = body
|
||||
f.start_block = block
|
||||
# Assuming that there will only be one end_block
|
||||
f.end_block = end_block
|
||||
f.signature = signature
|
||||
f.is_private = False
|
||||
return f
|
||||
|
||||
def __jump_to_next_loc(self, block: tac_cfg.TACBasicBlock,
|
||||
body: t.List[tac_cfg.TACBasicBlock],
|
||||
exit_stack: memtypes.VariableStack) -> tac_cfg.TACBasicBlock:
|
||||
"""
|
||||
Helper method to jump over private functions during public function
|
||||
identification. Uses BFS to find the next available block that is not
|
||||
in a function.
|
||||
|
||||
Args:
|
||||
block: The current block to be tested as being in the function body
|
||||
body: The body of the current function being identified.
|
||||
exit_stack: The stack of the previous block, or block before that after
|
||||
a previous jump over a function. If a block is in this
|
||||
exit_stack, then it is the next block in the flow of the
|
||||
function currently being identified.
|
||||
"""
|
||||
queue = [block]
|
||||
visited = [block]
|
||||
while len(queue) > 0:
|
||||
block = queue.pop()
|
||||
if len(block.succs) == 0:
|
||||
return block
|
||||
visited.append(block)
|
||||
in_func = False
|
||||
for f in self.functions:
|
||||
if block in f.body:
|
||||
in_func = True
|
||||
break
|
||||
# Check that the block is not related to another function,
|
||||
# and we haven't visited it yet, and that it is in the exit stack
|
||||
# We can discard blocks in the body of the function being identified
|
||||
# since we want to discover new blocks, not old ones
|
||||
if not in_func and block not in self.invoc_pairs.keys() and \
|
||||
block.ident() in str(exit_stack) and block not in body:
|
||||
return block
|
||||
for succ in block.succs:
|
||||
if succ not in visited:
|
||||
queue.append(succ)
|
||||
return None
|
||||
|
||||
def extract_private_functions(self) -> t.List[tac_cfg.TACBasicBlock]:
|
||||
"""
|
||||
Extracts private functions
|
||||
|
||||
Returns:
|
||||
A list of of Function objects: the private functions identified in the cfg
|
||||
"""
|
||||
# Get invocation site -> return block mappings
|
||||
start_blocks = []
|
||||
pair_list = []
|
||||
for block in reversed(self.cfg.blocks):
|
||||
invoc_pairs = self.is_private_func_start(block)
|
||||
if invoc_pairs:
|
||||
start_blocks.append(block)
|
||||
pair_list.append(invoc_pairs)
|
||||
|
||||
# Store invocation site - return pairs for later usage
|
||||
for d in pair_list:
|
||||
for key in d.keys():
|
||||
self.invoc_pairs[key] = d[key]
|
||||
|
||||
# Find the Function body itself
|
||||
private_funcs = list()
|
||||
for i, block in enumerate(start_blocks):
|
||||
return_blocks = list(pair_list[i].values())
|
||||
f = self.find_func_body(block, return_blocks, pair_list)
|
||||
if not f or len(f.body) == 1: # Can't have a function with 1 block in EVM
|
||||
continue
|
||||
if f is not None:
|
||||
f.is_private = True
|
||||
private_funcs.append(f)
|
||||
return private_funcs
|
||||
|
||||
def is_private_func_start(self, block: tac_cfg.TACBasicBlock) -> t.Dict[
|
||||
tac_cfg.TACBasicBlock, tac_cfg.TACBasicBlock]:
|
||||
"""
|
||||
Determines the invocation and return points of the function beginning
|
||||
with the given block, if it exists.
|
||||
|
||||
Args:
|
||||
block: a BasicBlock to be tested as the possible beginning of a function
|
||||
|
||||
Returns:
|
||||
A mapping of invocation sites to return blocks of the function,
|
||||
if the given block is the start of a function.
|
||||
None if the block is not the beginning of a function.
|
||||
"""
|
||||
# if there are multiple paths converging, this is a possible function start
|
||||
preds = list(sorted(block.preds))
|
||||
if (len(preds) <= 1) or len(list(block.succs)) == 0:
|
||||
return None
|
||||
func_succs = [] # a list of what succs to look out for.
|
||||
func_mapping = {} # mapping of predecessors -> successors of function
|
||||
params = [] # list of parameters
|
||||
for pre in preds:
|
||||
if pre not in block.succs:
|
||||
# Check for at least 2 push opcodes and net stack gain
|
||||
push_count = 0
|
||||
for evm_op in pre.evm_ops:
|
||||
if evm_op.opcode.is_push():
|
||||
push_count += 1
|
||||
if push_count <= 1:
|
||||
return None
|
||||
if len(pre.delta_stack) == 0:
|
||||
return None
|
||||
for val in list(pre.delta_stack):
|
||||
ref_block = self.cfg.get_block_by_ident(str(val))
|
||||
# Ensure that the block pointed to by the block exists and is reachable
|
||||
if ref_block is not None and self.cfg.reaches(pre, [ref_block]):
|
||||
func_mapping[pre] = ref_block
|
||||
func_succs.append(ref_block)
|
||||
break
|
||||
params.append(val)
|
||||
if not func_succs or len(func_mapping) == 1:
|
||||
return None
|
||||
# We have our start
|
||||
return func_mapping
|
||||
|
||||
def find_func_body(self, block: tac_cfg.TACBasicBlock,
|
||||
return_blocks: t.List[tac_cfg.TACBasicBlock],
|
||||
invoc_pairs: t.List[tac_cfg.TACBasicBlock]) -> 'Function':
|
||||
"""
|
||||
Assuming the block is a definite function start, identifies all paths
|
||||
from start to end using BFS
|
||||
|
||||
Args:
|
||||
block: the start block of a function.
|
||||
return_blocks: the list of return blocks of the function.
|
||||
invoc_pairs: a mapping of all other invocation and return point pairs
|
||||
to allow jumping over other functions.
|
||||
|
||||
Returns:
|
||||
A function object representing the function starting with the given block
|
||||
"""
|
||||
# Traverse down levels with BFS until we hit a block that has the return
|
||||
# addresses specified above
|
||||
body = []
|
||||
queue = [block]
|
||||
end = False
|
||||
while len(queue) > 0:
|
||||
curr_block = queue.pop(0)
|
||||
# When we call a function, we just jump to the return address
|
||||
for entry in invoc_pairs:
|
||||
if curr_block in entry:
|
||||
body.append(curr_block)
|
||||
curr_block = entry[curr_block]
|
||||
cur_succs = [b for b in curr_block.succs]
|
||||
if set(return_blocks).issubset(set(cur_succs)):
|
||||
end = True
|
||||
if curr_block not in body and self.cfg.reaches(curr_block, return_blocks):
|
||||
body.append(curr_block)
|
||||
for b in curr_block.succs:
|
||||
if b not in return_blocks:
|
||||
queue.append(b)
|
||||
|
||||
if end:
|
||||
f = Function()
|
||||
f.start_block = block
|
||||
poss_exit_blocks = [sorted(b.preds) for b in return_blocks]
|
||||
exit_blocks = set(poss_exit_blocks[0])
|
||||
for preds in poss_exit_blocks:
|
||||
exit_blocks = exit_blocks & set(preds)
|
||||
# We assume the end_block is the last block in the disasm from all candidates
|
||||
f.end_block = sorted(exit_blocks).pop()
|
||||
f.succs = sorted(return_blocks)
|
||||
f.preds = sorted(block.preds)
|
||||
f.body = body
|
||||
return f
|
||||
|
||||
return None
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""lattice.py: define lattices for use in meet-over-paths calculations.
|
||||
|
||||
We will take bottom elements to mean maximal value constraint
|
||||
(uninitialised, or empty set), while top elements will be taken to mean a
|
||||
maximally-unconstrained element (all possible values, universal set)."""
|
||||
|
||||
import abc
|
||||
import functools
|
||||
import itertools
|
||||
import types
|
||||
import typing as t
|
||||
from copy import copy
|
||||
|
||||
"""Lattice是一种代数结构,它包含一个偏序集和两个操作 meet 和 join。这个代码定义了LatticeElement类和BoundedLatticeElement类,并在BoundedLatticeElement类中实现了具有上下界的LatticeElement。类IntLatticeElement实现了一个特定的具有整数元素的BoundedLatticeElement。下面我们将这些类的实现逐一解释。
|
||||
|
||||
LatticeElement(抽象基类):定义了两个抽象方法 meet 和 join,分别代表 meet-over-paths 中的 meet 和 join 操作。在 meet 和 join 中,类的实例(即 LatticeElement 的子类)是其参数。该类还包括比较值的方法和其他辅助方法。 LatticeElement 是 BoundedLatticeElement 和 IntLatticeElement 类的基类。
|
||||
|
||||
BoundedLatticeElement(LatticeElement 的子类):它是 LatticeElement 的具体实现,实现了具有上下界的LatticeElement。BoundedLatticeElement 定义了 is_top 和 is_bottom 属性和对应的方法,用于检测该实例是否是最大或最小元素。该类还定义了两个类方法 meet_all 和 join_all,分别计算一组 LatticeElement 的 meet 和 join 操作。该类还包括有关顶部和底部元素的抽象方法 _top_val 和 _bottom_val,以及 top 和 bottom 静态方法,它们分别返回顶部和底部元素的实例。IntLatticeElement 类实现了一个特定的具有整数元素的 BoundedLatticeElement。
|
||||
|
||||
IntLatticeElement(BoundedLatticeElement 的子类):它实现了具有整数元素的 BoundedLatticeElement。因为整数之间没有偏序,所以这个 Lattice 中的整数被视为不可比较的元素。它还实现了一个帮助函数is_int,用于检查给定的 LatticeElement 是否为整数元素(而不是顶部或底部元素)。"""
|
||||
|
||||
|
||||
class LatticeElement(abc.ABC):
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Construct a lattice element with the given value.
|
||||
|
||||
Args:
|
||||
value: the value of this LatticeElement
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def meet(cls, a: 'LatticeElement', b: 'LatticeElement') -> 'LatticeElement':
|
||||
"""Return the infimum of the given elements."""
|
||||
|
||||
@classmethod
|
||||
def meet_all(cls, elements: t.Iterable['LatticeElement'],
|
||||
initial: 'LatticeElement' = None) -> 'LatticeElement':
|
||||
"""
|
||||
Return the infimum of the given iterable of elements.
|
||||
|
||||
Args:
|
||||
elements: a sequence of elements whose common meet to obtain
|
||||
initial: an additional element to meet with the rest.
|
||||
An empty sequence will result in this value, if provided.
|
||||
"""
|
||||
if initial is not None:
|
||||
return functools.reduce(
|
||||
lambda a, b: cls.meet(a, b),
|
||||
elements,
|
||||
initial
|
||||
)
|
||||
return functools.reduce(
|
||||
lambda a, b: cls.meet(a, b),
|
||||
elements,
|
||||
)
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def join(cls, a: 'LatticeElement', b: 'LatticeElement') -> 'LatticeElement':
|
||||
"""Return the infimum of the given elements."""
|
||||
|
||||
@classmethod
|
||||
def join_all(cls, elements: t.Iterable['LatticeElement'],
|
||||
initial: 'LatticeElement' = None) -> 'LatticeElement':
|
||||
"""
|
||||
Return the supremum of the given iterable of elements.
|
||||
|
||||
Args:
|
||||
elements: a sequence of elements whose common join to obtain
|
||||
initial: an additional element to join with the rest.
|
||||
An empty sequence will result in this value, if provided.
|
||||
"""
|
||||
if initial is not None:
|
||||
return functools.reduce(
|
||||
lambda a, b: cls.join(a, b),
|
||||
elements,
|
||||
initial
|
||||
)
|
||||
return functools.reduce(
|
||||
lambda a, b: cls.join(a, b),
|
||||
elements,
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} object {1}, {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
str(self)
|
||||
)
|
||||
|
||||
|
||||
class BoundedLatticeElement(LatticeElement):
|
||||
"""An element from a lattice with defined Top and Bottom elements."""
|
||||
TOP_SYMBOL = "⊤"
|
||||
BOTTOM_SYMBOL = "⊥"
|
||||
|
||||
def __init__(self, value):
|
||||
"""
|
||||
Construct a bounded lattice element with the given value.
|
||||
|
||||
Args:
|
||||
value: the value this lattice element should take.
|
||||
"""
|
||||
super().__init__(value)
|
||||
|
||||
@classmethod
|
||||
def meet_all(cls,
|
||||
elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement':
|
||||
"""
|
||||
Take the meet of all elements in the given sequence.
|
||||
An empty sequence produces Top.
|
||||
"""
|
||||
return super().meet_all(elements, cls.top())
|
||||
|
||||
@classmethod
|
||||
def join_all(cls,
|
||||
elements: t.Iterable['BoundedLatticeElement']) -> 'BoundedLatticeElement':
|
||||
"""
|
||||
Take the join of all elements in the given sequence.
|
||||
An empty sequence produces Bottom.
|
||||
"""
|
||||
return super().join_all(elements, cls.bottom())
|
||||
|
||||
@property
|
||||
def is_top(self):
|
||||
"""True if this element is Top."""
|
||||
return self.value == self._top_val()
|
||||
|
||||
@property
|
||||
def is_bottom(self):
|
||||
"""True if this element is Bottom."""
|
||||
return self.value == self._bottom_val()
|
||||
|
||||
def __str__(self):
|
||||
if self.is_top:
|
||||
return self.TOP_SYMBOL
|
||||
elif self.is_bottom:
|
||||
return self.BOTTOM_SYMBOL
|
||||
else:
|
||||
return str(self.value)
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def _top_val(cls):
|
||||
"""Return the Top value of this lattice."""
|
||||
|
||||
@abc.abstractclassmethod
|
||||
def _bottom_val(cls):
|
||||
"""Return the Bottom value of this lattice."""
|
||||
|
||||
@classmethod
|
||||
def top(cls) -> 'BoundedLatticeElement':
|
||||
"""Return the Top lattice element."""
|
||||
return cls(cls._top_val())
|
||||
|
||||
@classmethod
|
||||
def bottom(cls) -> 'BoundedLatticeElement':
|
||||
"""Return the Bottom lattice element."""
|
||||
return cls(cls._bottom_val())
|
||||
|
||||
def widen_to_top(self):
|
||||
"""Set this element's value to Top without changing anything else."""
|
||||
self.value = self._top_val()
|
||||
|
||||
|
||||
class IntLatticeElement(BoundedLatticeElement):
|
||||
"""
|
||||
An element of the lattice defined by augmenting
|
||||
the (unordered) set of integers with top and bottom elements.
|
||||
|
||||
Integers are incomparable with one another, while Top and Bottom
|
||||
compare superior and inferior with every other element, respectively.
|
||||
"""
|
||||
|
||||
def __init__(self, value: int):
|
||||
"""
|
||||
Args:
|
||||
value: the integer this element contains, if it is not Top or Bottom.
|
||||
"""
|
||||
super().__init__(value)
|
||||
|
||||
def is_int(self) -> bool:
|
||||
"""True iff this lattice element is neither Top nor Bottom."""
|
||||
return not (self.is_top or self.is_bottom)
|
||||
|
||||
def __add__(self, other):
|
||||
if self.is_int() and other.is_int():
|
||||
return IntLatticeElement(self.value + other.value)
|
||||
return self.bottom()
|
||||
|
||||
@classmethod
|
||||
def _top_val(cls):
|
||||
return cls.TOP_SYMBOL
|
||||
|
||||
@classmethod
|
||||
def _bottom_val(cls):
|
||||
return cls.BOTTOM_SYMBOL
|
||||
|
||||
@classmethod
|
||||
def meet(cls,
|
||||
a: 'IntLatticeElement', b: 'IntLatticeElement') -> 'IntLatticeElement':
|
||||
"""Return the infimum of the given elements."""
|
||||
|
||||
if a.is_bottom or b.is_bottom:
|
||||
return cls.bottom()
|
||||
|
||||
if a.is_top:
|
||||
return copy(b)
|
||||
if b.is_top:
|
||||
return copy(a)
|
||||
if a.value == b.value:
|
||||
return copy(a)
|
||||
|
||||
return cls.bottom()
|
||||
|
||||
@classmethod
|
||||
def join(cls,
|
||||
a: 'IntLatticeElement', b: 'IntLatticeElement') -> 'IntLatticeElement':
|
||||
"""Return the supremum of the given elements."""
|
||||
|
||||
if a.is_top or b.is_top:
|
||||
return cls.top()
|
||||
|
||||
if a.is_bottom:
|
||||
return copy(b)
|
||||
if b.is_bottom:
|
||||
return copy(a)
|
||||
if a.value == b.value:
|
||||
return copy(a)
|
||||
|
||||
return cls.top()
|
||||
|
||||
|
||||
class SubsetLatticeElement(BoundedLatticeElement):
|
||||
"""
|
||||
A subset lattice element. The top element is the complete set of all
|
||||
elements, the bottom is the empty set, and other elements are subsets of top.
|
||||
"""
|
||||
|
||||
def __init__(self, value: t.Iterable):
|
||||
"""
|
||||
Args:
|
||||
value: an iterable of elements which will compose the value of this
|
||||
lattice element. It will be converted to a set, so duplicate
|
||||
elements and ordering are ignored.
|
||||
"""
|
||||
super().__init__(set(value))
|
||||
|
||||
def __len__(self):
|
||||
if self.is_top:
|
||||
# TODO: determine if this is the right thing here. TOP has unbounded size.
|
||||
return 0
|
||||
return len(self.value)
|
||||
|
||||
def __iter__(self):
|
||||
if self.is_top:
|
||||
raise TypeError("Top lattice element cannot be iterated.")
|
||||
return iter(self.value)
|
||||
|
||||
def map(self, f: types.FunctionType) -> 'SubsetLatticeElement':
|
||||
"""
|
||||
Return the result of applying a function to each of this element's values.
|
||||
|
||||
Incidentally, this could be seen as special case of cartesian_map().
|
||||
"""
|
||||
if self.is_top:
|
||||
return copy(self)
|
||||
return type(self)([f(val) for val in self.value])
|
||||
|
||||
@classmethod
|
||||
def cartesian_map(cls, f: types.FunctionType,
|
||||
elements: t.Iterable['SubsetLatticeElement']) \
|
||||
-> 'SubsetLatticeElement':
|
||||
"""
|
||||
Apply the given function to each tuple of members in the product of the
|
||||
input elements, and return the resulting lattice element.
|
||||
|
||||
The function's arity must match the number of input elements.
|
||||
For example, for a binary function, and input elements a, b, the result is
|
||||
the element defined by the set f(u, v) for each u in a, v in b.
|
||||
"""
|
||||
|
||||
# Symbolic manipulations could be performed here as some operations might
|
||||
# constrain the results, even if some input set is unconstrained.
|
||||
if any([e.is_top for e in elements]):
|
||||
return cls.top()
|
||||
prod = itertools.product(*(list(e) for e in elements))
|
||||
return cls([f(*args) for args in prod])
|
||||
|
||||
@classmethod
|
||||
def _top_val(cls):
|
||||
return set(cls.TOP_SYMBOL)
|
||||
|
||||
@classmethod
|
||||
def _bottom_val(cls):
|
||||
return set()
|
||||
|
||||
@classmethod
|
||||
def meet(cls, a: 'SubsetLatticeElement',
|
||||
b: 'SubsetLatticeElement') -> 'SubsetLatticeElement':
|
||||
"""Return the set intersection of the given elements."""
|
||||
if a.is_top:
|
||||
return copy(b)
|
||||
if b.is_top:
|
||||
return copy(a)
|
||||
|
||||
return cls(a.value & b.value)
|
||||
|
||||
@classmethod
|
||||
def join(cls, a: 'SubsetLatticeElement',
|
||||
b: 'SubsetLatticeElement') -> 'SubsetLatticeElement':
|
||||
"""Return the set union of the given elements."""
|
||||
if a.is_top or b.is_top:
|
||||
return cls.top()
|
||||
|
||||
return cls(a.value | b.value)
|
||||
|
||||
@property
|
||||
def is_const(self) -> bool:
|
||||
"""True iff this variable has exactly one possible value."""
|
||||
return self.is_finite and len(self) == 1
|
||||
|
||||
@property
|
||||
def is_finite(self) -> bool:
|
||||
"""
|
||||
True iff this variable has a finite and nonzero number of possible values.
|
||||
"""
|
||||
return not (self.is_top or self.is_bottom)
|
||||
+664
@@ -0,0 +1,664 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""memtypes.py: Symbolic representations of ways of storing information
|
||||
in the ethereum machine."""
|
||||
|
||||
|
||||
"""
|
||||
定义了两个变量 VAR_DEFAULT_NAME 和 VAR_RESULT_NAME。
|
||||
|
||||
定义了一个抽象类 Location,表示存储位置,包含标识符、是否为常量、是否为不受约束、可存储的值等属性,以及一个 Variable 类,表示一个符号变量,包含变量的名称、大小、可能的值等属性,并重载了部分操作符,比如相等操作、取值操作等。其中 meet 和 join 方法分别返回两个变量的交集和并集
|
||||
|
||||
继承了LatticeElement类的VariableStack类,它定义了一个堆栈数据结构,用于保存TAC(三地址代码)变量。该堆栈还是一个格,因此meet和join在从栈顶向下逐个元素地操作时定义。
|
||||
|
||||
该堆栈被视为具有无限容量,空槽向下无限延伸。堆栈中的空槽被解释为具有底部值的变量,以便对于格定义而言,一个空堆栈将是该格的底部,而填有顶部变量的堆栈将是其顶部。因此,我们拥有了一个有界的格,但我们不需要BoundedLatticeElement类所涉及的额外复杂性。
|
||||
|
||||
该类中包含的方法有:
|
||||
|
||||
init : 类的初始化方法,定义了空堆栈的起始状态、堆栈的最大容量、最小最大容量以及调用的深度。
|
||||
iter : 类的迭代器方法,从堆栈的顶部向下迭代。
|
||||
str : 类的字符串表示方法,返回由每个元素的字符串表示组成的列表。
|
||||
len : 类的长度方法,返回堆栈中的元素数。
|
||||
eq : 类的相等方法,如果两个堆栈中的元素相等则返回True,否则返回False。
|
||||
copy : 创建一个堆栈的浅拷贝。
|
||||
metafy : 将所有无限制的变量转换为元变量,其标签是它们当前的堆栈位置。
|
||||
peek : 返回从堆栈顶部开始的第n个元素,不弹出任何元素。
|
||||
push : 将一个变量推入堆栈。
|
||||
pop : 如果存在符号堆栈,则将其弹出变量,否则从底部生成变量。
|
||||
push_many : 将一系列元素推入堆栈中,低索引元素首先推入。
|
||||
pop_many : 从堆栈中弹出并返回n个元素,第一个弹出的元素位于低索引位置。
|
||||
dup : 将堆栈的第n-1个元素复制到堆栈的顶部。
|
||||
swap : 交换堆栈[0]与堆栈[n]。
|
||||
set_max_size : 设置堆栈的最大容量。
|
||||
"""
|
||||
import abc
|
||||
import copy
|
||||
import typing as t
|
||||
from itertools import zip_longest, dropwhile
|
||||
|
||||
from src.lattice import LatticeElement, SubsetLatticeElement as ssle
|
||||
|
||||
import pdb
|
||||
|
||||
|
||||
VAR_DEFAULT_NAME = "Var"
|
||||
"""The fallback name when creating a fresh variable."""
|
||||
VAR_RESULT_NAME = "Res"
|
||||
"""The name to apply to variables resulting from an arithmetic operation."""
|
||||
|
||||
|
||||
class Location(abc.ABC):
|
||||
"""A generic storage location: variables, memory, static storage."""
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
"""Return the string identifying this object."""
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def is_const(self) -> bool:
|
||||
"""True if the set of possible values this Location stores is known."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_unconstrained(self) -> bool:
|
||||
"""
|
||||
True iff this variable could take on all possible values.
|
||||
"""
|
||||
return self.values.is_top
|
||||
|
||||
@property
|
||||
def values(self) -> ssle:
|
||||
"""
|
||||
Return the set of values this location may contain.
|
||||
Generically, this set is unconstrained.
|
||||
"""
|
||||
return ssle.top()
|
||||
|
||||
|
||||
class Variable(ssle, Location):
|
||||
"""
|
||||
A symbolic variable whose value is supposed to be
|
||||
the result of some TAC operation. Its size is 32 bytes.
|
||||
"""
|
||||
|
||||
SIZE = 32
|
||||
"""Variables are 32 bytes in size."""
|
||||
|
||||
CARDINALITY = 2 ** (SIZE * 8)
|
||||
"""
|
||||
The number of distinct values this variable could contain.
|
||||
The maximum integer representable by this Variable is then CARDINALITY - 1.
|
||||
"""
|
||||
|
||||
def __init__(self, values: t.Iterable = None, name: str = VAR_DEFAULT_NAME,
|
||||
def_sites: ssle = ssle.bottom()):
|
||||
# pdb.set_trace()
|
||||
"""
|
||||
Args:
|
||||
values: the set of values this variable could take.
|
||||
name: the name that uniquely identifies this variable.
|
||||
def_sites: a set of locations (TACLocRefs) where this variable
|
||||
was possibly defined.
|
||||
"""
|
||||
|
||||
|
||||
# Make sure the input values are not out of range.
|
||||
#TODO: test by zoey
|
||||
|
||||
# print(values)
|
||||
# print("Debugging memtypes.py in __init__:")
|
||||
# if values is not None:
|
||||
# for i, v in enumerate(values):
|
||||
# if v is None:
|
||||
# print(f"Item at index {i} in values is None")
|
||||
# else:
|
||||
# print(f"Item at index {i} is {v} and its type is {type(v)}")
|
||||
|
||||
# print(name) # zoey->print register
|
||||
|
||||
# 添加日志记录以调查 values 中的 None 值 - Zoey
|
||||
if values is not None and any(v is None for v in values):
|
||||
print(f"\x1b[0;30;41m 警告:在创建 Variable '{name}' 时发现 values 中存在 None 值。相关 values: {values} \x1b[0m ")
|
||||
|
||||
|
||||
# 执行原有逻辑
|
||||
mod = [] if values is None else [v % self.CARDINALITY for v in values]
|
||||
super().__init__(value=mod)
|
||||
self.name = name
|
||||
self.def_sites = def_sites
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
if self.is_top:
|
||||
return type(self).top(self.name, copy.deepcopy(self.def_sites, memodict))
|
||||
if self.is_bottom:
|
||||
return type(self).bottom(self.name, copy.deepcopy(self.def_sites, memodict))
|
||||
|
||||
return type(self)(copy.deepcopy(self.value, memodict),
|
||||
self.name,
|
||||
copy.deepcopy(self.def_sites, memodict))
|
||||
# Note: type(self) dynamically obtains the Variable class.
|
||||
# Hence, no explicit Variable constructor reference required.
|
||||
|
||||
@property
|
||||
def values(self) -> ssle:
|
||||
"""The value set this Variable contains."""
|
||||
return self
|
||||
|
||||
@values.setter
|
||||
def values(self, vals: t.Iterable):
|
||||
"""
|
||||
Set this Variable's value set, ensuring that they are all in range.
|
||||
|
||||
Args:
|
||||
vals: an iterable of values that this Variable will hold
|
||||
"""
|
||||
self.value = ssle(v % self.CARDINALITY for v in vals).value
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
"""Return the string identifying this object."""
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def is_true(self) -> bool:
|
||||
"""
|
||||
True iff all values contained in this variable are nonzero.
|
||||
N.B. is_true is not the inverse of is_false, as Variables are not bivalent.
|
||||
"""
|
||||
if not self.is_finite:
|
||||
return False
|
||||
return not any(c == 0 for c in self)
|
||||
|
||||
@property
|
||||
def is_false(self) -> bool:
|
||||
"""
|
||||
True iff all values contained in this variable are zero.
|
||||
N.B. is_false is not the inverse of is_true, as Variables are not bivalent.
|
||||
"""
|
||||
if not self.is_finite:
|
||||
return False
|
||||
return not any(c != 0 for c in self)
|
||||
|
||||
def __str__(self):
|
||||
if self.is_unconstrained:
|
||||
return self.identifier
|
||||
if self.is_const:
|
||||
return hex(self.const_value)
|
||||
val_str = ", ".join(hex(val) for val in sorted(self.value))
|
||||
return "{{{}}}".format(val_str)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} object {1}, {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.__str__()
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other
|
||||
|
||||
def __hash__(self):
|
||||
if self.is_top:
|
||||
return hash(self.TOP_SYMBOL) ^ hash(self.name)
|
||||
else:
|
||||
# frozenset because plain old sets are unhashable
|
||||
return hash(frozenset(self.value)) ^ hash(self.name)
|
||||
|
||||
@classmethod
|
||||
def meet(cls, a: 'Variable', b: 'Variable') -> 'Variable':
|
||||
"""
|
||||
Return a Variable whose values and def sites are the
|
||||
intersections of the inputs value and def site sets.
|
||||
"""
|
||||
vals = ssle.meet(a, b)
|
||||
sites = ssle.meet(a.def_sites, b.def_sites)
|
||||
if vals.is_top:
|
||||
return cls.top(def_sites=sites)
|
||||
return cls(values=vals, def_sites=sites)
|
||||
|
||||
@classmethod
|
||||
def join(cls, a: 'Variable', b: 'Variable') -> 'Variable':
|
||||
"""
|
||||
Return a Variable whose values and def sites are the
|
||||
unions of the inputs value and def site sets.
|
||||
"""
|
||||
vals = ssle.join(a, b)
|
||||
sites = ssle.join(a.def_sites, b.def_sites)
|
||||
if vals.is_top:
|
||||
return cls.top(def_sites=sites)
|
||||
return cls(values=vals, def_sites=sites)
|
||||
|
||||
@classmethod
|
||||
def top(cls, name=VAR_DEFAULT_NAME, def_sites: ssle = ssle.bottom()) -> 'Variable':
|
||||
"""
|
||||
Return a Variable with Top value, and optionally set its name.
|
||||
|
||||
Args:
|
||||
name: the name of the new variable.
|
||||
def_sites: a set of locations where this variable was possibly defined.
|
||||
"""
|
||||
result = cls(name=name, def_sites=def_sites)
|
||||
result.value = cls._top_val()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def bottom(cls, name=VAR_DEFAULT_NAME, def_sites: ssle = ssle.bottom()) -> 'Variable':
|
||||
"""
|
||||
Return a Variable with Bottom value, and optionally set its name.
|
||||
|
||||
Args:
|
||||
name: the name of the new variable.
|
||||
def_sites: a set of locations where this variable was possibly defined.
|
||||
"""
|
||||
return cls(values=cls._bottom_val(), name=name, def_sites=def_sites)
|
||||
|
||||
@property
|
||||
def const_value(self):
|
||||
"""If this variable is constant, return its value."""
|
||||
if not self.is_const:
|
||||
return None
|
||||
return next(iter(self))
|
||||
|
||||
def complement(self) -> 'Variable':
|
||||
"""
|
||||
Return the signed two's complement interpretation of this constant's values.
|
||||
"""
|
||||
return type(self)(values=self.value.map(self.twos_comp),
|
||||
name=VAR_RESULT_NAME)
|
||||
|
||||
@classmethod
|
||||
def twos_comp(cls, v: int) -> int:
|
||||
"""
|
||||
Return the signed two's complement interpretation of the given integer.
|
||||
"""
|
||||
return v - cls.CARDINALITY if v & (cls.CARDINALITY >> 1) else v
|
||||
|
||||
# EVM arithmetic operations follow.
|
||||
# For comparison operators, "True" and "False" are represented by Constants
|
||||
# with the value 1 and 0 respectively.
|
||||
# Op function names should be identical to the opcode names themselves.
|
||||
|
||||
@classmethod
|
||||
def arith_op(cls, opname: str, args: t.Iterable['Variable'],
|
||||
name=VAR_RESULT_NAME) -> 'Variable':
|
||||
"""
|
||||
Apply the named arithmetic operation to the given Variables' values
|
||||
in all permutations, and return a Variable containing the result.
|
||||
|
||||
Args:
|
||||
opname: the EVM operation to apply.
|
||||
args: a sequence of Variables whose length matches the
|
||||
arity of the specified operation.
|
||||
name: the name of the result Variable.
|
||||
"""
|
||||
result = ssle.cartesian_map(getattr(cls, opname), args)
|
||||
return cls(values=result, name=name)
|
||||
|
||||
@classmethod
|
||||
def ADD(cls, l: int, r: int) -> int:
|
||||
"""Return the sum of the inputs."""
|
||||
return l + r
|
||||
|
||||
@classmethod
|
||||
def MUL(cls, l: int, r: int) -> int:
|
||||
"""Return the product of the inputs."""
|
||||
return l * r
|
||||
|
||||
@classmethod
|
||||
def SUB(cls, l: int, r: int) -> int:
|
||||
"""Return the difference of the inputs."""
|
||||
return l - r
|
||||
|
||||
@classmethod
|
||||
def DIV(cls, l: int, r: int) -> int:
|
||||
"""Return the quotient of the inputs."""
|
||||
return 0 if (r == 0) else (l // r)
|
||||
|
||||
@classmethod
|
||||
def SDIV(cls, l: int, r: int) -> int:
|
||||
"""Return the signed quotient of the inputs."""
|
||||
l_val, r_val = cls.twos_comp(l), cls.twos_comp(r)
|
||||
sign = 1 if ((l_val * r_val) >= 0) else -1
|
||||
return 0 if (r_val == 0) else (sign * (abs(l_val) // abs(r_val)))
|
||||
|
||||
@classmethod
|
||||
def MOD(cls, v: int, m: int) -> int:
|
||||
"""Modulo operator."""
|
||||
return 0 if (m == 0) else (v % m)
|
||||
|
||||
@classmethod
|
||||
def SMOD(cls, v: int, m: int) -> int:
|
||||
"""Signed modulo operator. The output takes the sign of v."""
|
||||
v_val, m_val = cls.twos_comp(v), cls.twos_comp(m)
|
||||
sign = 1 if (v_val >= 0) else -1
|
||||
return 0 if (m == 0) else (sign * (abs(v_val) % abs(m_val)))
|
||||
|
||||
@classmethod
|
||||
def ADDMOD(cls, l: int, r: int, m: int) -> int:
|
||||
"""Modular addition: return (l + r) modulo m."""
|
||||
return 0 if (m == 0) else ((l + r) % m)
|
||||
|
||||
@classmethod
|
||||
def MULMOD(cls, l: int, r: int, m: int) -> int:
|
||||
"""Modular multiplication: return (l * r) modulo m."""
|
||||
return 0 if (m == 0) else ((l * r) % m)
|
||||
|
||||
@classmethod
|
||||
def EXP(cls, b: int, e: int) -> int:
|
||||
"""Exponentiation: return b to the power of e."""
|
||||
return b ** e
|
||||
|
||||
@classmethod
|
||||
def SIGNEXTEND(cls, b: int, v: int) -> int:
|
||||
"""
|
||||
Return v, but with the high bit of its b'th byte extended all the way
|
||||
to the most significant bit of the output.
|
||||
"""
|
||||
pos = 8 * (b + 1)
|
||||
mask = int("1" * ((cls.SIZE * 8) - pos) + "0" * pos, 2)
|
||||
val = 1 if (v & (1 << (pos - 1))) > 0 else 0
|
||||
|
||||
return (v & mask) if (val == 0) else (v | ~mask)
|
||||
|
||||
@classmethod
|
||||
def LT(cls, l: int, r: int) -> int:
|
||||
"""Less-than comparison."""
|
||||
return 1 if (l < r) else 0
|
||||
|
||||
@classmethod
|
||||
def GT(cls, l: int, r: int) -> int:
|
||||
"""Greater-than comparison."""
|
||||
return 1 if (l > r) else 0
|
||||
|
||||
@classmethod
|
||||
def SLT(cls, l: int, r: int) -> int:
|
||||
"""Signed less-than comparison."""
|
||||
return 1 if (cls.twos_comp(l) < cls.twos_comp(r)) else 0
|
||||
|
||||
@classmethod
|
||||
def SGT(cls, l: int, r: int) -> int:
|
||||
"""Signed greater-than comparison."""
|
||||
return 1 if (cls.twos_comp(l) > cls.twos_comp(r)) else 0
|
||||
|
||||
@classmethod
|
||||
def EQ(cls, l: int, r: int) -> int:
|
||||
"""Equality comparison."""
|
||||
return 1 if (l == r) else 0
|
||||
|
||||
@classmethod
|
||||
def ISZERO(cls, v: int) -> int:
|
||||
"""1 if the input is zero, 0 otherwise."""
|
||||
return 1 if (v == 0) else 0
|
||||
|
||||
@classmethod
|
||||
def AND(cls, l: int, r: int) -> int:
|
||||
"""Bitwise AND."""
|
||||
return l & r
|
||||
|
||||
@classmethod
|
||||
def OR(cls, l: int, r: int) -> int:
|
||||
"""Bitwise OR."""
|
||||
return l | r
|
||||
|
||||
@classmethod
|
||||
def XOR(cls, l: int, r: int) -> int:
|
||||
"""Bitwise XOR."""
|
||||
return l ^ r
|
||||
|
||||
@classmethod
|
||||
def NOT(cls, v: int) -> int:
|
||||
"""Bitwise NOT."""
|
||||
return ~v
|
||||
|
||||
@classmethod
|
||||
def BYTE(cls, b: int, v: int) -> int:
|
||||
"""Return the b'th byte of v."""
|
||||
return (v >> (248 - b * 8)) & 0xFF
|
||||
|
||||
@classmethod
|
||||
def SHL(cls, shift: int, value: int) -> int:
|
||||
return value << shift
|
||||
|
||||
@classmethod
|
||||
def SHR(cls, shift: int, value: int) -> int:
|
||||
return value >> shift
|
||||
|
||||
@classmethod
|
||||
def SAR(cls, shift: int, value: int) -> int:
|
||||
return value // (2 ** shift)
|
||||
|
||||
|
||||
class MetaVariable(Variable):
|
||||
"""A Variable to stand in for Variables."""
|
||||
|
||||
def __init__(self, name: str, payload=None, def_sites: ssle = ssle.bottom()):
|
||||
"""
|
||||
Args:
|
||||
name: the name of the new MetaVariable
|
||||
payload: some information to carry along with this MetaVariable.
|
||||
def_sites: a set of locations where this variable was possibly defined.
|
||||
"""
|
||||
super().__init__(values=self._bottom_val(), name=name, def_sites=def_sites)
|
||||
|
||||
self.value = self._top_val()
|
||||
"""
|
||||
The value of this MetaVariable.
|
||||
MetaVariables are taken to have unconstrained value sets.
|
||||
"""
|
||||
|
||||
self.payload = payload
|
||||
|
||||
def __str__(self):
|
||||
return self.identifier
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
return type(self)(self.name,
|
||||
self.payload,
|
||||
copy.deepcopy(self.def_sites, memodict))
|
||||
|
||||
|
||||
class VariableStack(LatticeElement):
|
||||
"""
|
||||
A stack that holds TAC variables.
|
||||
It is also a lattice, so meet and join are defined, and they operate
|
||||
element-wise from the top of the stack down.
|
||||
|
||||
The stack is taken to be of infinite capacity, with empty slots extending
|
||||
indefinitely downwards. An empty stack slot is interpreted as a Variable
|
||||
with Bottom value, for the purposes of the lattice definition.
|
||||
Thus an empty stack would be this lattice's Bottom, and a stack "filled" with
|
||||
Top Variables would be its Top.
|
||||
We therefore have a bounded lattice, but we don't need the extra complexity
|
||||
associated with the BoundedLatticeElement class.
|
||||
"""
|
||||
|
||||
DEFAULT_MAX = 1024
|
||||
"""
|
||||
The default maximum size of a variable stack.
|
||||
Any further elements pushed to a stack that is at its capacity are discarded.
|
||||
"""
|
||||
|
||||
DEFAULT_MIN_MAX_SIZE = 20
|
||||
"""The minimum maximum size of a variable stack."""
|
||||
|
||||
def __init__(self, state: t.Iterable[Variable] = None,
|
||||
max_size=DEFAULT_MAX, min_max_size=DEFAULT_MIN_MAX_SIZE,
|
||||
call_depth: int = None):
|
||||
super().__init__([] if state is None else list(state))
|
||||
|
||||
self.empty_pops = 0
|
||||
"""The number of times the stack was popped while empty."""
|
||||
|
||||
self.min_max_size = min_max_size
|
||||
"""
|
||||
The minimum size of this variable stack's maximum size.
|
||||
Taking the meet of two stacks produces a stack whose maximum size is the
|
||||
smaller of the two, but at least as large as this value.
|
||||
"""
|
||||
|
||||
self.max_size = max_size
|
||||
"""
|
||||
The maximum size of this variable stack before it overflows.
|
||||
Pushing to a full stack has no effect.
|
||||
"""
|
||||
self.set_max_size(max_size)
|
||||
|
||||
self.call_depth = call_depth
|
||||
|
||||
def __iter__(self):
|
||||
"""Iteration occurs from head of stack downwards."""
|
||||
return iter(reversed(self.value))
|
||||
|
||||
def __str__(self):
|
||||
return "[{}]".format(", ".join(str(v) for v in self.value))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.value)
|
||||
|
||||
def __eq__(self, other):
|
||||
return len(self) == len(other) and \
|
||||
all(v1 == v2 for v1, v2 in
|
||||
zip(reversed(self.value), reversed(other.value)))
|
||||
|
||||
def copy(self) -> 'VariableStack':
|
||||
"""
|
||||
Produce a copy of this stack, without deep copying
|
||||
the variables it contains.
|
||||
"""
|
||||
new_stack = type(self)()
|
||||
new_stack.value = copy.copy(self.value)
|
||||
new_stack.empty_pops = self.empty_pops
|
||||
new_stack.max_size = self.max_size
|
||||
return new_stack
|
||||
|
||||
def metafy(self) -> None:
|
||||
"""
|
||||
Turn all unconstrained variables into metavariables whose labels
|
||||
are their current stack position.
|
||||
"""
|
||||
for i in range(len(self)):
|
||||
var = self.value[-(i + 1)]
|
||||
if var.is_unconstrained:
|
||||
self.value[-(i + 1)] = self.__new_metavar(i, def_sites=var.def_sites)
|
||||
|
||||
@staticmethod
|
||||
def __new_metavar(n: int, def_sites: ssle = ssle.bottom()) -> MetaVariable:
|
||||
"""Return a MetaVariable with the given payload and a corresponding name."""
|
||||
return MetaVariable(name="S{}".format(n), payload=n, def_sites=def_sites)
|
||||
|
||||
def peek(self, n: int = 0) -> Variable:
|
||||
"""Return the n'th element from the top without popping anything."""
|
||||
if n >= len(self):
|
||||
return self.__new_metavar(n - len(self) + self.empty_pops)
|
||||
return self.value[-(n + 1)]
|
||||
|
||||
def push(self, var: Variable) -> None:
|
||||
"""Push a variable to the stack."""
|
||||
if len(self.value) < self.max_size:
|
||||
self.value.append(var)
|
||||
|
||||
def pop(self) -> Variable:
|
||||
"""
|
||||
Pop a variable off our symbolic stack if one exists, otherwise
|
||||
generate a variable from past the bottom.
|
||||
"""
|
||||
if len(self.value):
|
||||
return self.value.pop()
|
||||
else:
|
||||
self.empty_pops += 1
|
||||
return self.__new_metavar(self.empty_pops - 1)
|
||||
|
||||
def push_many(self, vs: t.Iterable[Variable]) -> None:
|
||||
"""
|
||||
Push a sequence of elements onto the stack.
|
||||
Low index elements are pushed first.
|
||||
"""
|
||||
for v in vs:
|
||||
self.push(v)
|
||||
|
||||
def pop_many(self, n: int) -> t.List[Variable]:
|
||||
"""
|
||||
Pop and return n items from the stack.
|
||||
First-popped elements inhabit low indices.
|
||||
"""
|
||||
return [self.pop() for _ in range(n)]
|
||||
|
||||
def dup(self, n: int) -> None:
|
||||
"""Place a copy of stack[n-1] on the top of the stack."""
|
||||
items = self.pop_many(n)
|
||||
duplicated = [items[-1]] + items
|
||||
self.push_many(reversed(duplicated))
|
||||
|
||||
def swap(self, n: int) -> None:
|
||||
"""Swap stack[0] with stack[n]."""
|
||||
items = self.pop_many(n)
|
||||
swapped = [items[-1]] + items[1:-1] + [items[0]]
|
||||
self.push_many(reversed(swapped))
|
||||
|
||||
def set_max_size(self, n: int) -> None:
|
||||
"""Set this stack's maximum capacity."""
|
||||
new_size = max(self.min_max_size, n)
|
||||
self.max_size = new_size
|
||||
self.value = self.value[-new_size:]
|
||||
|
||||
@classmethod
|
||||
def meet(cls, a: 'VariableStack', b: 'VariableStack') -> 'VariableStack':
|
||||
"""
|
||||
Return the meet of the given stacks, taking the element-wise meets of their
|
||||
contained Variables from the top down.
|
||||
"""
|
||||
|
||||
pairs = zip_longest(reversed(a.value), reversed(b.value),
|
||||
fillvalue=Variable.bottom())
|
||||
max_size = a.max_size if a.max_size < b.max_size else b.max_size
|
||||
return cls(dropwhile(lambda x: x.is_bottom,
|
||||
[Variable.meet(*p) for p in pairs][::-1]),
|
||||
max_size)
|
||||
|
||||
@classmethod
|
||||
def join(cls, a: 'VariableStack', b: 'VariableStack') -> 'VariableStack':
|
||||
"""
|
||||
Return the join of the given stacks, taking the element-wise joins of their
|
||||
contained Variables from the top down.
|
||||
"""
|
||||
|
||||
pairs = zip_longest(reversed(a.value), reversed(b.value),
|
||||
fillvalue=Variable.bottom())
|
||||
max_size = a.max_size if a.max_size > b.max_size else b.max_size
|
||||
return cls([Variable.join(*p) for p in pairs][::-1], max_size)
|
||||
|
||||
@classmethod
|
||||
def join_all(cls, elements: t.Iterable['VariableStack']) -> 'VariableStack':
|
||||
"""
|
||||
Return the common meet of the given sequence; an empty sequence
|
||||
yields an empty stack.
|
||||
"""
|
||||
return super().join_all(elements, initial=VariableStack())
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""opcodes.py: Definitions of all EVM opcodes, and related utility functions."""
|
||||
|
||||
|
||||
class OpCode:
|
||||
"""An EVM opcode."""
|
||||
|
||||
def __init__(self, name: str, code: int, pop: int, push: int):
|
||||
"""
|
||||
Args:
|
||||
name (str): Human-readable opcode, such as opcode PUSH1.
|
||||
code (int): The instruction byte itself.
|
||||
pop (int): The number of stack elements this op pops.
|
||||
push (int): The number of stack elements this op pushes.
|
||||
"""
|
||||
self.name = name
|
||||
self.code = code
|
||||
self.pop = pop
|
||||
self.push = push
|
||||
|
||||
def stack_delta(self) -> int:
|
||||
"""Return the net effect on the stack size of running this operation."""
|
||||
return self.push - self.pop
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{0} object {1}, {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.__str__()
|
||||
)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return self.code == other.code
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self.code.__hash__()
|
||||
|
||||
# Special cases for kind one, such as CALLVALUE
|
||||
# Those opcodes do not need anything from stack, but will give related dynamic info
|
||||
# TODO: adding SELFBALANCE, CHAINID, BASEFEE for the current EVM opcode version - zoey
|
||||
def is_kind_one(self) -> bool:
|
||||
special = {ADDRESS.name, ORIGIN.name, CALLER.name, CALLVALUE.name, CALLDATASIZE.name,
|
||||
CODESIZE.name, GASPRICE.name, RETURNDATASIZE.name, COINBASE.name, TIMESTAMP.name,
|
||||
NUMBER.name, DIFFICULTY.name, GASLIMIT.name, PC.name, MSIZE.name, GAS.name,
|
||||
SELFBALANCE.name, CHAINID.name,BASEFEE.name}
|
||||
|
||||
|
||||
return self.name in special
|
||||
|
||||
# Special cases for kind two, such as CALLDATALOAD
|
||||
# Need one or more stack arguments and related dynamic info
|
||||
# TODO: adding BALANCE for the current EVM opcode version - zoey
|
||||
def is_kind_two(self) -> bool:
|
||||
special = {SHA3.name, BALANCE.name, CALLDATALOAD.name, EXTCODESIZE.name, EXTCODEHASH.name, BLOCKHASH.name,
|
||||
BALANCE.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for part of kind three load, SLOAD
|
||||
def is_kind_three_load(self) -> bool:
|
||||
special = {SLOAD.name,MLOAD.name} # zoey 2.19 - add MLOAD.name as it should work as the SLOAD
|
||||
return self.name in special
|
||||
|
||||
# Special cases for part of kind three store, like MSTORE
|
||||
def is_kind_three_store_one(self) -> bool:
|
||||
special = {MSTORE.name, MSTORE8.name, SSTORE.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for some other store operations, they are special
|
||||
# since they do need some arguments from the stack and then get the related data
|
||||
# to store them into the memory
|
||||
def is_kind_three_store_two(self) -> bool:
|
||||
special = {CALLDATACOPY.name, CODECOPY.name, EXTCODECOPY.name, RETURNDATACOPY.name}
|
||||
return self.name in special
|
||||
|
||||
# Special cases for four call opcodes
|
||||
def is_kind_four(self) -> bool:
|
||||
special = {CALL.name, CALLCODE.name, DELEGATECALL.name, STATICCALL.name}
|
||||
return self.name in special
|
||||
|
||||
def is_kind_five(self) -> bool:
|
||||
special = {CREATE.name, CREATE2.name}
|
||||
return self.name in special
|
||||
|
||||
def op_pc_gap(self) -> int:
|
||||
if self.is_push():
|
||||
return int(self.name[4]) + 1
|
||||
else:
|
||||
return 1
|
||||
|
||||
def is_push(self) -> bool:
|
||||
"""Predicate: opcode is a push operation."""
|
||||
# return PUSH1.code <= self.code <= PUSH32.code
|
||||
return PUSH0.code <= self.code <= PUSH32.code # TODO: adding PUSH0 for EIP-3855 - zoey
|
||||
|
||||
def is_swap(self) -> bool:
|
||||
"""Predicate: opcode is a swap operation."""
|
||||
return SWAP1.code <= self.code <= SWAP16.code
|
||||
|
||||
def is_dup(self) -> bool:
|
||||
"""Predicate: opcode is a dup operation."""
|
||||
return DUP1.code <= self.code <= DUP16.code
|
||||
|
||||
def is_log(self) -> bool:
|
||||
"""Predicate: opcode is a log operation."""
|
||||
return LOG0.code <= self.code <= LOG4.code
|
||||
|
||||
def is_missing(self) -> bool:
|
||||
return self.code not in BYTECODES
|
||||
|
||||
def is_invalid(self) -> bool:
|
||||
return (self.code == INVALID.code) or self.is_missing()
|
||||
|
||||
|
||||
def is_arithmetic(self) -> bool:
|
||||
"""Predicate: opcode's result can be calculated from its inputs alone."""
|
||||
return (ADD.code <= self.code <= SIGNEXTEND.code) or \
|
||||
(LT.code <= self.code <= SAR.code)
|
||||
|
||||
|
||||
def is_memory(self) -> bool:
|
||||
"""Predicate: opcode operates on memory"""
|
||||
return MLOAD.code <= self.code <= MSTORE8.code
|
||||
|
||||
def is_storage(self) -> bool:
|
||||
"""Predicate: opcode operates on storage ('the tape')"""
|
||||
return SLOAD.code <= self.code <= SSTORE.code
|
||||
|
||||
def is_call(self) -> bool:
|
||||
"""Predicate: opcode calls an external contract"""
|
||||
return self in (CALL, CALLCODE, DELEGATECALL, STATICCALL,)
|
||||
|
||||
def alters_flow(self) -> bool:
|
||||
"""Predicate: opcode alters EVM control flow."""
|
||||
return (self.code in (JUMP.code, JUMPI.code,)) or self.possibly_halts()
|
||||
|
||||
def is_exception(self) -> bool:
|
||||
"""Predicate: opcode causes the EVM to throw an exception."""
|
||||
return (self.code in (THROW.code, THROWI.code, REVERT.code)) \
|
||||
or self.is_invalid()
|
||||
|
||||
def halts(self) -> bool:
|
||||
"""Predicate: opcode causes the EVM to halt."""
|
||||
halt_codes = (
|
||||
STOP.code,
|
||||
RETURN.code,
|
||||
SELFDESTRUCT.code,
|
||||
THROW.code,
|
||||
REVERT.code,
|
||||
)
|
||||
return (self.code in halt_codes) or self.is_invalid()
|
||||
|
||||
def possibly_halts(self) -> bool:
|
||||
"""Predicate: opcode MAY cause the EVM to halt. (halts + THROWI)"""
|
||||
return self.halts() or self.code == THROWI.code
|
||||
|
||||
def push_len(self) -> int:
|
||||
"""Return the number of bytes the given PUSH instruction pushes."""
|
||||
if self.code == PUSH0.code: #
|
||||
return PUSH1.code # this if statement is added for EIP-3855 PUSH0- zoey
|
||||
return self.code - PUSH1.code + 1 if self.is_push() else 0
|
||||
|
||||
def log_len(self) -> int:
|
||||
"""Return the number of topics the given LOG instruction includes."""
|
||||
return self.code - LOG0.code if self.is_log() else 0
|
||||
|
||||
|
||||
# Construct all EVM opcodes
|
||||
# Arithmetic Ops and STOP
|
||||
STOP = OpCode("STOP", 0x00, 0, 0)
|
||||
ADD = OpCode("ADD", 0x01, 2, 1)
|
||||
MUL = OpCode("MUL", 0x02, 2, 1)
|
||||
SUB = OpCode("SUB", 0x03, 2, 1)
|
||||
DIV = OpCode("DIV", 0x04, 2, 1)
|
||||
SDIV = OpCode("SDIV", 0x05, 2, 1)
|
||||
MOD = OpCode("MOD", 0x06, 2, 1)
|
||||
SMOD = OpCode("SMOD", 0x07, 2, 1)
|
||||
ADDMOD = OpCode("ADDMOD", 0x08, 3, 1)
|
||||
MULMOD = OpCode("MULMOD", 0x09, 3, 1)
|
||||
EXP = OpCode("EXP", 0x0a, 2, 1)
|
||||
SIGNEXTEND = OpCode("SIGNEXTEND", 0x0b, 2, 1)
|
||||
|
||||
# Comparison and Bitwise Logic
|
||||
LT = OpCode("LT", 0x10, 2, 1)
|
||||
GT = OpCode("GT", 0x11, 2, 1)
|
||||
SLT = OpCode("SLT", 0x12, 2, 1)
|
||||
SGT = OpCode("SGT", 0x13, 2, 1)
|
||||
EQ = OpCode("EQ", 0x14, 2, 1)
|
||||
ISZERO = OpCode("ISZERO", 0x15, 1, 1)
|
||||
AND = OpCode("AND", 0x16, 2, 1)
|
||||
OR = OpCode("OR", 0x17, 2, 1)
|
||||
XOR = OpCode("XOR", 0x18, 2, 1)
|
||||
NOT = OpCode("NOT", 0x19, 1, 1)
|
||||
BYTE = OpCode("BYTE", 0x1a, 2, 1)
|
||||
SHL = OpCode("SHL", 0x1b, 2, 1)
|
||||
SHR = OpCode("SHR", 0x1c, 2, 1)
|
||||
SAR = OpCode("SAR", 0x1d, 2, 1)
|
||||
|
||||
SHA3 = OpCode("SHA3", 0x20, 2, 1)
|
||||
KECCAK256 = OpCode("KECCAK256", 0x20, 2, 1)
|
||||
|
||||
# Environmental Information
|
||||
ADDRESS = OpCode("ADDRESS", 0x30, 0, 1)
|
||||
BALANCE = OpCode("BALANCE", 0x31, 1, 1)
|
||||
ORIGIN = OpCode("ORIGIN", 0x32, 0, 1)
|
||||
CALLER = OpCode("CALLER", 0x33, 0, 1)
|
||||
CALLVALUE = OpCode("CALLVALUE", 0x34, 0, 1)
|
||||
CALLDATALOAD = OpCode("CALLDATALOAD", 0x35, 1, 1)
|
||||
CALLDATASIZE = OpCode("CALLDATASIZE", 0x36, 0, 1)
|
||||
CALLDATACOPY = OpCode("CALLDATACOPY", 0x37, 3, 0)
|
||||
CODESIZE = OpCode("CODESIZE", 0x38, 0, 1)
|
||||
CODECOPY = OpCode("CODECOPY", 0x39, 3, 0)
|
||||
GASPRICE = OpCode("GASPRICE", 0x3a, 0, 1)
|
||||
EXTCODESIZE = OpCode("EXTCODESIZE", 0x3b, 1, 1)
|
||||
EXTCODECOPY = OpCode("EXTCODECOPY", 0x3c, 4, 0)
|
||||
|
||||
# Block Information
|
||||
BLOCKHASH = OpCode("BLOCKHASH", 0x40, 1, 1)
|
||||
COINBASE = OpCode("COINBASE", 0x41, 0, 1)
|
||||
TIMESTAMP = OpCode("TIMESTAMP", 0x42, 0, 1)
|
||||
NUMBER = OpCode("NUMBER", 0x43, 0, 1)
|
||||
DIFFICULTY = OpCode("DIFFICULTY", 0x44, 0, 1)
|
||||
GASLIMIT = OpCode("GASLIMIT", 0x45, 0, 1)
|
||||
CHAINID = OpCode("CHAINID", 0x46, 0, 1) # Zoey
|
||||
BASEFEE = OpCode("BASEFEE", 0x48, 0, 1) # Zoey
|
||||
|
||||
|
||||
# Stack, Memory, Storage, Flow
|
||||
POP = OpCode("POP", 0x50, 1, 0)
|
||||
MLOAD = OpCode("MLOAD", 0x51, 1, 1)
|
||||
MSTORE = OpCode("MSTORE", 0x52, 2, 0)
|
||||
MSTORE8 = OpCode("MSTORE8", 0x53, 2, 0)
|
||||
SLOAD = OpCode("SLOAD", 0x54, 1, 1)
|
||||
SSTORE = OpCode("SSTORE", 0x55, 2, 0)
|
||||
JUMP = OpCode("JUMP", 0x56, 1, 0)
|
||||
JUMPI = OpCode("JUMPI", 0x57, 2, 0)
|
||||
PC = OpCode("PC", 0x58, 0, 1)
|
||||
MSIZE = OpCode("MSIZE", 0x59, 0, 1)
|
||||
GAS = OpCode("GAS", 0x5a, 0, 1)
|
||||
JUMPDEST = OpCode("JUMPDEST", 0x5b, 0, 0)
|
||||
|
||||
PUSH1 = OpCode("PUSH1", 0x60, 0, 1)
|
||||
PUSH2 = OpCode("PUSH2", 0x61, 0, 1)
|
||||
PUSH3 = OpCode("PUSH3", 0x62, 0, 1)
|
||||
PUSH4 = OpCode("PUSH4", 0x63, 0, 1)
|
||||
PUSH5 = OpCode("PUSH5", 0x64, 0, 1)
|
||||
PUSH6 = OpCode("PUSH6", 0x65, 0, 1)
|
||||
PUSH7 = OpCode("PUSH7", 0x66, 0, 1)
|
||||
PUSH8 = OpCode("PUSH8", 0x67, 0, 1)
|
||||
PUSH9 = OpCode("PUSH9", 0x68, 0, 1)
|
||||
PUSH10 = OpCode("PUSH10", 0x69, 0, 1)
|
||||
PUSH11 = OpCode("PUSH11", 0x6a, 0, 1)
|
||||
PUSH12 = OpCode("PUSH12", 0x6b, 0, 1)
|
||||
PUSH13 = OpCode("PUSH13", 0x6c, 0, 1)
|
||||
PUSH14 = OpCode("PUSH14", 0x6d, 0, 1)
|
||||
PUSH15 = OpCode("PUSH15", 0x6e, 0, 1)
|
||||
PUSH16 = OpCode("PUSH16", 0x6f, 0, 1)
|
||||
PUSH17 = OpCode("PUSH17", 0x70, 0, 1)
|
||||
PUSH18 = OpCode("PUSH18", 0x71, 0, 1)
|
||||
PUSH19 = OpCode("PUSH19", 0x72, 0, 1)
|
||||
PUSH20 = OpCode("PUSH20", 0x73, 0, 1)
|
||||
PUSH21 = OpCode("PUSH21", 0x74, 0, 1)
|
||||
PUSH22 = OpCode("PUSH22", 0x75, 0, 1)
|
||||
PUSH23 = OpCode("PUSH23", 0x76, 0, 1)
|
||||
PUSH24 = OpCode("PUSH24", 0x77, 0, 1)
|
||||
PUSH25 = OpCode("PUSH25", 0x78, 0, 1)
|
||||
PUSH26 = OpCode("PUSH26", 0x79, 0, 1)
|
||||
PUSH27 = OpCode("PUSH27", 0x7a, 0, 1)
|
||||
PUSH28 = OpCode("PUSH28", 0x7b, 0, 1)
|
||||
PUSH29 = OpCode("PUSH29", 0x7c, 0, 1)
|
||||
PUSH30 = OpCode("PUSH30", 0x7d, 0, 1)
|
||||
PUSH31 = OpCode("PUSH31", 0x7e, 0, 1)
|
||||
PUSH32 = OpCode("PUSH32", 0x7f, 0, 1)
|
||||
|
||||
DUP1 = OpCode("DUP1", 0x80, 1, 2)
|
||||
DUP2 = OpCode("DUP2", 0x81, 2, 3)
|
||||
DUP3 = OpCode("DUP3", 0x82, 3, 4)
|
||||
DUP4 = OpCode("DUP4", 0x83, 4, 5)
|
||||
DUP5 = OpCode("DUP5", 0x84, 5, 6)
|
||||
DUP6 = OpCode("DUP6", 0x85, 6, 7)
|
||||
DUP7 = OpCode("DUP7", 0x86, 7, 8)
|
||||
DUP8 = OpCode("DUP8", 0x87, 8, 9)
|
||||
DUP9 = OpCode("DUP9", 0x88, 9, 10)
|
||||
DUP10 = OpCode("DUP10", 0x89, 10, 11)
|
||||
DUP11 = OpCode("DUP11", 0x8a, 11, 12)
|
||||
DUP12 = OpCode("DUP12", 0x8b, 12, 13)
|
||||
DUP13 = OpCode("DUP13", 0x8c, 13, 14)
|
||||
DUP14 = OpCode("DUP14", 0x8d, 14, 15)
|
||||
DUP15 = OpCode("DUP15", 0x8e, 15, 16)
|
||||
DUP16 = OpCode("DUP16", 0x8f, 16, 17)
|
||||
|
||||
SWAP1 = OpCode("SWAP1", 0x90, 2, 2)
|
||||
SWAP2 = OpCode("SWAP2", 0x91, 3, 3)
|
||||
SWAP3 = OpCode("SWAP3", 0x92, 4, 4)
|
||||
SWAP4 = OpCode("SWAP4", 0x93, 5, 5)
|
||||
SWAP5 = OpCode("SWAP5", 0x94, 6, 6)
|
||||
SWAP6 = OpCode("SWAP6", 0x95, 7, 7)
|
||||
SWAP7 = OpCode("SWAP7", 0x96, 8, 8)
|
||||
SWAP8 = OpCode("SWAP8", 0x97, 9, 9)
|
||||
SWAP9 = OpCode("SWAP9", 0x98, 10, 10)
|
||||
SWAP10 = OpCode("SWAP10", 0x99, 11, 11)
|
||||
SWAP11 = OpCode("SWAP11", 0x9a, 12, 12)
|
||||
SWAP12 = OpCode("SWAP12", 0x9b, 13, 13)
|
||||
SWAP13 = OpCode("SWAP13", 0x9c, 14, 14)
|
||||
SWAP14 = OpCode("SWAP14", 0x9d, 15, 15)
|
||||
SWAP15 = OpCode("SWAP15", 0x9e, 16, 16)
|
||||
SWAP16 = OpCode("SWAP16", 0x9f, 17, 17)
|
||||
|
||||
# Logging
|
||||
LOG0 = OpCode("LOG0", 0xa0, 2, 0)
|
||||
LOG1 = OpCode("LOG1", 0xa1, 3, 0)
|
||||
LOG2 = OpCode("LOG2", 0xa2, 4, 0)
|
||||
LOG3 = OpCode("LOG3", 0xa3, 5, 0)
|
||||
LOG4 = OpCode("LOG4", 0xa4, 6, 0)
|
||||
|
||||
# System Operations
|
||||
CREATE = OpCode("CREATE", 0xf0, 3, 1)
|
||||
CREATE2 = OpCode("CREATE2", 0xf5, 4, 1)
|
||||
CALL = OpCode("CALL", 0xf1, 7, 1)
|
||||
CALLCODE = OpCode("CALLCODE", 0xf2, 7, 1)
|
||||
RETURN = OpCode("RETURN", 0xf3, 2, 0)
|
||||
DELEGATECALL = OpCode("DELEGATECALL", 0xf4, 6, 1)
|
||||
INVALID = OpCode("INVALID", 0xfe, 0, 0)
|
||||
SELFDESTRUCT = OpCode("SELFDESTRUCT", 0xff, 1, 0)
|
||||
|
||||
# New Byzantinium OpCodes for block.number >= BYZANTIUM_FORK_BLKNUM
|
||||
REVERT = OpCode("REVERT", 0xfd, 2, 0)
|
||||
RETURNDATASIZE = OpCode("RETURNDATASIZE", 0x3d, 0, 1)
|
||||
RETURNDATACOPY = OpCode("RETURNDATACOPY", 0x3e, 3, 0)
|
||||
STATICCALL = OpCode("STATICCALL", 0xfa, 6, 1)
|
||||
|
||||
# TAC Operations
|
||||
# These are not EVM opcodes, but they are used by the three-address code
|
||||
NOP = OpCode("NOP", -1, 0, 0)
|
||||
CONST = OpCode("CONST", -2, 0, 0)
|
||||
LOG = OpCode("LOG", -3, 0, 0)
|
||||
THROW = OpCode("THROW", -4, 0, 0)
|
||||
THROWI = OpCode("THROWI", -5, 0, 0)
|
||||
|
||||
# TODO: adding when debuging - zoey
|
||||
SELFBALANCE = OpCode("SELFBALANCE", 0x47, 0, 1)
|
||||
EXTCODEHASH = OpCode("EXTCODEHASH", 0x3f, 1, 1)
|
||||
PUSH0 = OpCode("PUSH0", 0x5f, 0, 1)
|
||||
# Assuming you want PUSH0 to push 0 bytes onto the stack, which is unusual for EVM
|
||||
# PUSH0 = OpCode("PUSH0", [unique_value], 0, 1)
|
||||
|
||||
|
||||
# Produce mappings from names and instruction codes to opcode objects
|
||||
OPCODES = {
|
||||
code.name: code
|
||||
for code in globals().values()
|
||||
if isinstance(code, OpCode)
|
||||
}
|
||||
"""Dictionary mapping of opcode string names to EVM OpCode objects"""
|
||||
|
||||
# Handle incorrect opcode name from go-ethereum disasm
|
||||
OPCODES["TXGASPRICE"] = OPCODES["GASPRICE"]
|
||||
|
||||
BYTECODES = {code.code: code for code in OPCODES.values()}
|
||||
"""Dictionary mapping of byte values to EVM OpCode objects"""
|
||||
|
||||
|
||||
def opcode_by_name(name: str) -> OpCode:
|
||||
"""
|
||||
Mapping: Retrieves the named OpCode object (case-insensitive).
|
||||
|
||||
Throws:
|
||||
LookupError: if there is no opcode defined with the given name.
|
||||
"""
|
||||
name = name.upper()
|
||||
if name not in OPCODES:
|
||||
raise LookupError("No opcode named '{}'.".format(name))
|
||||
return OPCODES[name]
|
||||
|
||||
|
||||
def opcode_by_value(val: int) -> OpCode:
|
||||
"""
|
||||
Mapping: Retrieves the OpCode object with the given value.
|
||||
|
||||
Throws:
|
||||
LookupError: if there is no opcode defined with the given value.
|
||||
"""
|
||||
if val not in BYTECODES:
|
||||
raise LookupError("No opcode with value '0x{:02X}'.".format(val))
|
||||
return BYTECODES[val]
|
||||
|
||||
|
||||
def missing_opcode(val: int) -> OpCode:
|
||||
"""
|
||||
Produces a new OpCode with the given value, as long as that is
|
||||
an unknown code.
|
||||
|
||||
Throws:
|
||||
ValueError: if there is an opcode defined with the given value.
|
||||
"""
|
||||
if val in BYTECODES:
|
||||
raise ValueError("Opcode {} exists.")
|
||||
return OpCode("MISSING", val, 0, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""patterns.py: abstract base classes for the various design patterns"""
|
||||
|
||||
import abc
|
||||
import inspect
|
||||
|
||||
|
||||
class Visitable(abc.ABC):
|
||||
"""
|
||||
Provides an interface for an object which can accept a :obj:`Visitor`.
|
||||
"""
|
||||
|
||||
def accept(self, visitor: 'Visitor'):
|
||||
"""
|
||||
Accepts a :obj:`Visitor` and calls :obj:`Visitor.visit`
|
||||
|
||||
Args:
|
||||
visitor: the :obj:`Visitor` to accept
|
||||
"""
|
||||
visitor.visit(self)
|
||||
|
||||
|
||||
class Visitor(abc.ABC):
|
||||
"""
|
||||
Visitor design pattern abstract base class.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def visit(self, target: Visitable, *args, **kwargs):
|
||||
"""
|
||||
Visits the given object.
|
||||
|
||||
Args:
|
||||
target: object to visit.
|
||||
"""
|
||||
|
||||
def can_visit(self, type_):
|
||||
"""
|
||||
Checks if this :obj:`Visitor` can visit an object of the given `type_`.
|
||||
By default a :obj:`Visitor` can visit all types, so subclasses of
|
||||
:obj:`Visitor` should override this method if necessary.
|
||||
|
||||
Args:
|
||||
type_ (type): a valid Python :obj:`type` to be checked.
|
||||
|
||||
Returns:
|
||||
True (by default)
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class DynamicVisitor(Visitor):
|
||||
"""
|
||||
Visitor base class which dynamically calls a specialised visit method based
|
||||
on the target's type at runtime.
|
||||
|
||||
Example:
|
||||
Subclassing :obj:`DynamicVisitor`::
|
||||
|
||||
class PrinterDynamicVisitor(DynamicVisitor):
|
||||
def visit_str(self, string:str):
|
||||
print(string)
|
||||
|
||||
def visit_int(self, integer:int):
|
||||
print("{:08b}".format(integer))
|
||||
|
||||
def visit_object(self, obj:object):
|
||||
print(obj)
|
||||
|
||||
pdv = PrinterDynamicVisitor()
|
||||
pdv.visit("hello")
|
||||
pdv.visit(5)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Don't allow instantiation of DynamicVisitor itself
|
||||
if type(self) is DynamicVisitor:
|
||||
raise NotImplementedError("DynamicVisitor must be sub-classed")
|
||||
|
||||
def visit(self, target: Visitable, *args, **kwargs):
|
||||
"""
|
||||
Dispatches to a method called visit_TYPE where TYPE is the dynamic type
|
||||
(or the nearest parent type) of the `target`.
|
||||
|
||||
Args:
|
||||
target: object to visit.
|
||||
*args: arguments to be passed to the type-specific visit method.
|
||||
**kwargs: optional/keyword arguments to be passed to the type-specific
|
||||
visit method.
|
||||
"""
|
||||
# Try to find a visit method for our target's type
|
||||
visit_method = self.__get_visit_method(type(target))
|
||||
|
||||
# If we found a visit method, call it and return its returned value
|
||||
if visit_method is not None:
|
||||
return visit_method(target, *args, **kwargs)
|
||||
|
||||
# If no matching visit_TYPE method exists, call _no_visit_found
|
||||
return self._no_visit_found(target, *args, **kwargs)
|
||||
|
||||
def can_visit(self, type_):
|
||||
"""
|
||||
Checks if this :obj:`DynamicVisitor` can visit an object of the given
|
||||
`type_`.
|
||||
|
||||
Args:
|
||||
type_ (type): a valid Python :obj:`type` to be checked.
|
||||
|
||||
Returns:
|
||||
True if the current :obj:`DynamicVisitor` can visit the specified
|
||||
`type_` or False otherwise.
|
||||
"""
|
||||
return self.__get_visit_method(type_) is not None
|
||||
|
||||
def __get_visit_method(self, type_):
|
||||
"""
|
||||
Returns a visit method for the given type_, or None if none could be
|
||||
found.
|
||||
"""
|
||||
# Try all the type names in the target's MRO
|
||||
for base in inspect.getmro(type_):
|
||||
visit_name = "visit_{}".format(base.__name__)
|
||||
|
||||
# If we found a matching visit_TYPE method, return it
|
||||
if hasattr(self, visit_name):
|
||||
visit_method = getattr(self, visit_name)
|
||||
return visit_method
|
||||
|
||||
# Not found => return None
|
||||
return None
|
||||
|
||||
def _no_visit_found(self, target, *args, **kwargs):
|
||||
"""
|
||||
Called when no matching visit_TYPE method exists for the target's type.
|
||||
Raises a TypeError by default and should be overridden if different
|
||||
behaviour is desired by a derived class.
|
||||
|
||||
Args:
|
||||
target: object passed to :obj:`visit`
|
||||
*args: arguments passed to :obj:`visit`
|
||||
**kwargs: keyword arguments passed to :obj:`visit`
|
||||
|
||||
Raises:
|
||||
TypeError
|
||||
"""
|
||||
raise TypeError("could not find a visit method for target type {}"
|
||||
.format(type(target).__name__))
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""settings.py: dataflow analysis settings.
|
||||
|
||||
The user can change these settings in bin/config.ini or by providing command
|
||||
line flags to override them.
|
||||
Default settings are stored in src/default_config.ini.
|
||||
|
||||
max_iterations:
|
||||
The maximum number of graph analysis iterations.
|
||||
Lower is faster, but potentially less precise.
|
||||
A negative value means no limit. No limit by default.
|
||||
|
||||
bailout_seconds:
|
||||
Begin to terminate the analysis loop if it's looking to take more time
|
||||
than specified. Bailing out early may mean the analysis is not able
|
||||
to reach a fixed-point, so the results may be less precise.
|
||||
This is not a hard cap, as subsequent analysis steps are required,
|
||||
and at least one iteration will always be performed.
|
||||
A negative value means no cap on the running time.
|
||||
No cap by default.
|
||||
|
||||
remove_unreachable:
|
||||
Upon completion of the analysis, if there are blocks unreachable from the
|
||||
contract root, remove them. False by default.
|
||||
|
||||
merge_unreachable:
|
||||
Upon completion of the analysis, if there are blocks unreachable from the
|
||||
contract root, merge them. True by default.
|
||||
|
||||
die_on_empty_pop:
|
||||
Raise an exception if an empty stack is popped. False by default.
|
||||
|
||||
skip_stack_on_overflow:
|
||||
Do not apply changes to exit stacks after a symbolic overflow occurrs
|
||||
in their blocks. True by default.
|
||||
|
||||
reinit_stacks:
|
||||
Reinitialise all blocks' exit stacks to be empty. True by default.
|
||||
|
||||
hook_up_stack_vars:
|
||||
After completing the analysis, propagate entry stack values into blocks.
|
||||
True by default.
|
||||
|
||||
hook_up_jumps:
|
||||
Connect any new edges that can be inferred after performing the analysis.
|
||||
True by default.
|
||||
|
||||
mutate_jumps:
|
||||
JUMPIs with known conditions become JUMPs (or are deleted).
|
||||
For example, a JUMPI with a known-true condition becomes a JUMP.
|
||||
False by default.
|
||||
|
||||
generate_throws:
|
||||
JUMP and JUMPI instructions with invalid destinations become THROW and
|
||||
THROWIs. False by default.
|
||||
|
||||
final_mutate_jumps:
|
||||
Mutate jumps in the final analysis phase. False by default.
|
||||
|
||||
final_generate_throws:
|
||||
generate throws in the final analysis phase. True by default.
|
||||
|
||||
mutate_blockwise:
|
||||
Hook up stack vars and/or hook up jumps after each block rather than after
|
||||
the whole analysis is complete. True by default.
|
||||
|
||||
clamp_large_stacks:
|
||||
If stacks start growing deeper without more of the program's control flow
|
||||
graph being inferred for sufficiently many iterations, we can freeze the
|
||||
maximum stack size in order to save computation.
|
||||
True by default.
|
||||
|
||||
clamp_stack_minimum:
|
||||
Stack sizes will not be clamped smaller than this value. Default value is 20.
|
||||
|
||||
widen_variables:
|
||||
If any computed variable's number of possible values exceeds a given
|
||||
threshold, widen its value to Top. True by default.
|
||||
|
||||
widen_threshold:
|
||||
Whenever the result of an operation may take more than this number of
|
||||
possible values, then widen the result variable's value to the Top lattice
|
||||
value (treat its value as unconstrained).
|
||||
Default value is 10.
|
||||
|
||||
set_valued_ops:
|
||||
If True, apply arithmetic operations to variables with multiple values;
|
||||
otherwise, only apply them to variables whose value takes only one
|
||||
value.
|
||||
Disable to gain speed at the cost of precision. True by default.
|
||||
|
||||
analytics:
|
||||
If True, dataflow analysis will return a dict of information about
|
||||
the contract, otherwise return an empty dict.
|
||||
Disabling this might yield a slight speed improvement. False by default.
|
||||
|
||||
extract_functions:
|
||||
If True, attempt to extract solidity functions.
|
||||
|
||||
mark_functions:
|
||||
If true, tag block names with the function(s) they belong to.
|
||||
|
||||
strict:
|
||||
If true, then unrecognised opcodes and invalid disassembly
|
||||
will not be skipped, but will result in an error.
|
||||
|
||||
Note: If we have already reached complete information about our stack CFG
|
||||
structure and stack states, we can use die_on_empty_pop and reinit_stacks
|
||||
to discover places where empty stack exceptions will be thrown.
|
||||
"""
|
||||
|
||||
# The settings - these are None until initialised by import_config
|
||||
max_iterations = None
|
||||
bailout_seconds = None
|
||||
remove_unreachable = None
|
||||
merge_unreachable = None
|
||||
die_on_empty_pop = None
|
||||
skip_stack_on_overflow = None
|
||||
reinit_stacks = None
|
||||
hook_up_stack_vars = None
|
||||
hook_up_jumps = None
|
||||
mutate_jumps = None
|
||||
generate_throws = None
|
||||
final_mutate_jumps = None
|
||||
final_generate_throws = None
|
||||
mutate_blockwise = None
|
||||
clamp_large_stacks = None
|
||||
clamp_stack_minimum = None
|
||||
widen_variables = None
|
||||
widen_threshold = None
|
||||
set_valued_ops = None
|
||||
analytics = None
|
||||
extract_functions = None
|
||||
mark_functions = None
|
||||
strict = None
|
||||
|
||||
# A reference to this module for retrieving its members; import sys like this so that it does not appear in _names_.
|
||||
_module_ = __import__("sys").modules[__name__]
|
||||
|
||||
# The names of all the settings defined above.
|
||||
_names_ = [s for s in dir(_module_) if not (s.startswith("_"))]
|
||||
|
||||
# Set up the types of the various settings, so they can be converted
|
||||
# correctly when being read from config.
|
||||
_types_ = {n: ("int" if n in ["max_iterations", "bailout_seconds",
|
||||
"clamp_stack_minimum", "widen_threshold"]
|
||||
else "bool") for n in _names_}
|
||||
|
||||
# A stack for saving and restoring setting configurations.
|
||||
_stack_ = []
|
||||
|
||||
# Imports and definitions appearing below the definition of _names_
|
||||
# do not appear in that list, by design. Don't move them up.
|
||||
import logging
|
||||
import sys
|
||||
from os.path import dirname, normpath, join
|
||||
|
||||
_dir_ = dirname(__file__)
|
||||
|
||||
# Default settings are stored here.
|
||||
_DEFAULT_LOC_ = normpath(join(_dir_, "../src/default_config.ini"))
|
||||
|
||||
# User settings are located here, and will override default settings.
|
||||
_CONFIG_LOC_ = normpath(join(_dir_, "../bin/config.ini"))
|
||||
|
||||
|
||||
def _get_dict_():
|
||||
"""
|
||||
Return the current module's dictionary of members so the settings can be
|
||||
dynamically accessed by name.
|
||||
"""
|
||||
return _module_.__dict__
|
||||
|
||||
|
||||
def save():
|
||||
"""Push the current setting configuration to the stack."""
|
||||
sd = _get_dict_()
|
||||
_stack_.append({n: sd[n] for n in _names_})
|
||||
|
||||
|
||||
def restore():
|
||||
"""Restore the setting configuration from the top of the stack."""
|
||||
_get_dict_().update(_stack_.pop())
|
||||
|
||||
|
||||
def set_from_string(setting_name: str, value: str):
|
||||
"""
|
||||
Assign to the named setting the given value, first converting that value
|
||||
to the type appropriate for that setting.
|
||||
Names and values are not case sensitive.
|
||||
"""
|
||||
name = setting_name.lower()
|
||||
val = value.lower()
|
||||
|
||||
if name not in _names_:
|
||||
logging.error('Unrecognised setting "%s".', setting_name)
|
||||
sys.exit(1)
|
||||
|
||||
if _types_[name] == "int":
|
||||
_get_dict_()[name] = int(val)
|
||||
elif _types_[name] == "bool":
|
||||
if val in {"1", "yes", "true", "on"}:
|
||||
_get_dict_()[name] = True
|
||||
elif val in {"0", "no", "false", "off"}:
|
||||
_get_dict_()[name] = False
|
||||
else:
|
||||
logging.error('Cannot interpret value "%s" as boolean for setting "%s"',
|
||||
value, setting_name)
|
||||
sys.exit(1)
|
||||
else:
|
||||
logging.error('Unknown type "%s" for setting "%s".', setting_name)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def import_config(filepath: str = _CONFIG_LOC_):
|
||||
"""
|
||||
Import settings from the given configuration file.
|
||||
This should be called before running the decompiler.
|
||||
"""
|
||||
import configparser
|
||||
config = configparser.ConfigParser()
|
||||
with open(_DEFAULT_LOC_) as default:
|
||||
config.read_file(default)
|
||||
config.read(filepath)
|
||||
for name in _names_:
|
||||
set_from_string(name, config.get("settings", name))
|
||||
+1015
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,830 @@
|
||||
# BSD 3-Clause License
|
||||
#
|
||||
# Copyright (c) 2020, The Ohio State Univerisity. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""tac_efg.py: generate a Intermediate Representation based execution flow graph."""
|
||||
|
||||
"""这份脚本实现了从EFG convert to IR的过程
|
||||
该代码由以下几个类组成:
|
||||
TACGraph:基于TAC(Three-Address Code,三地址码)的执行流图(Execution Flow Graph,EFG),用于分析以太坊智能合约的字节码(Bytecode)
|
||||
TACBasicBlock:基于TAC的基本块(Basic Block),用于分析以太坊智能合约的字节码
|
||||
TACOp,TACOpConst:基于TAC的操作码(Operation Code,OpCode),用于分析以太坊智能合约的字节码。
|
||||
TACOp代表了三地址代码中的一条操作,就是三地址代码中的一条指令,它执行某种计算或操作
|
||||
TACAssignOp代表一条分配操作,将一个值存储到指定的内存位置或寄存器中
|
||||
TACArg包含了操作的参数信息
|
||||
TACLocRef包含了一个基本块内特定指令的程序计数器引用
|
||||
Destackifier将EVMBasicBlocks转换为TACBasicBlocks
|
||||
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import typing as t
|
||||
import src.cfg as cfg
|
||||
import src.evm_efg as evm_efg
|
||||
import src.memtypes as mem
|
||||
import src.opcodes as opcodes
|
||||
import src.patterns as patterns
|
||||
import src.settings as settings
|
||||
from src.lattice import SubsetLatticeElement as ssle
|
||||
|
||||
POSTDOM_END_NODE = "END"
|
||||
"""The name of the synthetic end node added for post-dominator calculations."""
|
||||
UNRES_DEST = "?"
|
||||
"""The name of the unresolved jump destination auxiliary node."""
|
||||
|
||||
def remove_0x(temptstr):
|
||||
if temptstr.startswith("0x"):
|
||||
return temptstr[2:]
|
||||
return temptstr
|
||||
|
||||
|
||||
class TACGraph(cfg.ControlFlowGraph):
|
||||
"""
|
||||
A execution flow graph holding Three-Address Code blocks and the edges between them.
|
||||
"""
|
||||
|
||||
def __init__(self, evm_blocks: t.Iterable[evm_efg.EVMBasicBlock]):
|
||||
"""
|
||||
Construct a TAC execution flow graph from a given sequence of EVM blocks.
|
||||
Immediately after conversion, constants will be propagated and folded
|
||||
through arithmetic operations, and EFG edges will be connected up, wherever
|
||||
they can be inferred.
|
||||
|
||||
Args:
|
||||
evm_blocks: an iterable of EVMBasicBlocks to convert into TAC form.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
# Convert the input EVM blocks to TAC blocks.
|
||||
destack = Destackifier()
|
||||
|
||||
# Create a whole global stack to store all the tempt_stacks to deal with multiple smart contracts
|
||||
stacks = []
|
||||
|
||||
for i, b in enumerate(evm_blocks):
|
||||
tac_block = destack.convert_block(b, stacks)
|
||||
self.blocks.append(tac_block)
|
||||
|
||||
"""The sequence of TACBasicBlocks contained in this graph."""
|
||||
for b in self.blocks:
|
||||
b.cfg = self
|
||||
|
||||
self.root = next((b for b in self.blocks if b.entry == 0), None)
|
||||
"""
|
||||
The root block of this EFG.
|
||||
The entry point will always be at index 0, if it exists.
|
||||
"""
|
||||
|
||||
self.split_node_succs = {}
|
||||
"""
|
||||
A mapping from addresses to addresses storing all successors of a
|
||||
block at the time it was split. At merge time these edges can be restored.
|
||||
"""
|
||||
|
||||
self.function_extractor = None
|
||||
"""
|
||||
A FunctionExtractor object, which encapsulates solidity functions and extracts logic.
|
||||
"""
|
||||
|
||||
# Add some fields to store stack and memory values
|
||||
# This function is not used in our work
|
||||
self.stack_values = defaultdict(dict)
|
||||
self.memory = bytearray()
|
||||
|
||||
# Propagate constants and add EFG edges.
|
||||
# Assgin the opcode related value to memory and stack
|
||||
self.apply_operations()
|
||||
|
||||
# Connect all the edges
|
||||
for i, b in enumerate(self.blocks):
|
||||
b.index = i
|
||||
|
||||
self.connectEFGNode()
|
||||
|
||||
|
||||
# Accept extracted argument from the opcode
|
||||
@classmethod
|
||||
def from_opcode(cls, opcode: t.Iterable) -> 'TACGraph':
|
||||
"""
|
||||
Construct and return a TACGraph from the given geth opcode.
|
||||
|
||||
Args:
|
||||
opcode: a sequence of EVM opcode, either in a hexadecimal string format or a byte array.
|
||||
"""
|
||||
original_opcodes = []
|
||||
for l in opcode:
|
||||
if len(l.strip()) > 0:
|
||||
# One example of the opcode line: 0; PUSH1; 0x64, which represents <PC; OPCODE NAME; ARGS>
|
||||
args = l.strip().split(";")
|
||||
if args[2] == "":
|
||||
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), None, None))
|
||||
else:
|
||||
if "," in args[2]:
|
||||
args_extras = args[2].strip().split(",")
|
||||
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]),
|
||||
int(args_extras[0]), int(args_extras[1])))
|
||||
else:
|
||||
if opcodes.opcode_by_name(args[1]).name == opcodes.SELFDESTRUCT.name:
|
||||
addr = int(args[2], 0)
|
||||
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), addr, None))
|
||||
else:
|
||||
original_opcodes.append(evm_efg.EVMOp(int(args[0]), opcodes.opcode_by_name(args[1]), int(args[2]), None))
|
||||
|
||||
# Obtain evm opcode based EFG firstly
|
||||
basic_blocks = evm_efg.blocks_from_ops(original_opcodes)
|
||||
return cls(basic_blocks)
|
||||
|
||||
@property
|
||||
def tac_ops(self):
|
||||
for block in self.blocks:
|
||||
for op in block.tac_ops:
|
||||
yield op
|
||||
|
||||
@property
|
||||
def last_op(self):
|
||||
return max((b.last_op for b in self.blocks),
|
||||
key=lambda o: o.pc)
|
||||
|
||||
def apply_operations(self, use_sets=False) -> None:
|
||||
"""
|
||||
Propagate and fold constants through the arithmetic TAC instructions
|
||||
in this EFG.
|
||||
|
||||
If use_sets is True, folding will also be done on Variables that
|
||||
possess multiple possible values, performing operations in all possible
|
||||
combinations of values.
|
||||
"""
|
||||
for block in self.blocks:
|
||||
# Add stack and memory for the whole TAC-based EFG and store the related value from geth to memory, stack
|
||||
block.apply_operations(self.stack_values, self.memory, use_sets)
|
||||
|
||||
def connectEFGNode(self):
|
||||
if len(list(self.blocks)) > 1:
|
||||
for i, block in enumerate(self.blocks):
|
||||
if i == 0:
|
||||
block.preds = []
|
||||
block.succs = [self.blocks[i + 1]]
|
||||
elif i == len(self.blocks)-1:
|
||||
block.preds = [self.blocks[i - 1]]
|
||||
block.succs = []
|
||||
else:
|
||||
block.preds = [self.blocks[i - 1]]
|
||||
block.succs = [self.blocks[i + 1]]
|
||||
|
||||
def extract_functions(self):
|
||||
"""
|
||||
Attempt to extract solidity functions from this contract.
|
||||
Call this after having already called prop_vars_between_blocks() on efg.
|
||||
"""
|
||||
import src.function as function
|
||||
fe = function.FunctionExtractor(self)
|
||||
fe.extract()
|
||||
self.function_extractor = fe
|
||||
|
||||
|
||||
class TACBasicBlock(evm_efg.EVMBasicBlock):
|
||||
"""
|
||||
A basic block containing both three-address code, and its
|
||||
equivalent EVM code, along with information about the transformation
|
||||
applied to the stack as a consequence of its execution.
|
||||
"""
|
||||
def __init__(self, entry_pc: int, exit_pc: int,
|
||||
tac_ops: t.List['TACOp'],
|
||||
evm_ops: t.List[evm_efg.EVMOp],
|
||||
delta_stack: mem.VariableStack,
|
||||
cfg=None, index=None):
|
||||
"""
|
||||
Args:
|
||||
entry_pc: The pc of the first byte in the source EVM block
|
||||
exit_pc: The pc of the last byte in the source EVM block
|
||||
tac_ops: A sequence of TACOps whose execution is equivalent to the source
|
||||
EVM code.
|
||||
evm_ops: the source EVM code.
|
||||
delta_stack: A stack describing the change in the stack state as a result
|
||||
of running this block.
|
||||
This stack contains the new items inhabiting the top of
|
||||
stack after execution, along with the number of items
|
||||
removed from the stack.
|
||||
cfg: The TACGraph to which this block belongs.
|
||||
|
||||
Entry and exit variables should span the entire range of values enclosed
|
||||
in this block, taking care to note that the exit address may not be an
|
||||
instruction, but an argument of a PUSH.
|
||||
The range of pc values spanned by all blocks in a CFG should be a
|
||||
continuous range from 0 to the maximum value with no gaps between blocks.
|
||||
|
||||
If the input stack state is known, obtain the exit stack state by
|
||||
popping off delta_stack.empty_pops items and add the delta_stack items
|
||||
to the top.
|
||||
"""
|
||||
|
||||
super().__init__(entry_pc, exit_pc, evm_ops)
|
||||
|
||||
self.tac_ops = tac_ops
|
||||
"""A sequence of TACOps whose execution is equivalent to the source EVM
|
||||
code"""
|
||||
|
||||
self.delta_stack = delta_stack
|
||||
"""
|
||||
A stack describing the stack state changes caused by running this block.
|
||||
MetaVariables named Sn symbolically denote the variable that was n places
|
||||
from the top of the stack at entry to this block.
|
||||
"""
|
||||
|
||||
self.entry_stack = mem.VariableStack()
|
||||
"""Holds the complete stack state before execution of the block."""
|
||||
|
||||
self.exit_stack = mem.VariableStack()
|
||||
"""Holds the complete stack state after execution of the block."""
|
||||
|
||||
self.symbolic_overflow = False
|
||||
"""
|
||||
Indicates whether a symbolic stack overflow has occurred in dataflow
|
||||
analysis of this block.
|
||||
"""
|
||||
|
||||
self.cfg = cfg
|
||||
"""The TACGraph to which this block belongs."""
|
||||
|
||||
self.index = index
|
||||
|
||||
def __str__(self):
|
||||
super_str = super().__str__()
|
||||
op_seq = "\n".join(str(op) for op in self.tac_ops)
|
||||
entry_stack = "Entry stack: {}".format(str(self.entry_stack))
|
||||
stack_pops = "Stack pops: {}".format(self.delta_stack.empty_pops)
|
||||
stack_adds = "Stack additions: {}".format(str(self.delta_stack))
|
||||
exit_stack = "Exit stack: {}".format(str(self.exit_stack))
|
||||
return "\n".join([super_str, self._STR_SEP, op_seq, self._STR_SEP,
|
||||
entry_stack, stack_pops, stack_adds, exit_stack])
|
||||
|
||||
def accept(self, visitor: patterns.Visitor) -> None:
|
||||
"""
|
||||
Accepts a visitor and visits itself and all TACOps in the block.
|
||||
|
||||
Args:
|
||||
visitor: an instance of :obj:`patterns.Visitor` to accept.
|
||||
"""
|
||||
super().accept(visitor)
|
||||
|
||||
if visitor.can_visit(TACOp) or visitor.can_visit(TACAssignOp):
|
||||
for tac_op in self.tac_ops:
|
||||
visitor.visit(tac_op)
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
"""Return a copy of this block."""
|
||||
|
||||
new_block = TACBasicBlock(self.entry, self.exit,
|
||||
copy.deepcopy(self.tac_ops, memodict),
|
||||
[copy.copy(op) for op in self.evm_ops],
|
||||
copy.deepcopy(self.delta_stack, memodict))
|
||||
|
||||
new_block.fallthrough = self.fallthrough
|
||||
new_block.has_unresolved_jump = self.has_unresolved_jump
|
||||
new_block.symbolic_overflow = self.symbolic_overflow
|
||||
new_block.entry_stack = copy.deepcopy(self.entry_stack, memodict)
|
||||
new_block.exit_stack = copy.deepcopy(self.exit_stack, memodict)
|
||||
new_block.preds = copy.copy(self.preds)
|
||||
new_block.succs = copy.copy(self.succs)
|
||||
new_block.ident_suffix = self.ident_suffix
|
||||
new_block.cfg = self.cfg
|
||||
|
||||
new_block.reset_block_refs()
|
||||
|
||||
return new_block
|
||||
|
||||
@property
|
||||
def last_op(self) -> 'TACOp':
|
||||
"""Return the last TAC operation in this block if it exists."""
|
||||
if len(self.tac_ops):
|
||||
return self.tac_ops[-1]
|
||||
return None
|
||||
|
||||
@last_op.setter
|
||||
def last_op(self, op):
|
||||
"""
|
||||
Set the last TAC operation in this block, if there is one.
|
||||
Append if one doesn't exist.
|
||||
"""
|
||||
if len(self.tac_ops):
|
||||
self.tac_ops[-1] = op
|
||||
else:
|
||||
self.tac_ops.append(op)
|
||||
|
||||
def reset_block_refs(self) -> None:
|
||||
"""Update all operations and new def sites to refer to this block."""
|
||||
|
||||
for op in self.evm_ops:
|
||||
op.block = self
|
||||
for op in self.tac_ops:
|
||||
op.block = self
|
||||
if isinstance(op, TACAssignOp) and isinstance(op.lhs, mem.Variable):
|
||||
for site in op.lhs.def_sites:
|
||||
site.block = self
|
||||
|
||||
def apply_operations(self, stack_values: defaultdict, memory: bytearray, use_sets=False) -> None:
|
||||
"""
|
||||
Propagate and fold constants through the arithmetic TAC instructions in this block.
|
||||
"""
|
||||
for op in self.tac_ops:
|
||||
if op.opcode == opcodes.CONST:
|
||||
op.lhs.values = op.args[0].value.values
|
||||
|
||||
# Special cases: they both belong to three_store_two.
|
||||
elif op.opcode == opcodes.CALLDATACOPY or op.opcode == opcodes.CODECOPY \
|
||||
or op.opcode == opcodes.RETURNDATACOPY:
|
||||
arg0 = remove_0x(str(op.args[0]))
|
||||
destoffset = int(arg0, 16)
|
||||
arg2 = remove_0x(str(op.args[2]))
|
||||
length = int(arg2, 16)
|
||||
value = op.value
|
||||
memory[destoffset: destoffset + length] = value.to_bytes(length, byteorder='big')
|
||||
elif op.opcode == opcodes.EXTCODECOPY:
|
||||
arg1 = remove_0x(str(op.args[1]))
|
||||
destoffset = int(arg1, 16)
|
||||
arg3 = remove_0x(str(op.args[3]))
|
||||
length = int(arg3, 16)
|
||||
value = op.value
|
||||
memory[destoffset: destoffset + length] = value.to_bytes(length, byteorder='big')
|
||||
|
||||
# Special cases: cases for kind one and two, but those opcodes are not in three_store
|
||||
# Those opcodes have already had their value assigned to the lhs in the __handal_evm_op
|
||||
elif op.opcode.is_kind_one() or op.opcode.is_kind_two():
|
||||
continue
|
||||
|
||||
# Special cases: SLOAD and MLOAD get their value from the geth, and these values have been assigned
|
||||
elif op.opcode == opcodes.MLOAD or op.opcode == opcodes.SLOAD:
|
||||
continue
|
||||
|
||||
# Special cases: SSTORE and MSTORE. Store variable values to the related storage and memory
|
||||
elif op.opcode == opcodes.SSTORE:
|
||||
var_name = "S[{}]".format(op.args[0])
|
||||
var_value = op.args[1].value.values
|
||||
stack_values[var_name] = var_value
|
||||
elif op.opcode == opcodes.MSTORE:
|
||||
arg0 = remove_0x(str(op.args[0]))
|
||||
offset = int(arg0, 16)
|
||||
arg1 = remove_0x(str(op.args[1]))
|
||||
value = int(arg1, 16)
|
||||
memory[offset: offset + 32] = value.to_bytes(32, byteorder='big')
|
||||
elif op.opcode == opcodes.MSTORE8:
|
||||
arg0 = remove_0x(str(op.args[0]))
|
||||
offset = int(arg0, 16)
|
||||
arg1 = remove_0x(str(op.args[1]))
|
||||
value = int(arg1, 16)
|
||||
memory[offset: offset + 1] = value.to_bytes(8, byteorder='big')
|
||||
|
||||
elif op.opcode.is_arithmetic():
|
||||
if op.constant_args() or (op.constrained_args() and use_sets):
|
||||
rhs = [arg.value for arg in op.args]
|
||||
op.lhs.values = mem.Variable.arith_op(op.opcode.name, rhs).values
|
||||
elif not op.lhs.is_unconstrained:
|
||||
op.lhs.widen_to_top()
|
||||
|
||||
|
||||
class TACOp(patterns.Visitable):
|
||||
"""
|
||||
A Three-Address Code operation.
|
||||
Each operation consists of an opcode object defining its function,
|
||||
a list of argument variables, and the unique program counter address
|
||||
of the EVM instruction it was derived from.
|
||||
"""
|
||||
|
||||
def __init__(self, opcode: opcodes.OpCode, args: t.List['TACArg'],
|
||||
pc: int, block=None, value: int = None,
|
||||
loc: int = None, call_depth: int = None, call_number: int = None):
|
||||
"""
|
||||
Args:
|
||||
opcode: the operation being performed.
|
||||
args: Variables that are operated upon.
|
||||
pc: the program counter at the corresponding instruction in the original bytecode.
|
||||
block: the block this operation belongs to. Defaults to None.
|
||||
value: The value generated by Geth. Only kind three that stores two opcodes needs this field.
|
||||
loc: the position of the opcode
|
||||
call_depth: the depth of the called smart contracts
|
||||
call_number: the number of the called smart contracts so far
|
||||
"""
|
||||
self.opcode = opcode
|
||||
self.args = args
|
||||
self.pc = pc
|
||||
self.block = block
|
||||
self.value = value
|
||||
self.loc = loc
|
||||
self.call_depth = call_depth
|
||||
self.call_number = call_number
|
||||
|
||||
def __str__(self):
|
||||
if self.opcode in [opcodes.MSTORE, opcodes.MSTORE8, opcodes.SSTORE]:
|
||||
if self.opcode == opcodes.MSTORE:
|
||||
lhs = "M[{}]".format(self.args[0])
|
||||
elif self.opcode == opcodes.MSTORE8:
|
||||
lhs = "M8[{}]".format(self.args[0])
|
||||
else:
|
||||
lhs = "S[{}]".format(self.args[0])
|
||||
|
||||
return "{}: {} = {}".format(hex(self.pc), lhs,
|
||||
" ".join([str(arg) for arg in self.args[1:]]))
|
||||
return "{}: {} {}".format(hex(self.pc), self.opcode,
|
||||
" ".join([str(arg) for arg in self.args]))
|
||||
|
||||
def __repr__(self):
|
||||
return "<{0} object {1}, {2}>".format(
|
||||
self.__class__.__name__,
|
||||
hex(id(self)),
|
||||
self.__str__()
|
||||
)
|
||||
|
||||
def constant_args(self) -> bool:
|
||||
"""True iff each of this operations arguments is a constant value."""
|
||||
return all([arg.value.is_const for arg in self.args])
|
||||
|
||||
def constrained_args(self) -> bool:
|
||||
"""True iff none of this operations arguments is value-unconstrained."""
|
||||
return all([not arg.value.is_unconstrained for arg in self.args])
|
||||
|
||||
@staticmethod
|
||||
def has_lhs() -> bool:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def convert_jump_to_throw(cls, op: 'TACOp') -> 'TACOp':
|
||||
"""
|
||||
Given a jump, convert it to a throw, preserving the condition var if JUMPI.
|
||||
Otherwise, return the given operation unchanged.
|
||||
"""
|
||||
if op.opcode not in [opcodes.JUMP, opcodes.JUMPI]:
|
||||
return op
|
||||
elif op.opcode == opcodes.JUMP:
|
||||
return cls(opcodes.THROW, [], op.pc, op.block)
|
||||
elif op.opcode == opcodes.JUMPI:
|
||||
return cls(opcodes.THROWI, [op.args[1]], op.pc, op.block)
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
new_op = type(self)(self.opcode,
|
||||
copy.deepcopy(self.args, memodict),
|
||||
self.pc,
|
||||
self.block)
|
||||
return new_op
|
||||
|
||||
|
||||
class TACAssignOp(TACOp):
|
||||
"""
|
||||
A TAC operation that additionally takes a variable to which
|
||||
this operation's result is implicitly bound.
|
||||
"""
|
||||
def __init__(self, lhs: mem.Variable, opcode: opcodes.OpCode,
|
||||
args: t.List['TACArg'], pc: int, block=None,
|
||||
print_name: bool = True, value_extra: int = None,
|
||||
loc: int = None, call_depth: int = None, call_number: int = None):
|
||||
"""
|
||||
Args:
|
||||
lhs: The Variable that will receive the result of this operation.
|
||||
print_name: Some operations (e.g. CONST) don't need to print their
|
||||
name in order to be readable.
|
||||
value_extra: store the value_extra for CALL CALLCODE STATICCALL DELEGATECALL
|
||||
loc: the position of the opcode
|
||||
call_depth: the depth of the called smart contracts
|
||||
call_number: the number of the called smart contracts so far
|
||||
"""
|
||||
super().__init__(opcode, args, pc, block)
|
||||
self.lhs = lhs
|
||||
self.print_name = print_name
|
||||
self.value_extra = value_extra
|
||||
self.loc = loc
|
||||
self.call_depth = call_depth
|
||||
self.call_number = call_number
|
||||
|
||||
# Special case TAC expression
|
||||
# For example V4 = CALLVALUE to V4 = value content
|
||||
def __str__(self):
|
||||
if self.opcode in [opcodes.SLOAD, opcodes.MLOAD]:
|
||||
if self.opcode == opcodes.SLOAD:
|
||||
rhs = "S[{}]".format(self.args[0])
|
||||
else:
|
||||
rhs = "M[{}]".format(self.args[0])
|
||||
|
||||
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, rhs)
|
||||
elif self.opcode.is_kind_one() or self.opcode.is_kind_two():
|
||||
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, self.lhs.values)
|
||||
|
||||
arglist = ([str(self.opcode)] if self.print_name else []) \
|
||||
+ [str(arg) for arg in self.args]
|
||||
return "{}: {} = {}".format(hex(self.pc), self.lhs.identifier, " ".join(arglist))
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
"""
|
||||
Return a copy of this TACAssignOp, deep copying the args and vars,
|
||||
but leaving block references unchanged.
|
||||
"""
|
||||
new_op = type(self)(copy.deepcopy(self.lhs, memodict),
|
||||
self.opcode,
|
||||
copy.deepcopy(self.args, memodict),
|
||||
self.pc,
|
||||
self.block,
|
||||
self.print_name)
|
||||
return new_op
|
||||
|
||||
@staticmethod
|
||||
def has_lhs() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class TACArg:
|
||||
"""
|
||||
Contains information held in an argument to a TACOp.
|
||||
In particular, a TACArg may hold both the current value of an argument,
|
||||
if it exists; along with the entry stack position it came from, if it did.
|
||||
This allows updated/refined stack data to be propagated into the body
|
||||
of a TACBasicBlock.
|
||||
"""
|
||||
|
||||
def __init__(self, var: mem.Variable = None, stack_var: mem.MetaVariable = None):
|
||||
self.var = var
|
||||
"""The actual variable this arg contains."""
|
||||
self.stack_var = stack_var
|
||||
"""The stack position this variable came from."""
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Return this arg's value if it has one, otherwise return its stack variable.
|
||||
"""
|
||||
if self.var is None:
|
||||
if self.stack_var is None:
|
||||
raise ValueError("TAC Argument has no value.")
|
||||
else:
|
||||
return self.stack_var
|
||||
else:
|
||||
return self.var
|
||||
|
||||
@classmethod
|
||||
def from_var(cls, var: mem.Variable):
|
||||
if isinstance(var, mem.MetaVariable):
|
||||
return cls(stack_var=var)
|
||||
return cls(var=var)
|
||||
|
||||
|
||||
class TACLocRef:
|
||||
"""Contains a reference to a program counter within a particular block."""
|
||||
|
||||
def __init__(self, block, pc):
|
||||
self.block = block
|
||||
"""The block that contains the referenced instruction."""
|
||||
self.pc = pc
|
||||
"""The program counter of the referenced instruction."""
|
||||
|
||||
def __deepcopy__(self, memodict={}):
|
||||
return type(self)(self.block, self.pc)
|
||||
|
||||
def __str__(self):
|
||||
return "{}.{}".format(self.block.ident(), hex(self.pc))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.block == other.block and self.pc == other.pc
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.block) ^ hash(self.pc)
|
||||
|
||||
def get_instruction(self):
|
||||
"""Return the TACOp referred to by this TACLocRef, if it exists."""
|
||||
for i in self.block.tac_ops:
|
||||
if i.pc == self.pc:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
class Destackifier:
|
||||
"""Converts EVMBasicBlocks into corresponding TACBasicBlocks.
|
||||
|
||||
Most instructions get mapped over directly, except:
|
||||
POP: generates no TAC op, but pops the symbolic stack;
|
||||
PUSH: generates a CONST TAC assignment operation;
|
||||
DUP, SWAP: these simply permute the symbolic stack, generate no ops;
|
||||
LOG0 ... LOG4: all translated to a generic LOG instruction
|
||||
|
||||
Additionally, there is a NOP TAC instruction that does nothing, to represent
|
||||
a block containing EVM instructions with no corresponding TAC code.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# A sequence of three-address operations
|
||||
self.ops = []
|
||||
|
||||
# The symbolic variable stack we'll be operating on.
|
||||
self.stack = mem.VariableStack()
|
||||
|
||||
# Entry address of the current block being converted
|
||||
self.block_entry = None
|
||||
|
||||
# The number of TAC variables we've assigned,
|
||||
# in order to produce unique identifiers. Typically the same as
|
||||
# the number of items pushed to the stack.
|
||||
# We increment it so that variable names will be globally unique.
|
||||
self.stack_vars = 0
|
||||
|
||||
def __fresh_init(self, evm_block: evm_efg.EVMBasicBlock) -> None:
|
||||
"""Reinitialise all structures in preparation for converting a block."""
|
||||
self.ops = []
|
||||
self.stack = mem.VariableStack()
|
||||
self.block_entry = evm_block.evm_ops[0].pc \
|
||||
if len(evm_block.evm_ops) > 0 else None
|
||||
|
||||
def __new_var(self) -> mem.Variable:
|
||||
"""Construct and return a new variable with the next free identifier."""
|
||||
|
||||
# Generate the new variable, numbering it by the implicit stack location
|
||||
# it came from.
|
||||
var = mem.Variable.top(name="V{}".format(self.stack_vars),
|
||||
def_sites=ssle([TACLocRef(None, self.block_entry)]))
|
||||
self.stack_vars += 1
|
||||
return var
|
||||
|
||||
|
||||
# Add the last block's stack into the following one
|
||||
def convert_block(self, evm_block: evm_efg.EVMBasicBlock, stacks: [mem.VariableStack]) -> (TACBasicBlock):
|
||||
"""
|
||||
Given a EVMBasicBlock, produce an equivalent three-address code sequence and return the resulting TACBasicBlock.
|
||||
"""
|
||||
# Step1: How to use the stack
|
||||
if len(evm_block.evm_ops) > 0:
|
||||
first_opcode = evm_block.evm_ops[0]
|
||||
if first_opcode.pc == 0:
|
||||
pre_stack = mem.VariableStack(call_depth=first_opcode.call_depth)
|
||||
elif first_opcode.opcode.is_kind_four() or first_opcode.opcode.is_kind_five():
|
||||
pre_stack = stacks.pop()
|
||||
if first_opcode.call_depth != pre_stack.call_depth:
|
||||
pre_stack = stacks.pop()
|
||||
|
||||
self.__fresh_init(evm_block)
|
||||
|
||||
self.stack = pre_stack
|
||||
for op in evm_block.evm_ops:
|
||||
self.__handle_evm_op(op)
|
||||
|
||||
entry = evm_block.evm_ops[0].pc if len(evm_block.evm_ops) > 0 else None
|
||||
exit = evm_block.evm_ops[-1].pc + evm_block.evm_ops[-1].opcode.push_len() \
|
||||
if len(evm_block.evm_ops) > 0 else None
|
||||
|
||||
# If the block is empty, append a NOP before continuing.
|
||||
if len(self.ops) == 0:
|
||||
self.ops.append(TACOp(opcodes.NOP, [], entry))
|
||||
|
||||
new_block = TACBasicBlock(entry, exit, self.ops, evm_block.evm_ops,
|
||||
self.stack)
|
||||
|
||||
# Link up new ops and def sites to the block that contains them.
|
||||
new_block.reset_block_refs()
|
||||
|
||||
# Step2: How to add the stack
|
||||
if len(evm_block.evm_ops) > 0:
|
||||
first_opcode = evm_block.evm_ops[0]
|
||||
last_opcode = evm_block.evm_ops[len(evm_block.evm_ops) - 1]
|
||||
if first_opcode.pc == 0 and not last_opcode.opcode.possibly_halts():
|
||||
stacks.append(self.stack)
|
||||
if (first_opcode.opcode.is_kind_four() or first_opcode.opcode.is_kind_five())\
|
||||
and not last_opcode.opcode.possibly_halts():
|
||||
stacks.append(self.stack)
|
||||
|
||||
return new_block
|
||||
|
||||
def __handle_evm_op(self, op: evm_efg.EVMOp) -> None:
|
||||
"""
|
||||
Produce from an EVM line its corresponding TAC instruction, if there is one,
|
||||
appending it to the current TAC sequence.
|
||||
"""
|
||||
if op.opcode.is_swap():
|
||||
self.stack.swap(op.opcode.pop)
|
||||
elif op.opcode.is_dup():
|
||||
self.stack.dup(op.opcode.pop)
|
||||
elif op.opcode == opcodes.POP:
|
||||
self.stack.pop()
|
||||
else:
|
||||
# When generating TAC operation from evm opcode, making use of value and value_extra generated from geth
|
||||
self.__gen_instruction(op)
|
||||
|
||||
# Use values from geth
|
||||
def __gen_instruction(self, op: evm_efg.EVMOp) -> None:
|
||||
"""
|
||||
Given a line, generate its corresponding TAC operation,
|
||||
append it to the op sequence, and push any generated
|
||||
variables to the stack.
|
||||
"""
|
||||
inst = None
|
||||
new_var = self.__new_var() if op.opcode.push == 1 else None
|
||||
|
||||
# Set this variable's def site
|
||||
if new_var is not None:
|
||||
for site in new_var.def_sites:
|
||||
site.pc = op.pc
|
||||
|
||||
# Generate the appropriate TAC operation.
|
||||
# Special cases first, followed by the fallback to generic instructions.
|
||||
# Although the opcode is PUSH, vandal still marks it as CONST to do arithemetic operations.
|
||||
if op.opcode.is_push():
|
||||
args = [TACArg(var=mem.Variable(values=[op.value], name="C"))]
|
||||
inst = TACAssignOp(new_var, opcodes.CONST, args, op.pc, print_name=False)
|
||||
elif op.opcode.is_missing():
|
||||
args = [TACArg(var=mem.Variable(values=[op.value], name="C"))]
|
||||
inst = TACOp(op.opcode, args, op.pc)
|
||||
elif op.opcode.is_log():
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACOp(opcodes.LOG, args, op.pc)
|
||||
elif op.opcode == opcodes.MSTORE:
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.MSTORE.pop)]
|
||||
inst = TACOp(op.opcode, args, op.pc)
|
||||
elif op.opcode == opcodes.MSTORE8:
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.MSTORE8.pop)]
|
||||
inst = TACOp(op.opcode, args, op.pc)
|
||||
|
||||
# SLOAD is same as MLOAD, they both hasve value in the tempt file
|
||||
# We will assign the real value to the storage variable
|
||||
elif op.opcode == opcodes.SLOAD or op.opcode == opcodes.MLOAD:
|
||||
new_var = mem.Variable(values=[op.value], name=new_var.name)
|
||||
args = [TACArg.from_var(self.stack.pop())]
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc)
|
||||
elif op.opcode == opcodes.SSTORE:
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(opcodes.SSTORE.pop)]
|
||||
inst = TACOp(op.opcode, args, op.pc)
|
||||
|
||||
# Special cases for kind one, such as CALLVALUE
|
||||
# For kind one, there are no arguments for the previous vandal, so the inst will be incomplete
|
||||
# For example, 0xa CALLVALUE 0x0 will be transalated into V4 =
|
||||
# Now we assign the real value to this opcode and keep its opcode
|
||||
elif op.opcode.is_kind_one():
|
||||
new_var = mem.Variable(values=[op.value], name=new_var.name)
|
||||
args = []
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc, print_name=False)
|
||||
|
||||
# Special cases for kind two, such as CALLDATALOAD
|
||||
# Args have all the stack arguments, those information (stack arguments) are useless
|
||||
# Since we just get the values from geth, not using them.
|
||||
elif op.opcode.is_kind_two():
|
||||
new_var = mem.Variable(values=[op.value], name=new_var.name)
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc, print_name=False)
|
||||
|
||||
# Special cases for kind three store two, such as CALLDATACOPY
|
||||
# There are multiple arguments in this kind of opcodes
|
||||
elif op.opcode.is_kind_three_store_two():
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACOp(op.opcode, args, op.pc, None, op.value)
|
||||
|
||||
# Special cases for kind four, such as call
|
||||
# Field value_extra is the memory content
|
||||
elif op.opcode.is_kind_four():
|
||||
# op.value is success flag, value_extra is the memory content.
|
||||
new_var = mem.Variable(values=[op.value], name=new_var.name)
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc, None, True, op.value_extra)
|
||||
|
||||
elif op.opcode.is_kind_five():
|
||||
new_var = mem.Variable(values=[op.value], name=new_var.name)
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc, None, True, None)
|
||||
|
||||
elif new_var is not None:
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACAssignOp(new_var, op.opcode, args, op.pc)
|
||||
else:
|
||||
args = [TACArg.from_var(var) for var in self.stack.pop_many(op.opcode.pop)]
|
||||
inst = TACOp(op.opcode, args, op.pc)
|
||||
|
||||
# This var must only be pushed after the operation is performed.
|
||||
if new_var is not None:
|
||||
self.stack.push(new_var)
|
||||
|
||||
inst.loc = op.loc
|
||||
inst.call_depth = op.call_depth
|
||||
inst.call_number = op.call_number
|
||||
|
||||
self.ops.append(inst)
|
||||
+1015
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user