Concrete: Modularizing Smart Contract Architecture

Author: Ryan Turner, VP Engineering (@oxgato)

Through the creation of Concrete, we have developed a novel approach to protocol architecture that utilizes modular components to not only ensure scalability but also to prioritize security.

This article is an introduction to some of the problems we observed in protocol design and how we devised Concrete’s modular architecture.

The Challenge:

The existing upgradeability patterns for smart contracts pose a significant security risk. Current upgradeable smart contracts employ proxies and delegated function calls without a unified storage solution. This leads to a scenario where multiple instances of smart contracts point to different storage slots, escalating the risk of potential storage collision with each upgrade.

After extensive research, we have determined that neither the traditional upgrade pattern nor standard (immutable) contracts meet our needs. We require a secure and readable solution that allows us to respond to rapidly evolving business needs.

It is of paramount importance to safely manage any potential issues, especially when dealing with substantial amounts of user funds.

Previous Attempts:

Our initial iteration (V1) was a standard upgradable protocol, utilizing the Proxy pattern. However, this quickly became unmanageable, resulting in a system that was challenging, if not impossible, to update.

Recognizing this issue, we explored alternative patterns to construct a modular and secure protocol. One such system we considered was ERC-2535, also known as the Diamond Standard. While this standard aligns with our requirements of modularity and upgradeability, it lacks widespread support outside the original author. Additionally, this standard makes heavy use of proxies to provide upgradeability. The standard also introduces what we felt to be difficult-to-understand nomenclature that would represent a barrier of entry to incoming engineers. Due to these issues, we elected to forego following this standard.

This exploration led us to define a set of essential criteria for our solution:

  • Must be secure.

  • Must be modular.

  • Must allow for "hot swapping" of modules.

  • Must follow a logical flow for easy onboarding.

  • Must utilize unified storage (to allow for hot swapping).

  • Must not impose an unreasonable engineering burden.

The Solution:

Ultimately, we chose to pioneer a novel approach, something we had not seen used in production products before.

We decided to break apart our protocol into digestible parts that could be individually swapped out. This sparked the creation of several "tiers" of smart contracts that served as a chain, each with progressively finer-grained functionality. We elected to make use of bytes, as well as low-level calls to ensure that we could pass the required information to the executing logic.

In addition, we devised a method to "whitelist" new modules and restrict the ability to trigger functions to the tier above. A unique storage solution was created that uses specific bytes in the form of keys to store a key/value pair.

This ensures we avoid storage collisions and that data will not be altered without explicit intent.

To complete the solution, we use a protocol-wide access control contract that utilizes ConcreteStorage. There are multiple roles within this Access Control contract with the ability to add any number of new roles in the future.

All contracts undergo rigorous testing to validate our thesis. We utilize extensive unit tests and integration testing that replicates a production environment from front to back.

Coupled with formal audits, we have developed a high-performing protocol that prioritizes lasting security for the future.

Protocol Architecture Specifics:

Concrete Storage

To facilitate state across the entirety of the protocol we devised a unique storage strategy. Named Concrete Storage, this system combines a traditional key/value storage pattern with Solidity’s in-built mapping. By utilizing pointers and keys, we can make entries into storage that have virtually no risk of storage collision.

To facilitate our unique and strict naming convention, we have created a library that will generate keys based on the input provided. An example can be found below:

bytes32 uniqueKey = concreteStorage.CreateKey("Protocol", "Token.User.Balance", address(0x2));
concreteStorage.setUint(uniqueKey, uint(balance));

In the second line of code, the value -in this case uint(balance)- is assigned to the unique key created.

Internally what is happening is that a unique key is being created, and then a value is assigned to that unique key. This creates both an organized, and efficient method of storing large amounts of unique data to the blockchain.

Modularized Code Logic

The protocol is broken into four distinct tiers; each with different rules/guidelines and desired effects. By design, every contract at each tier can be swapped out with any implementations’ logic.

Protocol

The protocol tier serves as the entry point into the system. Since a large amount of our functionality is triggered by a centralized backend* (for now, more on this later), we have two entry points; a “public”, which captures the address of msg.sender and stores it to storage for later use (not unlike Context), and a “private” endpoint that is restricted to the backend server that we run. From there, the call is packaged and forwarded to the module manager, for execution further down the line.

Modules

Modules form the subsequent tier in the execution line. The Modules are entrusted with bundling blueprint calls and transmitting them through the ModuleManager contract for execution. Modules can be invoked to chain multiple calls together for execution.

Blueprints

Blueprints are designed to be the “execution brain” of the system. As the Blueprint receives function calls, it breaks the calls down into individual logic components. It then chains these components together into action calls. Blueprints can read state but cannot write to storage.

Actions

Actions execute a singular piece of logic, for example; writing specific data to storage, executing a specific transfer of tokens, etc. Actions can read or write to storage. Actions are limited in scope to a singular piece of functionality. If engineers find that their action is executing multiple pieces of logic, the code should be moved to a Blueprint.

Managers

Managers are vital components of any system. They are responsible for receiving function calls and necessary data, and passing them along to the next step in the execution chain. These contracts are designed to be concise and focused, with the sole purpose of delegating the calls they receive to the appropriate destination.

Conclusion

We appreciate the time and work of our audit partners and broader ecosystem advisors in reviewing the Concrete architecture.

This platform approach allows for exciting future expansions and enhancements, and if you are interested in chatting about protocol design, working together, or integrating within the Concrete ecosystem, please reach out to hello@concrete.xyz

Follow along on Twitter and join the community

Concrete Protocol: https://twitter.com/ConcreteXYZ

Blueprint Finance: https://twitter.com/Blueprint_DeFi

Subscribe to Concrete
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.