Value of DEIP investments opportunities
Another intrinsic part of the creator economy implemented in the DEIP pallet, the module with the essential DEIP business logic, is investment opportunities. This feature lets you organize different types of campaigns, from a public fundraising to private investing.
An example of an everyday business scenario is the asset exchange, when a DAO that owns (F-)NFTs wants to exchange them for another asset type. To reach the goal, the DAO would create an investment opportunity and list all available (F-)NFTs and desirable assets for an easy exchange.
A more interesting business scenario is fundraising for the development of a project, such as new technology, research, film script, worktext, text recording of a musical work, and so on. On DEIP, you can tokenize the project into (F-)NFTs, which are offered to investors during the fundraising campaign. It opens up great opportunities to invest in promising projects and receive future dividends from your investments.
Currently, DEIP implements a single investment opportunity type — a simple crowdfunding, which is enough to cover a large number of different scenarios. In the future, new types can be introduced via improvement proposals, like in Ethereum. This process is planned to be seamless thanks to the flexibility of the Substrate framework and Rust programming language used in DEIP.
How to use it
Let’s see the investment opportunities in practice and create one with Polkadot.js, the Polkadot app.
DEIP investment opportunity constructor on Polkadot.js
The first parameter is externalId — a unique opportunity identifier that can be used as a reference both in on-chain transactions to the blockchain and in off-chain infrastructure records such as description of the proposed campaign, deadlines, and so on. It’s mandatory, of the type H160 (20 bytes) and can be represented as a hash of 160 bits. We generate it using the ripemd160 crypto-function and some salt.
The second parameter is mandatory and holds the creator’s address. Since we’re building a blockchain-agnostic protocol, the address is duplicated and checked with the native Substrate address (AccountId) extracted from the signature.
The third parameter is mandatory and holds an argument list with the (F-)NFTs that will be offered during the campaign. The list contains IDs of the assets and their quantity. Note that the creator must have the required campaign funds in their account at the launch time or sending the campaign live will result in an error.
The fourth parameter holds the type of campaign. As mentioned earlier, DEIP currently only supports SimpleCrowdfunding, which has four essential parameters:
- The first two parameters set the start and end time of the campaign. They are set in the UNIX time format. You can use any online UNIX time converter to set the desired time.
- The third parameter, soft_cap, sets the minimum threshold of funds to be drawn. If soft_cap is reached by the end of campaigning, the campaign is considered successful, and the distribution of funds begins: the amount raised is transferred to the creator while the shares offered as a percentage of the investment are transferred to the participants. Otherwise, the funds return to their original owners after the campaign ends.
- The fourth parameter, hard_cap, sets the maximum threshold, which can be ≥ to soft_cap. Whenever hard_cap is reached, the campaign ends regardless of its end time described above.
Once a fundraising campaign has been successfully created, you can view its status navigating on the Polkadot.js app: Chainstate -> DEIP -> SimpleCrowdfundingMap.
Network members can invest in multiple campaigns. The number of investments is not limited.
To invest on Polkadot.js, you need to select “extrinsic invest”, enter the external_id of the campaign and the invested sum, and send a network transaction with this data.
Technical details
InvestmentOpportunity functionality is implemented in the DEIP pallet, the module with the essential DEIP functionality. The implementation consists of two public methods, create_investment_opportunity and invest, which offers the business logic of creating an investment campaign and investing in one. All these methods operate on the associative container SimpleCrowdfundingMap that holds data on campaigns with their detailed descriptions, and on InvestmentMap that holds the records on investments (campaign, who, when, amount). A successfully executed operation triggers a created/invested event.
The SimpleCrowdfundingMap and InvestmentMap containers can be accessed from outside via the following RPC methods: DEIP_getInvestmentOpportunity that returns information about the company by its ID, and DEIP_getInvestmentOpportunityList that returns a list of opportunities and supports pagination.
The mechanism for activating and completing fundraising campaigns deserves a special mention. For the mechanism to be viable, it would require passing control to a special code that checks the current time and parameters of the campaign, as well as the amount of currently raised funds. Substrate offers two basic approaches for implementing a mechanism like that:
- On_initialize/on_finalize callbacks, which are called for each block, respectively
- Off-chain workers (OCW) — a logic that can be run by selected network nodes on a specified block; code execution can last indefinitely, and an extrinsic can be sent to the network upon completion (read more on that in the Substrate documentation)
Our implementation uses the second mechanism, the first not being suitable, as the amount of data would be quite big, e.g.10, 000 or even 100,000 records. Callbacks also require very fast execution or the network slows down or even stops.
To apply OCW, we have implemented ValidateUnsigned. The auxiliary process_investment_opportunities_offchain function runs linearly through the SimpleCrowdfundingMap container and sends auxiliary extrinsic and expire_crowdfunding, finish_crowdfunding, or activate_crowdfunding, respectively. In the ValidateUnsigned implementation, they are handled accordingly.
Linear enumeration of the SimpleCrowdfundingMap container is not an issue. First of all, the execution of this logic is done directly by the node on the running machine, so it can be scaled vertically by increasing the power of such nodes. Second, this logic can also be scaled horizontally adding more nodes. Third, if the data volumes turn out to be so large that it takes 10\20\30 seconds for a powerful machine to process, the block loss will be negligible, 1–5 missing blocks. Considering that fundraising campaigns take days, weeks, or more, a delay like this is very acceptable.
In the development process, we had to solve the zombie DAO problem.
Imagine a DAO has invested in a campaign. Then, while the campaign is still running, the DAO spends its own remaining account assets, which makes it — in Asset pallet terms — a zombie. Will the DAO be able to get its invested (F-)NFTs back? The answer to that question is no.
How did we come to this conclusion? Using the Substrate’s rich testing capabilities, we added a corresponding case to the simple_crowdfunding_expired test, and the test runs confirmed it.
In detail, the main frame_system pallet in Substrate provides well documented API methods inc\dec_consumers of an account, and if an account has at least one consumer, it cannot be deleted. A DAO that invests in a campaign gets an inc_consumers call. When the campaign is finished, the investing DAO gets a dec_consumers, which ensures that the network doesn’t go into an incorrect state. For the campaign creator, a similar problem is solved differently: an ExistentialDeposit is taken from the account and returned upon completion.
Future plans
We plan to expand the number of investment opportunity types, via user proposals or otherwise. One such idea is the donations type that implements the classic model of fundraising through donations.