Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

upgradable smart contracts with Solidity: interface vs library?

In the context of upgradable smart contracts, when should one use interfaces and when libraries? I read several similar questions and blog posts, but none of them give a straight-to-the-point answer:

  • (Sub) contract vs. library vs. struct vs. Interface
  • How to improve smart contact design in order to distinguish data and their manipulation functions for the same domain object?
  • Writing upgradable contracts in Solidity
  • Interfaces make your Solidity contracts upgradeable
  • Library Driven Development in Solidity
  • Proxy Libraries in Solidity
  • Exploring Code Reuse in Solidity

I understand that the main criteria to consider (besides security) when designing for upgradability are:

  • modularity - for reusability and easier maintenance
  • gas limit - split huge contracts so that they can be deployed in several transactions, so as to not hit the gas limit
  • cost of upgrade - how much does each contract upgrade cost. After a (small) change in one contract, which other contracts need to be re-deployed?
  • cost of execution - separate contracts may result in gas overhead on each call. Try to keep that overhead low.

This Medium post suggests to use libraries to encapsulate logic (e.g. when interacting with "storage contracts") and to use interfaces to decouple inter-contract communication. Other posts suggest different techniques. As far as I understand, libraries are linked to contracts prior to deployment, so once the contract changes, libraries need to be re-deployed. Why it is not better to use interfaces for interacting with storage contracts?

Below I present the two solutions I have seen so far - one with library and one with an interface. (I'd like to avoid solutions with inline assembly...)

Solution with library

StorageWithLib.sol:

contract StorageWithLib {
    uint public data;

    function getData() public returns(uint) {
        return data;
    }
}

StorageLib.sol:

import './StorageWithLib.sol';

library StorageLib {

    function getData(address _storageContract) public view returns(uint) {
        return StorageWithLib(_storageContract).getData();
    }
}

ActionWithLib.sol:

import './StorageLib.sol';

contract ActionWithLib {
    using StorageLib for address;
    address public storageContract;

    function ActionWithLib(address _storageContract) public {
        storageContract = _storageContract;
    }

    function doSomething() public {
        uint data = storageContract.getData();
        // do something with data ...
    }
}

Solution with interface

IStorage.sol:

contract IStorage {     
    function getData() public returns(uint);
}

StorageWithInterface.sol:

import './IStorage.sol';

contract StorageWithInterface is IStorage {
    uint public data;

    function getData() public returns(uint) {
        return data;
    }
}

ActionWithInterface.sol:

import './IStorage.sol';

contract ActionWithInterface {
    IStorage public storageContract;

    function ActionWithInterface(address _storageContract) public {
        storageContract = IStorage(_storageContract);
    }

    function doSomething() public {
        uint data = storageContract.getData();
        // do something with data ...
    }   
}

Considering the above criteria, which solution is preferred for separating storage and logic, and why? In which other cases is the other solution better?

like image 847
Mike Avatar asked Apr 25 '18 12:04

Mike


People also ask

How do upgradable smart contracts work?

Instead, an upgradable smart contract uses a special proxy pattern. The latter involves deploying proxy contracts and implementation contracts (logic contracts): How do Upgradable Smart Contracts Work? Looking at the schematic above, you can see that a user interacts with a logic contract via a proxy contract.

How does cardstack realize upgradable contracts in solidity?

The approach that Cardstack is taking to realize upgradable contracts in Solidity includes these 3 important features: Using a registry contract to manage contract addresses and to bind contracts to their state

What is the use of abstract in solidity?

They can however be used as base contracts from which other contracts can inherit from. Unlike other languages, Solidity contracts do not need an abstract keyword to be marked as abstract. Rather, any contract that has at least one unimplemented function is treated as abstract in Solidity.

How to upgrade smart contracts in Ethereum?

The Topic of upgradeable contracts is not very new to the world of ethereum. There are some different approaches to upgrade smart contracts. Separate logic and data. Partially upgradable smart contracts system. Separate logic and data in key value pairs.


2 Answers

Libraries and Interfaces are really different and used in different cases. I personally don't see them as interchangeable in the contract design. Below I've tried to outline the key characteristics of the two. Note that by interface I mean an abstract contract (which is what you have in your example above). There are still issues imo in Interfaces in Solidity which I highlighted previously here https://medium.com/@elena_di/hi-there-answers-below-6378b08cfcef

Libraries:

  • Can contain logic and are used to extract code away from the contract for maintainability and reuse purposes

  • Deployed once, then referenced in contracts. Their bytecode is deployed separately and is NOT part of the contracts that references them. This is defined as a singleton in my article above ("Writing upgradable contracts in Solidity") where I explain the benefits such as lower deployment cost.

Abstract contracts / Interfaces

  • Cannon contain logic just interface definition

  • Mainly useful as imports to other contracts providing interaction with contract implementations. Interfaces have a much smaller deploy/import size than the implementer contract

  • Provide abstraction for upgradability which I've also described in my article in section on "Use ‘interfaces’ to decouple inter-contract communication"

The only similarity I could think between the two above is that they both can't contain storage variables.

like image 106
Elena Dimitrova Avatar answered Sep 27 '22 21:09

Elena Dimitrova


I'm hoping someone can weigh in with a better answer, but this is a good question and wanted to give my opinion.

In short, as it relates specifically to upgradeable contracts, I don't think there is really a difference between the two approaches. With either implementation, you still have a separate storage contract and you are still issuing a call to the storage contract (one from the action contract through the interface and the other from the action contract indirectly through the library).

The only concrete difference comes with gas consumption. Going through the interface, you are issuing a single call operation. Going through a library, you are adding one layer of abstraction and end up with a delegatecall followed by a call. The gas overhead is not very big, though, so at the end of the day, I think the two approaches are very similar. The approach you take is personal preference.

This is not meant to imply that libraries in general aren't useful. I use them a lot for code reuse for common data structures or operations (for example, since iterating in Solidity is a pain, I have a library to use as a basic iterable collection). I just don't see much value in using them for my storage contract integration. I'm curious to see if Solidity experts out there have a different view.

like image 29
Adam Kipnis Avatar answered Sep 27 '22 22:09

Adam Kipnis