This project aims to create a pallet to manage a liquidity pool. A user can stake a token (e.g. a DOT) and receive a liquid token (e.g. LDOT). The user can:
- Trade/transfer the LDOT
- Hold it to receive more LDOTs every X blocks
- Propose and vote changes about the economic parameters of the pool.
Note
What is a liquidity pool? A liquidity pool is a digital pile of cryptocurrency locked in a smart contract. This results in creating liquidity for faster transactions. A major component of a liquidity pool is automated market makers (AMMs). An AMM is a protocol that uses liquidity pools to allow digital assets to be traded in an automated way rather than through a traditional market of buyers and sellers.
Warning
This is an educational purpose project only. Should NOT be used in a real production system.
- Run tests with
cargo test
- Build with
cargo build --release
- Clone this modified version of
substrate-node-template
. - Build with
cargo build --release
- Run the binary with
./target/release/node-template --dev
- Interact with the node using polkadot.js and/or with this modified version of
substrate-front-end-template
(Useful to see the balance in LDOT).
The following is a summary of src/lib.rs
stake(amount: u128)
unstake(amount: u128)
transfer(recv: T::AccountId, amount: u128)
change_percentage(percentage: u8)
change_block_time(block_time: u32)
on_finalize()
BlockToUnlock<T> = StorageValue<_, u32, ValueQuery, DefaultBlockTime<T>>
Percentage<T> = StorageValue<_, u8, ValueQuery, DefaultPercentage<T>>
StakedTimes<T> = StorageMap<_, Blake2_128Concat, T::AccountId, T::BlockNumber, OptionQuery>
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>
type MainToken: ReservableCurrency<Self::AccountId, Balance = u128>
type StakedToken: Currency<Self::AccountId, Balance = u128>
type PalletId: Get<PalletId>
- Instead of sending the funds via
pallet-staking
I used aReservableCurrency
to handle the "main token", so I can do areserve
to lock the funds and give aCurrency
representing the Liquid Token in return. - When the user calls
stake(amount)
anamount
ofReservableCurrency
is reserved and a(amount + n%)
ofCurrency
is created and deposited to the user. - The user cannot call
stake(amount)
again before several blocks (BlockToUnlock
). - The user cannot call
unstake(amount)
before some blocks (BlockToUnlock
). - After several blocks (
BlockToUnlock
) the user can callunstake(amount)
to burn anamount
ofCurrency
and unreserve his portion ofReservableCurrency
. - The user can transfer using
transfer(recv, amount)
part of hisCurrency
- Using
pallet_democracy
the user can create a proposal paying inCurrency
(so the liquid token, not inReservableCurrency
), as shown here. - Through governance then users holding the liquid token can vote to use
change_percentage(percentage)
andchange_block_time(block_time)
to vary the economic parameters of the pool. - As an incentive not to transfer liquid tokens, new tokens are issued every X blocks and distributed to users using logic inside the
on_finalize
hook. - At the moment you can only propose changes using the liquid token, but to vote you have to use the Main Token. To vote using the Liquid Token I would have to create a wrapper for
pallet_democracy
, but I would lose the integration with polkadot.js Governance tab, and I did not know if this was correct or not. A code example:
pub fn vote_in_favor(
origin: OriginFor<T>,
#[pallet::compact] referendum_index: ReferendumIndex,
#[pallet::compact] weight: StakedTokenBalance<T>,
) -> DispatchResult {
// Check that the extrinsic was signed and get the signer.
// This function will return an error if the extrinsic is not signed.
let _who = ensure_signed(origin.clone())?;
let vote = pallet_democracy::AccountVote::Split {
aye: weight.into(),
nay: 0_u8.into(),
};
pallet_democracy::Pallet::<T>::vote(origin, referendum_index, vote)?;
Ok(())
}
Warning
Of course, I am aware that it makes no economic sense, but I had fun experimenting by combining various pallets.
- Use Cumulus to convert a Substrate FRAME runtime into a Parachain runtime.
- Use XCM and XCMP to transfer the LDOT to other parachains.
- Build a frontend that queries the DOT and LDOT balances