diff --git a/wake_printers/__init__.py b/wake_printers/__init__.py index 753122417..794eb0f21 100644 --- a/wake_printers/__init__.py +++ b/wake_printers/__init__.py @@ -15,3 +15,4 @@ from .state_changes import StateChangesPrinter from .storage_layout import StorageLayoutPrinter from .tokens import TokensPrinter +from .lsp_global_symbols_printer import LspGlobalSymbolsPrinterPrinter \ No newline at end of file diff --git a/wake_printers/lsp_global_symbols_printer.py b/wake_printers/lsp_global_symbols_printer.py new file mode 100755 index 000000000..42d0c81ef --- /dev/null +++ b/wake_printers/lsp_global_symbols_printer.py @@ -0,0 +1,345 @@ +from __future__ import annotations + +import networkx as nx +import rich_click as click +import wake.ir as ir +import wake.ir.types as types +from rich import print +from wake.cli import SolidityName +from wake.printers import Printer, printer +#almost everything was foung at link below +#https://docs.soliditylang.org/en/v0.8.26/cheatsheet.html#abi-encoding-and-decoding-functions + + +""" +ABI.ENCODECALL: https://solidity-by-example.org/abi-encode/ + + + +""" + + + +global_definitions = { + "decode": "", + + "encode": "", + + "encodeCall": "", #ask Michal if function can be like this + + "encodePacked" : "", + + "encodeWithSelector" : "", + + "encodeWithSignature" : "", + + "addmod": "// compute: (x + y) % k, where k != 0\nfunction: addmod(uint x, uint y, uint k) returns (uint)", + + "balance": "// balance of the Address in Wei\nuint256
.balance", + + "call":"
.call(bytes memory) returns (bool, bytes memory)", + + "code": "// code at the Address (can be empty)\nbytes memory
.code", + + "codehash": "// the codehash of the Address\nbytes32
.codehash", + + "delegatecall": "
.delegatecall(bytes memory) returns (bool, bytes memory)", + + "send": "// send given amount of Wei to the Address\n.send(uint256 amount) returns (bool)", + + "staticcall": "
.staticcall(bytes memory) returns (bool, bytes memory)", + + "transfer": "// transfer given amount of Wei to the Address, throws on failure\n.transfer(uint256 amount)", #function?? + + "length" : "// yields the fixed length of the byte array (read-only)", + + "pop" : "", + + "push" : "", + + "assert": "// ensure the condition is true, otherwise throw\nfunction assert(bool condition)", + + "blobhash": "// returns the hash of the blob at the given index associated with the current transaction\nfunction blobhash(uint index) returns (bytes32)", + + "blockhash": "// hash of the given block\nfunction blockhash(uint blockNumber) returns (bytes32)", + + "basefee": "// current block's base fee\nuint block.basefee", + + "blobbasefee": "// current block's blob base fee\nuint block.blobbasefee", + + "chainid": "// current chain id\nuint block.chainid", + + "coinbase": "// current block's miner address\naddress payable block.coinbase", + + "difficulty" : "// current block's difficulty\nuint block.difficulty", + + "gaslimit": "// current block's gas limit\nuint block.gaslimit", + + "number": "// current block number\nuint block.number", + + "prevrandao": "// random number provided by the beacon chain\nuint block.prevrandao", + + "timestamp": "// current block timestamp in seconds since Unix epoch\nuint block.timestamp", + + "concat": "// concatenate two byte/string arrays\nfunction bytes/string.concat(...) returns (bytes/string memory)", + + "ecrecover": "// recover the address associated with the public key from eliptic curve signature\necrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)", + + "address": "// returns the address", #TODO: is it ok? + + "gas": "", #TODO: idk what to write here + + "selector": "// returns the function selector", #TODO: is it ok? + + "value": "", #TODO: idk what to write here + + "gasleft": "// remaining gas\nuint256 gasleft()", + + "keccak256": "// compute the Keccak-256 hash of the input\nfunction keccak256(bytes memory) returns (bytes32)", + + "data": "// complete calldata\nbytes msg.data", + + "sender": "// Address of the sender of the message (current call)\nmsg.sender", + + "sig" : "// first four bytes of the calldata (i.e. function identifier)\nbytes4 msg.sig", + + "value": "// amount of Wei sent with the message\nuint msg.value", + + "mulmod": "// compute: (x * y) % k, where k != 0\nfunction mulmod(uint x, uint y, uint k) returns (uint)", + + "now" : "// current block timestamp (alias for block.timestamp)\nuint now", #TODO: check if it's ok + + "require": "", #done in code + + "revert": "// abort execution and revert state changes\nfunction revert(string memory message)", + + "ripemd160": "// compute the RIPEMD-160 hash of the input\nfunction ripemd160(bytes memory) returns (bytes20)", + + "selfdestruct": "// destroy the current contract, sending its funds to the recipient\nfunction selfdestruct(address payable recipient)", + + "sha256": "// compute the SHA-256 hash of the input\nfunction sha256(bytes memory) returns (bytes32)", + + "gasprice": "// gas price of the transaction\nuint tx.gasprice", + + "origin": "// sender of the transaction (full call chain)\naddress tx.origin", + + "creationCode": "// creation bytecode of the Contract\nbytes memory type(Contract).creationCode", + + "interfaceId": "// value containing the EIP-165 interface identifier of the given interface\nbytes4 type(Interface).interfaceId", + + "max": "", #done in code + + "min": "", #done in code + + "name": "// name of the contract\nstring memory type(Contract).name", + + "runtimeCode": "// runtime bytecode of the Contract\nbytes memory type(Contract).runtimeCode", + + "wrap": "", #TODO: cant find the right definition + + "unwrap": "", #TODO: cant find the right definition + +} + + + +class LspGlobalSymbolsPrinterPrinter(Printer): + execution_mode = "lsp" + + bit_sizes = [8, 16, 32, 64, 128, 256] + signed_integers = [f"int{i}" for i in bit_sizes] # ['int8', 'int16', 'int32', 'int64', 'int128', 'int256'] + unsigned_integers = [f"uint{i}" for i in bit_sizes] # ['uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256'] + + + + + def handle_abi_decode(self,parent_node: ir.MemberAccess): + """ + unnecesary --> abi.decode is already checking for the types + allowed_types = { + "uint256", "int256", "uint128", "int128", "uint64", "int64", + "uint32", "int32", "uint16", "int16", "uint8", "int8", + "bool", "address", "bytes", "bytes32", "string" + } + """ + + data = (parent_node.arguments[1].source).split(",") #assuming arguments[0] will always be encodedData --> rtn ['(address', ' uint256', ' bool)'] + cleaned_data = [item.replace('(', '').replace(')', '') for item in data] #get strings without brackets + + return f"```solidity\nfunction abi.decode(bytes memory encodedData, ({', '.join(cleaned_data)})) returns ({', '.join(cleaned_data)})\n```" + + + def handle_abi_encode(self, parent_node: ir.MemberAccess): + args = parent_node.arguments + args_abi = [arg.type.abi_type for arg in args] + + return f"```solidity\nfunction abi.encode({', '.join(args_abi)}) returns (bytes memory)\n```" + + + def handle_encode_call(self, parent_node: ir.MemberAccess): + arg_1 = parent_node.arguments[0].source + arg_2 = parent_node.arguments[1].type.abi_type + + return f"```solidity\nfunction abi.encodeCall({arg_1}, {arg_2}) returns (bytes memory)\n```" + + + def handle_encode_packed(self, parent_node: ir.MemberAccess): + args = parent_node.arguments + args_abi = [arg.type_string for arg in args] + + + return f"```solidity\nfunction abi.encodePacked({', '.join(args_abi)}) returns (bytes memory)\n```" + + def handle_encode_selector(self, parent_node: ir.MemberAccess): + args = parent_node.arguments + args_abi = [arg.type_string for arg in args] + + return f"```solidity\n// equivalent to:abi.encodeWithSignature(...)\nfunction abi.encodeWithSelector({', '.join(args_abi)}) returns (bytes memory)\n```" + + + def handle_encode_signature(self, parent_node: ir.MemberAccess): + args = parent_node.arguments + args_abi = [arg.type_string for arg in args] + + return f"```solidity\n// equivalent to: abi.encodeWithSelector(...)\nfunction abi.encodeWithSignature({', '.join(args_abi)}) returns (bytes memory)\n```" + + + def handle_push(self, parent_node: ir.MemberAccess): + array_type = str(parent_node.expression.type_string).split("[")[0].replace("function", "") + + + if len(parent_node.arguments) == 0: + return f"```solidity\nfunction push() returns{array_type})\n```" + else: + array_type = array_type.replace(" ", "") + return f"```solidity\nfunction push{array_type})\n```" + + + """ + MemberAccess nodes: other symbols that are NOT in range between -1 to -99 + """ + def visit_member_access(self, node: ir.MemberAccess): + hover_text = "" + if node.member_name in global_definitions: + parent = node.parent + if node.type.abi_type in self.signed_integers or node.type.abi_type in self.unsigned_integers: + hover_text = f"```solidity\n{node.type.abi_type} type({node.type.abi_type}).{node.member_name}\n```" + + elif node.member_name == "concat": + args_0 = parent.arguments[0].type_string + args_1 = parent.arguments[1].type_string + + if parent.type.abi_type == "bytes": + hover_text = f"```solidity\n// concatenate two byte arrays\nbytes.concat({args_0}, {args_1}) returns (bytes memory)\n```" + else: + hover_text = f"```solidity\n// concatenate two string arrays\nstring.concat(string memory a, string memory b) returns (string memory)\n```" + + elif node.member_name == "decode": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_abi_decode(parent) + + elif node.member_name == "encode": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_abi_encode(parent) + + elif node.member_name == "encodeCall": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_encode_call(parent) + + elif node.member_name == "encodePacked": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_encode_packed(parent) + + elif node.member_name == "encodeWithSelector": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_encode_selector(parent) + + elif node.member_name == "encodeWithSignature": + # equivalent to: abi.encodeWithSelector + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_encode_signature(parent) + + elif node.member_name == "push": + assert isinstance(parent, ir.FunctionCall) + hover_text = self.handle_push(parent) + + elif node.member_name == "pop": + assert isinstance(parent, ir.FunctionCall) + rtn = parent.expression.type_string.split("[")[0].replace("function", "") + + hover_text = f"```solidity\nfunction pop() returns{rtn})\n```" + + elif node.member_name == "wrap": + assert isinstance(parent, ir.FunctionCall) + arg = parent.arguments[0].type_string + rtn = parent.parent.source.split(".")[0].split(" ")[1] + hover_text = f"```solidity\nfunction wrap({arg}) returns ({rtn})\n```" + + + + elif node.member_name == "unwrap": + assert isinstance(parent, ir.FunctionCall) + rtn = parent.arguments[0].type_string + abi_type = parent.arguments[0].type.abi_type + + hover_text = f"```solidity\nfunction unwrap({rtn}) returns ({abi_type})\n```" + + + else: + hover_text = f"```solidity\n{global_definitions[node.member_name]}\n```" + + assert self.lsp_provider is not None + self.lsp_provider.add_hover(node, hover_text) + + + + + + """ + Identifier nodes: symbols of the Solidity language with identifiers between -1 to -99 + """ + def visit_identifier(self, node: ir.Identifier): + if node.name in global_definitions: + if node.name == "require": + parent = node.parent + assert isinstance(parent, ir.FunctionCall) + if len(parent.arguments) == 1: + hover_text = f"```solidity\n// ensure the condition is true, otherwise throw\nrequire(bool condition) \n```" + else: + hover_text = f"```solidity\n// ensure the condition is true, otherwise throw\nrequire(bool condition, string memory message)\n```" + elif node.name == "": + pass + + else: + hover_text = f"```solidity\n{global_definitions[node.name]}\n```" + + + + + + + assert self.lsp_provider is not None + self.lsp_provider.add_hover(node, hover_text) + + + + + + + + def print(self) -> None: + pass + + + @printer.command(name="lsp_global_symbols_printer") + def cli(self) -> None: + pass + + + + + + + + +