Skip to content

VIP: abstract methods in vyper #4730

@charles-cooper

Description

@charles-cooper

Simple Summary

add abstract methods to the vyper language. use a top-level resolution block as an explicit, in-text declaration of programmer intent.

the text below was co-created with claude code and is a starting point for the final specification.

Motivation

This document outlines the design for adding abstract methods to the Vyper programming language. The goal is to enable library authors to provide reusable contract patterns while maintaining Vyper's core principles of readability, explicitness, and security.

Library authors currently face limitations when trying to provide reusable contract implementations. They often need to implement 95% of a pattern's functionality while leaving specific customization points for users. Without abstract methods, this requires either:

  • Copy-pasting entire implementations
  • Complex delegation patterns
  • Sacrificing type safety

Abstract methods allow library authors to define contracts with "holes" that users fill in, similar to the template method pattern in object-oriented programming.

Design Goals

  1. No spooky action at a distance - Module behavior must be understandable by reading the module itself
  2. Explicit over implicit - All abstract method resolutions must be explicit
  3. Compile-time resolution - No runtime dispatch or polymorphism
  4. Prevent ambiguity - The diamond problem must be impossible
  5. Maintain simplicity - The mental model must remain simple

Specification

Core Concepts

  1. Libraries can define abstract methods that must be implemented by users
  2. Libraries can provide implementations for other libraries' abstract methods
  3. Compilation targets must explicitly resolve all abstract methods using a resolutions block
  4. All resolution happens at compile time through code inlining

Syntax

Defining Abstract Methods in Libraries

# base_token.vy
@library

@abstract
def before_transfer(sender: address, recipient: address, amount: uint256): ...

@abstract
def after_transfer(sender: address, recipient: address, amount: uint256): ...

@external
def transfer(recipient: address, amount: uint256) -> bool:
    self.before_transfer(msg.sender, recipient, amount)
    # ... transfer logic ...
    return True

Providing Implementations in Libraries

# pausable_token.vy
import base_token

@library
uses: base_token

provides:
    base_token.before_transfer -> self._check_pause
    base_token.after_transfer -> self._after_transfer

@internal
def _check_pause(sender: address, recipient: address, amount: uint256):
    assert not self.paused, "Token is paused"

@internal
def _after_transfer(sender: address, recipient: address, amount: uint256):
    pass  # no-op

Resolution in Compilation Targets

# my_token.vy

import base_token
import pausable_token

resolutions:
    base_token.before_transfer: accept pausable_token._check_pause
    base_token.after_transfer: override pausable_token._log -> self._my_log
    base_token.on_mint -> self._mint_checks

uses: pausable_token

@internal
def _my_log(sender: address, recipient: address, amount: uint256):
    # Custom implementation
    pass

@internal
def _mint_checks(recipient: address, amount: uint256):
    # Implementation for abstract method
    pass

Resolution Syntax

  • accept <provider>.<method> - Use the provided implementation
  • override <provider>.<method> -> <implementation> - Replace provided implementation
  • -> <implementation> - Provide fresh implementation (when no provider exists)

Key Rules

  1. Libraries cannot override - Only provide or pass through
  2. Explicit conflict resolution - Multiple providers require explicit choice
  3. Complete resolution required - All reachable abstract methods must be in resolutions
  4. Single library rule - A module can only use one library with abstract methods (prevents complexity)

Design Process and Rejected Alternatives

1. Inheritance-Based Approaches (Rejected)

Considered: Traditional OOP-style inheritance with method overriding

Rejected because:

  • Violates "no spooky action at a distance" principle
  • Inheritance chains make reasoning about behavior difficult
  • Vyper explicitly avoids inheritance

2. Implicit Resolution (Rejected)

Considered: Automatically using the "most recent" or "most specific" implementation

Rejected because:

  • Too implicit for Vyper's philosophy
  • Can lead to surprising behavior
  • Makes code review and auditing harder

3. Runtime Dispatch (Rejected)

Considered: Virtual method tables or dynamic dispatch

Rejected because:

  • Performance overhead
  • Complexity in understanding execution flow
  • Against Vyper's principle of static verification

4. Multiple Template Instantiation (Rejected)

Considered: Allowing multiple templates/libraries with abstract methods per contract

Rejected because:

  • Creates complex diamond problems
  • Difficult to reason about method resolution order
  • Significantly increases complexity

5. Cross-Module Abstract Methods Only (Rejected)

Considered: Abstract methods that can only be implemented across module boundaries

Rejected because:

  • The diamond problem becomes unavoidable
  • Complex resolution rules needed
  • Libraries couldn't compose easily

6. Single-File Abstract Methods Only (Rejected)

Considered: Abstract methods only for forward declarations within one file

Rejected because:

  • Doesn't solve the library author use case
  • Too limited in functionality
  • Doesn't enable code reuse

7. Strategy Pattern / Explicit Delegation (Rejected)

Considered: Using interfaces and explicit delegation instead of abstract methods

# Example of rejected pattern
@external
def transfer(recipient: address, amount: uint256, validator: IValidator) -> bool:
    validator.validate_transfer(msg.sender, recipient, amount)
    # ...

Rejected because:

  • Changes contract APIs
  • Less elegant than abstract methods
  • Requires deploying separate contracts

8. Event-like Hook System (Rejected)

Considered: Multiple implementations that all get called

Rejected because:

  • Ordering problems
  • Performance overhead
  • Too different from traditional abstract methods

9. Implicit accept (Considered, then Rejected)

Considered: Making accept the default, not requiring explicit keyword

Rejected because:

  • Explicit is better than implicit
  • accept makes intent clearer
  • Helps with code review and auditing

Diamond Problem Resolution

The diamond problem is completely prevented through:

  1. Explicit resolution requirement - When multiple libraries provide the same abstract method, compilation fails unless explicitly resolved
  2. Single source of truth - Only the compilation target's resolutions block determines final bindings
  3. No library override - Libraries cannot override other libraries' provisions

Example:

# If lib_a and lib_b both provide base.foo
resolutions:
    base.foo -> lib_a._foo  # Must explicitly choose
    # OR
    base.foo -> lib_b._foo
    # OR  
    base.foo -> self._my_foo

Benefits

  1. Library reusability - Authors can provide mostly-complete implementations
  2. Clear composition - All wiring visible at contract top
  3. Type safety - Compile-time verification of all resolutions
  4. No ambiguity - Diamond problem impossible
  5. Performance - Zero runtime overhead
  6. Auditability - Easy to understand what code does

Risks and Mitigations

  1. Complexity - Mitigated by keeping rules simple and explicit
  2. Learning curve - Mitigated by clear documentation and examples
  3. Tooling updates - Mitigated by phased rollout

Conclusion

This design provides abstraction capabilities for library authors while maintaining Vyper's core philosophy. By requiring explicit resolution and preventing library overrides, we avoid the drawbacks of inheritance while enabling useful code reuse patterns.

The resolutions block serves as a single source of truth at the top-level compilation target, making contracts simpler to audit. All method dispatch is resolved at compile time, maintaining Vyper's performance characteristics and security properties.

Backwards Compatibility

No known backwards compatibility issues

Dependencies

#3722
#2431

References

Add any references that this VIP might reference (other VIPs/issues, links to blog posts, etc.)

Copyright

Copyright and related rights waived via CC0

Metadata

Metadata

Assignees

No one assigned

    Labels

    VIP: DiscussionUsed to denote VIPs and more complex issues that are waiting discussion in a meeting

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions