def _check_no_ghost_function(self): if self.ghost_functions: cond = "Ghost function declaration" node = first(self.ghost_functions.values()).node elif self.ghost_function_implementations: cond = "Ghost function definition" node = first(self.ghost_function_implementations.values()).node else: return raise InvalidProgramException( node, 'invalid.ghost', f'{cond} only allowed after "#@ interface"')
def _check_ghost_implements(program: VyperProgram): def check(cond, node, ghost_name, interface_name): if not cond: raise InvalidProgramException( node, 'ghost.not.implemented', f'The ghost function "{ghost_name}" from the interface "{interface_name}" ' f'has not been implemented correctly.') ghost_function_implementations = dict( program.ghost_function_implementations) for itype in program.implements: interface = program.interfaces[itype.name] for ghost in interface.own_ghost_functions.values(): implementation = ghost_function_implementations.pop( ghost.name, None) check(implementation is not None, program.node, ghost.name, itype.name) check(implementation.name == ghost.name, implementation.node, ghost.name, itype.name) check( len(implementation.args) == len(ghost.args), implementation.node, ghost.name, itype.name) check(implementation.type == ghost.type, implementation.node, ghost.name, itype.name) if len(ghost_function_implementations) > 0: raise InvalidProgramException( first(ghost_function_implementations.values()).node, 'invalid.ghost.implemented', f'This contract implements some ghost functions that have no declaration in ' f'any of the implemented interfaces.\n' f'(Ghost functions without declaration: {list(ghost_function_implementations)})' )
def is_canditate(f: VyperFunction) -> bool: if not f.is_public(): return False elif len(f.args) == 1: return first(f.args.values()).type == types.VYPER_WEI_VALUE else: return not f.args
def compute(self, program: VyperProgram): send_functions = [] for function in program.functions.values(): self.visit(function.node, function, send_functions) def is_canditate(f: VyperFunction) -> bool: if not f.is_public(): return False elif len(f.args) == 1: return first(f.args.values()).type == types.VYPER_WEI_VALUE else: return not f.args def val(f: VyperFunction): name = f.name.lower() if name == names.WITHDRAW: return 0 elif names.WITHDRAW in name: return 1 else: return 3 candidates = sorted((f for f in send_functions if is_canditate(f)), key=val) program.analysis.accessible_function = first(candidates)
def _check_ghost_functions(program: VyperProgram): if not isinstance(program, VyperInterface): node = first(program.node.stmts) or program.node for implemented_ghost in program.ghost_function_implementations.values( ): if program.ghost_functions.get(implemented_ghost.name) is None: raise InvalidProgramException( implemented_ghost.node, 'missing.ghost', f'This contract is implementing an unknown ghost function. ' f'None of the interfaces, this contract implements, declares a ghost ' f'function "{implemented_ghost.name}".') for interface in program.interfaces.values(): for ghost_function_list in interface.ghost_functions.values(): for ghost_function in ghost_function_list: imported_ghost_functions = [ ghost_func for ghost_func in program.ghost_functions.get( ghost_function.name, []) if ghost_func.file == ghost_function.file ] if not imported_ghost_functions: prefix_length = len( os.path.commonprefix( [ghost_function.file, program.file])) raise InvalidProgramException( node, 'missing.ghost', f'The interface "{interface.name}" ' f'needs a ghost function "{ghost_function.name}" from ' f'".{os.path.sep}{ghost_function.file[prefix_length:]}" but it ' f'was not imported for this contract.') imported_ghost_functions = [ ghost_func for ghost_func in program.ghost_functions.get(ghost_function.name) if ghost_func.interface == ghost_function.interface ] for imported_ghost_function in imported_ghost_functions: if ghost_function.file != imported_ghost_function.file: prefix_length = len( os.path.commonprefix([ ghost_function.file, imported_ghost_function.file ])) ghost_function_file = ghost_function.file[ prefix_length:] imported_ghost_function_file = imported_ghost_function.file[ prefix_length:] raise InvalidProgramException( node, 'duplicate.ghost', f'There are two versions of the ghost function ' f'"{ghost_function.name}" defined in an interface ' f'"{ghost_function.interface}" one from ' f'[...]"{imported_ghost_function_file}" the other from ' f'[...]"{ghost_function_file}".')
def check(self, program: VyperProgram): if program.resources and not program.config.has_option(names.CONFIG_ALLOCATION): msg = "Resources require allocation config option." raise InvalidProgramException(first(program.node.stmts) or program.node, 'alloc.not.alloc', msg) seen_functions = set() for implements in program.real_implements: interface = program.interfaces.get(implements.name) if interface is not None: function_names = set(function.name for function in interface.functions.values()) else: contract = program.contracts[implements.name] function_names = set(contract.type.function_types) _assert(not seen_functions & function_names, program.node, 'invalid.implemented.interfaces', f'Implemented interfaces should not have a function that shares the name with another function of ' f'another implemented interface.\n' f'(Conflicting functions: {seen_functions & function_names})') seen_functions.update(function_names) for function in program.functions.values(): self.visit(function.node, _Context.CODE, program, function) if function.is_pure(): self._function_pure_checker.check_function(function, program) for postcondition in function.postconditions: self.visit(postcondition, _Context.POSTCONDITION, program, function) if function.preconditions: _assert(not function.is_public(), function.preconditions[0], 'invalid.preconditions', 'Public functions are not allowed to have preconditions.') for precondition in function.preconditions: self.visit(precondition, _Context.PRECONDITION, program, function) if function.checks: _assert(not program.is_interface(), function.checks[0], 'invalid.checks', 'No checks are allowed in interfaces.') for check in function.checks: self.visit(check, _Context.CHECK, program, function) if function.performs: _assert(function.name != names.INIT, function.performs[0], 'invalid.performs', '__init__ does not require and must not have performs clauses.') _assert(function.is_public(), function.performs[0], 'invalid.performs', 'Private functions are not allowed to have performs clauses.') for performs in function.performs: self._visit_performs(performs, program, function) for lemma in program.lemmas.values(): for default_val in lemma.defaults.values(): _assert(default_val is None, lemma.node, 'invalid.lemma') _assert(not lemma.postconditions, first(lemma.postconditions), 'invalid.lemma', 'No postconditions are allowed for lemmas.') _assert(not lemma.checks, first(lemma.checks), 'invalid.lemma', 'No checks are allowed for lemmas.') _assert(not lemma.performs, first(lemma.performs), 'invalid.lemma') self.visit(lemma.node, _Context.LEMMA, program, lemma) for stmt in lemma.node.body: _assert(isinstance(stmt, ast.ExprStmt), stmt, 'invalid.lemma', 'All steps of the lemma should be expressions') for precondition in lemma.preconditions: self.visit(precondition, _Context.LEMMA, program, lemma) for invariant in program.invariants: self.visit(invariant, _Context.INVARIANT, program, None) if program.general_checks: _assert(not program.is_interface(), program.general_checks[0], 'invalid.checks', 'No checks are allowed in interfaces.') for check in program.general_checks: self.visit(check, _Context.CHECK, program, None) for postcondition in program.general_postconditions: self.visit(postcondition, _Context.POSTCONDITION, program, None) if program.transitive_postconditions: _assert(not program.is_interface(), program.transitive_postconditions[0], 'invalid.transitive.postconditions', 'No transitive postconditions are allowed in interfaces') for postcondition in program.transitive_postconditions: self.visit(postcondition, _Context.TRANSITIVE_POSTCONDITION, program, None) for ghost_function in program.ghost_function_implementations.values(): self.visit(ghost_function.node, _Context.GHOST_FUNCTION, program, None) if isinstance(program, VyperInterface): for caller_private in program.caller_private: self._visited_caller_spec = False self._num_visited_conditional = 0 self.visit(caller_private, _Context.CALLER_PRIVATE, program, None) _assert(self._visited_caller_spec, caller_private, 'invalid.caller.private', 'A caller private expression must contain "caller()"') _assert(self._num_visited_conditional <= 1, caller_private, 'invalid.caller.private', 'A caller private expression can only contain at most one "conditional(...)"')
def _check_resources(program: VyperProgram): if not isinstance(program, VyperInterface): node = first(program.node.stmts) or program.node for interface in program.interfaces.values(): for resource_name, resources_list in interface.resources.items(): for resource in resources_list: if resource.file is None: if program.resources.get(resource_name) is None: if not program.config.has_option( names.CONFIG_ALLOCATION): raise InvalidProgramException( node, 'alloc.not.alloc', f'The interface "{interface.name}" uses the ' f'allocation config option. Therefore, this contract ' f'also has to enable this config option.') raise InvalidProgramException( node, 'missing.resource', f'The interface "{interface.name}" ' f'needs a default resource "{resource_name}" ' f'that is not present in this contract.') continue imported_resources = [ r for r in program.resources.get(resource_name, []) if r.file == resource.file ] if not imported_resources: prefix_length = len( os.path.commonprefix([resource.file, program.file])) raise InvalidProgramException( node, 'missing.resource', f'The interface "{interface.name}" ' f'needs a resource "{resource_name}" from ' f'".{os.path.sep}{resource.file[prefix_length:]}" but it ' f'was not imported for this contract.') imported_resources = [ r for r in program.resources.get(resource_name) if r.interface == resource.interface ] for imported_resource in imported_resources: if resource.file != imported_resource.file: prefix_length = len( os.path.commonprefix( [resource.file, imported_resource.file])) resource_file = resource.file[prefix_length:] imported_resource_file = imported_resource.file[ prefix_length:] raise InvalidProgramException( node, 'duplicate.resource', f'There are two versions of the resource ' f'"{resource_name}" defined in an interface ' f'"{imported_resource.interface}" one from ' f'[...]"{imported_resource_file}" the other from ' f'[...]"{resource_file}".') for interface_type in program.implements: interface = program.interfaces[interface_type.name] for resource_name, resource in program.declared_resources.items(): if resource_name in interface.own_resources: raise InvalidProgramException( resource.node, 'duplicate.resource', f'A contract cannot redeclare a resource it already imports. ' f'The resource "{resource_name}" got already declared in the ' f'interface {interface.name}.')
def _check_no_lemmas(self): if self.lemmas: raise InvalidProgramException( first(self.lemmas.values()).node, 'invalid.lemma', 'Lemmas are not allowed in interfaces')
def build(self, node) -> VyperProgram: self.visit(node) # No trailing local specs allowed self._check_no_local_spec() self.config = self.config or Config([]) if self.config.has_option(names.CONFIG_ALLOCATION): # Add wei underlying resource underlying_wei_type = ResourceType(names.UNDERLYING_WEI, {}) underlying_wei_resource = Resource(underlying_wei_type, None, None) self.resources[names.UNDERLYING_WEI] = underlying_wei_resource # Create a fake node for the underlying wei resource fake_node = ast.Name(names.UNDERLYING_WEI) copy_pos_from(node, fake_node) fake_node.is_ghost_code = True if not self.config.has_option(names.CONFIG_NO_DERIVED_WEI): # Add wei derived resource wei_type = DerivedResourceType(names.WEI, {}, underlying_wei_type) wei_resource = Resource(wei_type, None, None, fake_node) self.resources[names.WEI] = wei_resource if self.is_interface: interface_type = InterfaceType(self.name) if self.parse_level <= 2: return VyperInterface(node, self.path, self.name, self.config, self.functions, self.interfaces, self.resources, self.local_state_invariants, self.inter_contract_invariants, self.general_postconditions, self.transitive_postconditions, self.general_checks, self.caller_private, self.ghost_functions, interface_type) else: return VyperInterface(node, self.path, self.name, self.config, {}, {}, self.resources, [], [], [], [], [], [], self.ghost_functions, interface_type, is_stub=True) else: if self.caller_private: node = first(self.caller_private) raise InvalidProgramException( node, 'invalid.caller.private', 'Caller private is only allowed in interfaces') # Create the self-type self_type = SelfType(self.field_types) self_struct = VyperStruct(names.SELF, self_type, None) return VyperProgram( node, self.path, self.config, self_struct, self.functions, self.interfaces, self.structs, self.contracts, self.events, self.resources, self.local_state_invariants, self.inter_contract_invariants, self.general_postconditions, self.transitive_postconditions, self.general_checks, self.lemmas, self.implements, self.implements + self.additional_implements, self.ghost_function_implementations)