Skip to content

Latest commit

 

History

History
93 lines (67 loc) · 5.02 KB

Extensibility.md

File metadata and controls

93 lines (67 loc) · 5.02 KB

Extensibility

Expect this pattern to evolve (as it has already done) or even disappear if proper extensibility features are implemented into Cairo.

The extensibility problem

Smart contract development is a critical task. As with all software development, it is error prone; but unlike most scenarios, a bug can result in major losses for organizations as well as individuals. Therefore writing complex smart contracts is a delicate task.

One of the best approaches to minimize introducing bugs is to reuse existing, battle-tested code, a.k.a. using libraries. But code reutilization in StarkNet’s smart contracts is not easy:

  • Cairo has no explicit smart contract extension mechanisms such as inheritance or composability
  • Using imports for modularity can result in clashes (more so given that arguments are not part of the selector), and lack of overrides or aliasing leaves no way to resolve them
  • Any @external function defined in an imported module will be automatically re-exposed by the importer (i.e. the smart contract)

To overcome these problems, this project builds on the following guidelines™.

The pattern

The idea is to have two types of Cairo modules: libraries and contracts. Libraries define reusable logic and storage variables which can then be extended and exposed by contracts. Contracts can be deployed, libraries cannot.

To minimize risk, boilerplate, and avoid function naming clashes, we follow these rules:

Libraries

  • All function and storage variable names must be prefixed with the file name to prevent clashing with other libraries (e.g. ERC20_approve in the ERC20 library)
  • Must not implement any @external or @view functions
  • Must not implement constructors
  • Must not call initializers on any function
  • Should implement initializer functions to mimic construction logic if needed (as any other library function, never as @external)

Contracts

  • Can import from libraries
  • Should implement @external functions if needed
  • Should implement a constructor that calls initializers
  • Must not call initializers in any function beside the constructor

Note that since initializers will never be marked as @external and they won’t be called from anywhere but the contract constructor, there’s no risk of re-initialization after deployment. It’s up to the library developers not to make initializers interdependent to avoid weird dependency paths that may lead to double initialization of libraries.

Presets

Presets are pre-written contracts that extend from our library of contracts. They can be deployed as-is or used as templates for customization.

Some presets are:

Emulating hooks

Unlike the Solidity version of OpenZeppelin Contracts, this library does not implement hooks. The main reason being that Cairo does not support overriding functions.

This is what a hook looks like in Solidity:

abstract contract ERC20Pausable is ERC20, Pausable {
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
        super._beforeTokenTransfer(from, to, amount);

        require(!paused(), "ERC20Pausable: token transfer while paused");
    }
}

Instead, the extensibility pattern allows us to simply extend the library implementation of a function (namely transfer) by adding lines before or after calling it. This way, we can get away with:

@external
func transfer{
        syscall_ptr : felt*, 
        pedersen_ptr : HashBuiltin*,
        range_check_ptr
    }(recipient: felt, amount: Uint256) -> (success: felt):
    Pausable_when_not_paused()
    ERC20_transfer(recipient, amount)
    # Cairo equivalent to 'return (true)'
    return (1)
end