Setup the project
In the first step, we will define the structure of the project. We suggest using that structure during development for the following reasons:
- The interface of the contracts is defined separately from the contracts. That allows others to communicate with these contracts without knowledge about the implementation and these interfaces can easily be imported to another project(that allows others to communicate with these contracts).
- Resolves the problem with cyclic dependencies across the project. To call the methods of the contract from the project you enough to have an interface.
- The usage of the
ink-as-dependency
feature is minimized. That can resolve a lot of headaches in the future. - The implementation of big contracts can be split into small parts to simplify the development.
- The body of the contract doesn't contain the whole implementation of the contract. That improves the readability of the contracts.
The project will contain the following directories:
traits
- contains all traits(interfaces) of the contracts developed in the project. Traits describe the functionality of each contract and allow to do cross-contracts calls without knowledge about the implementation(no need to import the contract, using a trait is enough).impls
- contains the implementations of traits for the contracts. If the contract contains several simple functions, better to implement them in the body of the contract. But if the contract contains a lot of logic and methods, better to move(and maybe split on parts) the implementation to that directory. Better to store the implementation of one contract in its own directory and not mix it with others.contracts
- contains the bodies of the contracts. Each contract should be defined in its own crate(it is a rule of the ink!). Each folder in that directory is a crate(contract). These contracts can have the implementation inside themselves or they can import the implementation fromimpls
.
In that structure traits
and impls
directories are the parts of on PROJECT_NAME
crate.
Each contract in the contracts
directory imports the crate PROJECT_NAME
and use it inside.
Based on the rules above the structure will look like the following:
├── traits
│ ├── lending.rs
│ ├── loan.rs
│ ├── mod.rs
│ ├── shares.rs
│ └── stable_coin.rs
├── impls
│ ├── lending
│ │ ├── data.rs
│ │ ├── lending.rs
│ │ ├── lending_permissioned.rs
│ │ └── mod.rs
│ └── mod.rs
├── contracts
│ ├── lending
│ │ ├── Cargo.toml
│ │ └── lib.rs
│ ├── loan
│ │ ├── Cargo.toml
│ │ └── lib.rs
│ ├── shares
│ │ ├── Cargo.toml
│ │ └── lib.rs
│ └── stable_coin
│ ├── Cargo.toml
│ └── lib.rs
├── lib.rs
├── Cargo.toml
traits
directory contains 4 traits with logic from the overview.
In the example:
LendingContract
is a big contract and we moved the main logic intoimpls/lending
folder. That logic is split into two traits(Lending
andLendingPermissione
) to show how it can be done.LoanContract
contains few methods, so the implementation is defined directly in the body of the contract.SharesContract
isPSP22
token with publicmint
andburn
functions that require ownership.StableCoinContract
is a purePSP22
token.