From daff5aed7a104c2024a339e05505fb402f6cf9d3 Mon Sep 17 00:00:00 2001 From: toschdev <8368497+toschdev@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:21:46 +0100 Subject: [PATCH 01/12] Shorten the DeFi loan tutorial --- docs/docs/02-guide/05-loan.md | 448 +++++++++++++++++++++ docs/docs/02-guide/05-loan/00-intro.md | 86 ---- docs/docs/02-guide/05-loan/01-init.md | 72 ---- docs/docs/02-guide/05-loan/02-bank.md | 32 -- docs/docs/02-guide/05-loan/03-request.md | 118 ------ docs/docs/02-guide/05-loan/04-approve.md | 98 ----- docs/docs/02-guide/05-loan/05-repay.md | 98 ----- docs/docs/02-guide/05-loan/06-liquidate.md | 90 ----- docs/docs/02-guide/05-loan/07-cancel.md | 74 ---- docs/docs/02-guide/05-loan/08-play.md | 318 --------------- docs/docs/02-guide/05-loan/_category_.json | 4 - 11 files changed, 448 insertions(+), 990 deletions(-) create mode 100644 docs/docs/02-guide/05-loan.md delete mode 100644 docs/docs/02-guide/05-loan/00-intro.md delete mode 100644 docs/docs/02-guide/05-loan/01-init.md delete mode 100644 docs/docs/02-guide/05-loan/02-bank.md delete mode 100644 docs/docs/02-guide/05-loan/03-request.md delete mode 100644 docs/docs/02-guide/05-loan/04-approve.md delete mode 100644 docs/docs/02-guide/05-loan/05-repay.md delete mode 100644 docs/docs/02-guide/05-loan/06-liquidate.md delete mode 100644 docs/docs/02-guide/05-loan/07-cancel.md delete mode 100644 docs/docs/02-guide/05-loan/08-play.md delete mode 100644 docs/docs/02-guide/05-loan/_category_.json diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md new file mode 100644 index 0000000000..3507e07e40 --- /dev/null +++ b/docs/docs/02-guide/05-loan.md @@ -0,0 +1,448 @@ +# DeFi Loan + +## Introduction + +Decentralized finance (DeFi) is a rapidly growing sector of the blockchain ecosystem that is transforming the way we think about financial instruments and services. DeFi offers a wide range of innovative financial products and services, including lending, borrowing, spot trading, margin trading, and flash loans, that are accessible to anyone with an internet connection and a digital wallet. + +A DeFi loan represents a financial contract in which the borrower receives a specific asset, such as currency or digital tokens. +In return, you agree to pay an additional fee and repay the loan within a set period of time. +To secure a loan, the borrower provides collateral that the lender can claim in the event of default. + +## Setup and Scaffold + +1. **Create a New Blockchain:** + +```bash +ignite scaffold chain loan --no-module && cd loan +``` + +2. **Create a Module:** + +Create a module with a dependency on the standard Cosmos SDK `bank` module. + +```bash +ignite scaffold module loan --dep bank +``` + +3. **Define the loan Module:** + +The "list" scaffolding command is used to generate files that implement the logic for storing and interacting with data stored as a list in the blockchain state. + +```bash +ignite scaffold list loan amount fee collateral deadline state borrower lender --no-message +``` + +4. **Scaffold the Messages:** + +Scaffold the code for handling the messages for requesting, approving, repaying, liquidating, and cancelling loans. + +- Handling Loan Requests + +```bash +ignite scaffold message request-loan amount fee collateral deadline +``` + +- Approving and Liquidating Loans + +```bash +ignite scaffold message approve-loan id:uint +``` + +```bash +ignite scaffold message repay-loan id:uint +``` + +- Repaying and Canceling Loans + +```bash +ignite scaffold message liquidate-loan id:uint +``` + +```bash +ignite scaffold message cancel-loan id:uint +``` + +## Additional Features + +- **Extend the BankKeeper:** + +Ignite takes care of adding the `bank` keeper, but you still need to tell the loan module which bank methods you will be using. You will be using three methods: `SendCoins`, `SendCoinsFromAccountToModule`, and `SendCoinsFromModuleToAccount`. +Add these to the `Bankkeeper` interface. + +```go title="x/loan/types/expected_keepers.go" +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error +} +``` + +- **Implement basic Validation to ensure proper loan requests:** + +When a loan is created, a certain message input validation is required. You want to throw error messages in case the end user tries impossible inputs. + +```go title="x/loan/types/message_request_loan.go" +package types + +import ( + "strconv" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +func (msg *MsgRequestLoan) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + amount, _ := sdk.ParseCoinsNormalized(msg.Amount) + if !amount.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is not a valid Coins object") + } + if amount.Empty() { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is empty") + } + fee, _ := sdk.ParseCoinsNormalized(msg.Fee) + if !fee.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "fee is not a valid Coins object") + } + deadline, err := strconv.ParseInt(msg.Deadline, 10, 64) + if err != nil { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline is not an integer") + } + if deadline <= 0 { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline should be a positive integer") + } + collateral, _ := sdk.ParseCoinsNormalized(msg.Collateral) + if !collateral.IsValid() { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is not a valid Coins object") + } + if collateral.Empty() { + return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is empty") + } + return nil +} +``` + +## Using the Platform + +1. **As a Borrower:** + +Implement `RequestLoan` keeper method that will be called whenever a user requests a loan. `RequestLoan` creates a new loan; Set terms like amount, fee, collateral, and repayment deadline. The collateral from the borrower's account is sent to a module account, and adds the loan to the blockchain's store. + +```go title="x/loan/keeper/msg_server_request_loan.go" +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "loan/x/loan/types" +) + +func (k msgServer) RequestLoan(goCtx context.Context, msg *types.MsgRequestLoan) (*types.MsgRequestLoanResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + var loan = types.Loan{ + Amount: msg.Amount, + Fee: msg.Fee, + Collateral: msg.Collateral, + Deadline: msg.Deadline, + State: "requested", + Borrower: msg.Creator, + } + borrower, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + collateral, err := sdk.ParseCoinsNormalized(loan.Collateral) + if err != nil { + panic(err) + } + sdkError := k.bankKeeper.SendCoinsFromAccountToModule(ctx, borrower, types.ModuleName, collateral) + if sdkError != nil { + return nil, sdkError + } + k.AppendLoan(ctx, loan) + return &types.MsgRequestLoanResponse{}, nil +} +``` + +As a borrower, you have the option to cancel a loan you have created if you no longer want to proceed with it. However, this action is only possible if the loan's current status is marked as "requested". + +```go title="x/loan/keeper/msg_server_cancel_loan.go" +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "loan/x/loan/types" +) + +func (k msgServer) CancelLoan(goCtx context.Context, msg *types.MsgCancelLoan) (*types.MsgCancelLoanResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + loan, found := k.GetLoan(ctx, msg.Id) + if !found { + return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) + } + if loan.Borrower != msg.Creator { + return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot cancel: not the borrower") + } + if loan.State != "requested" { + return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) + } + borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) + collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) + err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) + if err != nil { + return nil, err + } + loan.State = "cancelled" + k.SetLoan(ctx, loan) + return &types.MsgCancelLoanResponse{}, nil +} +``` + +2. **As a Lender:** + +Approve loan requests and liquidate loans if borrowers fail to repay. + +```go title="x/loan/keeper/msg_server_approve_loan.go" +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "loan/x/loan/types" +) + +func (k msgServer) ApproveLoan(goCtx context.Context, msg *types.MsgApproveLoan) (*types.MsgApproveLoanResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + loan, found := k.GetLoan(ctx, msg.Id) + if !found { + return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) + } + if loan.State != "requested" { + return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) + } + lender, _ := sdk.AccAddressFromBech32(msg.Creator) + borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) + amount, err := sdk.ParseCoinsNormalized(loan.Amount) + if err != nil { + return nil, errorsmod.Wrap(types.ErrWrongLoanState, "Cannot parse coins in loan amount") + } + err = k.bankKeeper.SendCoins(ctx, lender, borrower, amount) + if err != nil { + return nil, err + } + loan.Lender = msg.Creator + loan.State = "approved" + k.SetLoan(ctx, loan) + return &types.MsgApproveLoanResponse{}, nil +} +``` + +```go title="x/loan/keeper/msg_server_repay_loan.go" +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "loan/x/loan/types" +) + +func (k msgServer) RepayLoan(goCtx context.Context, msg *types.MsgRepayLoan) (*types.MsgRepayLoanResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + loan, found := k.GetLoan(ctx, msg.Id) + if !found { + return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) + } + if loan.State != "approved" { + return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) + } + lender, _ := sdk.AccAddressFromBech32(loan.Lender) + borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) + if msg.Creator != loan.Borrower { + return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot repay: not the borrower") + } + amount, _ := sdk.ParseCoinsNormalized(loan.Amount) + fee, _ := sdk.ParseCoinsNormalized(loan.Fee) + collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) + err := k.bankKeeper.SendCoins(ctx, borrower, lender, amount) + if err != nil { + return nil, err + } + err = k.bankKeeper.SendCoins(ctx, borrower, lender, fee) + if err != nil { + return nil, err + } + err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) + if err != nil { + return nil, err + } + loan.State = "repayed" + k.SetLoan(ctx, loan) + return &types.MsgRepayLoanResponse{}, nil +} +``` + +```go title="x/loan/keeper/msg_server_liquidate_loan.go" +package keeper + +import ( + "context" + "strconv" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "loan/x/loan/types" +) + +func (k msgServer) LiquidateLoan(goCtx context.Context, msg *types.MsgLiquidateLoan) (*types.MsgLiquidateLoanResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + loan, found := k.GetLoan(ctx, msg.Id) + if !found { + return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) + } + if loan.Lender != msg.Creator { + return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot liquidate: not the lender") + } + if loan.State != "approved" { + return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) + } + lender, _ := sdk.AccAddressFromBech32(loan.Lender) + collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) + deadline, err := strconv.ParseInt(loan.Deadline, 10, 64) + if err != nil { + panic(err) + } + if ctx.BlockHeight() < deadline { + return nil, errorsmod.Wrap(types.ErrDeadline, "Cannot liquidate before deadline") + } + err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, lender, collateral) + if err != nil { + return nil, err + } + loan.State = "liquidated" + k.SetLoan(ctx, loan) + return &types.MsgLiquidateLoanResponse{}, nil +} +``` + +Add the custom errors `ErrWrongLoanState` and `ErrDeadline`: + +```go title="x/loan/types/errors.go" +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + ErrWrongLoanState = sdkerrors.Register(ModuleName, 2, "wrong loan state") + ErrDeadline = sdkerrors.Register(ModuleName, 3, "deadline") +) +``` + +## Testing the Application + +- **Add Test Tokens:** + +Configure config.yml to add tokens (e.g., 10000foocoin) to test accounts. + +```bash title="config.yml" +version: 1 +accounts: + - name: alice + coins: + - 20000token + - 10000foocoin + - 200000000stake + - name: bob + coins: + - 10000token + - 100000000stake +client: + openapi: + path: docs/static/openapi.yml +faucet: + name: bob + coins: + - 5token + - 100000stake +validators: + - name: alice + bonded: 100000000stake +``` + +- **Start the Blockchain:** + +```bash +ignite chain serve +``` + +- **Request a loan:** + +In a new terminal window, request a loan of `1000token` with `100token` as a fee and `1000foocoin` as a collateral from Alice's account. The deadline is set to `500` blocks: + +```bash +loand tx loan request-loan 1000token 100token 1000foocoin 500 --from alice --chain-id loan +``` + +- **Approve the loan:** + +```bash +loand tx loan approve-loan 0 --from bob --chain-id loan +``` + +- **Repay a loan:** + +```bash +loand tx loan repay-loan 0 --from alice --chain-id loan +``` + +- **Liquidate a loan:** + +```bash +loand tx loan request-loan 1000token 100token 1000foocoin 20 --from alice --chain-id loan -y +loand tx loan approve-loan 1 --from bob --chain-id loan -y +loand tx loan liquidate-loan 1 --from bob --chain-id loan -y +``` + +At any state in the process, use `q list loan` to see the active state of all loans. + +```bash +loand q loan list-loan +``` + +Query the blockchain for balances to confirm they have changed according to your transactions. + +```bash +loand q bank balances $(loand keys show alice -a) +``` + +## Conclusion +This tutorial outlines the process of setting up a decentralized platform for digital asset loans using blockchain technology. By following these steps, you can create a DeFi platform that allows users to engage in secure and transparent lending and borrowing activities. diff --git a/docs/docs/02-guide/05-loan/00-intro.md b/docs/docs/02-guide/05-loan/00-intro.md deleted file mode 100644 index 9c3380bcd9..0000000000 --- a/docs/docs/02-guide/05-loan/00-intro.md +++ /dev/null @@ -1,86 +0,0 @@ -# DeFi Loan - -Decentralized finance (DeFi) is a rapidly growing sector of the blockchain -ecosystem that is transforming the way we think about financial instruments and -services. DeFi offers a wide range of innovative financial products and -services, including lending, borrowing, spot trading, margin trading, and flash -loans, that are accessible to anyone with an internet connection and a digital -wallet. - -One of the key benefits of DeFi is that it allows end users to access financial -instruments and services quickly and easily, without the need for complex -onboarding processes or the submission of personal documents such as passports -or background checks. This makes DeFi an attractive alternative to traditional -banking systems, which can be slow, costly, and inconvenient. - -In this tutorial, you will learn how to create a DeFi platform that enables -users to lend and borrow digital assets from each other. The platform you will -build will be powered by a blockchain, which provides a decentralized and -immutable record of all transactions. This ensures that the platform is -transparent, secure, and resistant to fraud. - -A loan is a financial transaction in which one party, the borrower, receives a -certain amount of assets, such as money or digital tokens, and agrees to pay -back the loan amount plus a fee to the lender by a predetermined deadline. To -secure the loan, the borrower provides collateral, which may be seized by the -lender if the borrower fails to pay back the loan as agreed. - -A loan has several properties that define its terms and conditions. - -The `id` is a unique identifier that is used to identify the loan on a -blockchain. - -The `amount` is the amount of assets that are being lent to the borrower. - -The `fee` is the cost that the borrower must pay to the lender for the loan. - -The `collateral` is the asset or assets that the borrower provides to the lender -as security for the loan. - -The `deadline` is the date by which the borrower must pay back the loan. If the -borrower fails to pay back the loan by the deadline, the lender may choose to -liquidate the loan and seize the collateral. - -The `state` of a loan describes the current status of the loan and can take on -several values, such as `requested`, `approved`, `paid`, `cancelled`, or -`liquidated`. A loan is in the `requested` state when the borrower first submits -a request for the loan. If the lender approves the request, the loan moves to -the `approved` state. When the borrower repays the loan, the loan moves to the -`paid` state. If the borrower cancels the loan before it is approved, the loan -moves to the `cancelled` state. If the borrower is unable to pay back the loan -by the deadline, the lender may choose to liquidate the loan and seize the -collateral. In this case, the loan moves to the `liquidated` state. - -In a loan transaction, there are two parties involved: the borrower and the -lender. The borrower is the party that requests the loan and agrees to pay back -the loan amount plus a fee to the lender by a predetermined deadline. The lender -is the party that approves the loan request and provides the borrower with the -loan amount. - -As a borrower, you should be able to perform several actions on the loan -platform. These actions may include: - -* requesting a loan, -* canceling a loan, -* repaying a loan. - -Requesting a loan allows you to specify the terms and conditions of the loan, -including the amount, the fee, the collateral, and the deadline for repayment. -If you cancel a loan, you can withdraw your request for the loan before it is -approved or funded. Repaying a loan allows you to pay back the loan amount plus -the fee to the lender in accordance with the loan terms. - -As a lender, you should be able to perform two actions on the platform: - -* approving a loan -* liquidating a loan. - -Approving a loan allows you to accept the terms and conditions of the loan and -send the loan amount to the borrower. Liquidating a loan allows the lender to -seize the collateral if you are unable to pay back the loan by the deadline. - -By performing these actions, lenders and borrowers can interact with each other -and facilitate the lending and borrowing of digital assets on the platform. The -platform enables users to access financial instruments and services that allow -them to manage their assets and achieve their financial goals in a secure and -transparent manner. \ No newline at end of file diff --git a/docs/docs/02-guide/05-loan/01-init.md b/docs/docs/02-guide/05-loan/01-init.md deleted file mode 100644 index 9a979da953..0000000000 --- a/docs/docs/02-guide/05-loan/01-init.md +++ /dev/null @@ -1,72 +0,0 @@ -# Creating a structure of the application - -To create a structure for a blockchain application that enables users to lend -and borrow digital assets from each other, use the Ignite CLI to generate the -necessary code. - -First, create a new blockchain called `loan` by running the following command: - -``` -ignite scaffold chain loan --no-module -``` - -The `--no-module` flag tells Ignite not to create a default module. Instead, you -will create the module yourself in the next step. - -Next, change the directory to `loan/`: - -``` -cd loan -``` - -Create a module with a dependency on the standard Cosmos SDK `bank` module by -running the following command: - -``` -ignite scaffold module loan --dep bank -``` - -Create a `loan` model with a list of properties. - -``` -ignite scaffold list loan amount fee collateral deadline state borrower lender --no-message -``` - -The `--no-message` flag tells Ignite not to generate Cosmos SDK messages for -creating, updating, and deleting loans. Instead, you will generate the code for -custom messages. - - -To generate the code for handling the messages for requesting, approving, -repaying, liquidating, and cancelling loans, run the following commands: - -``` -ignite scaffold message request-loan amount fee collateral deadline -``` - -``` -ignite scaffold message approve-loan id:uint -``` - -``` -ignite scaffold message repay-loan id:uint -``` - -``` -ignite scaffold message liquidate-loan id:uint -``` - -``` -ignite scaffold message cancel-loan id:uint -``` - -Great job! By using a few simple commands with Ignite CLI, you have successfully -set up the foundation for your blockchain application. You have created a loan -model and included keeper methods to allow interaction with the store. In -addition, you have also implemented message handlers for five custom messages. - -Now that the basic structure is in place, it's time to move on to the next phase -of development. In the coming sections, you will be focusing on implementing the -business logic within the message handlers you have created. This will involve -writing code to define the specific actions and processes that should be carried -out when each message is received. \ No newline at end of file diff --git a/docs/docs/02-guide/05-loan/02-bank.md b/docs/docs/02-guide/05-loan/02-bank.md deleted file mode 100644 index bdc2c726ca..0000000000 --- a/docs/docs/02-guide/05-loan/02-bank.md +++ /dev/null @@ -1,32 +0,0 @@ -# Importing methods from the Bank keeper - -In the previous step you have created the `loan` module with `ignite scaffold -module` using `--dep bank`. This command created a new module and added the -`bank` keeper to the `loan` module, which allows you to add and use bank's -keeper methods in loan's keeper methods. - -To see the changes made by `--dep bank`, review the following files: -`x/loan/keeper/keeper.go` and `x/loan/module.go`. - -Ignite takes care of adding the `bank` keeper, but you still need to tell the -`loan` module which `bank` methods you will be using. You will be using three -methods: `SendCoins`, `SendCoinsFromAccountToModule`, and -`SendCoinsFromModuleToAccount`. You can do that by adding method signatures to -the `BankKeeper` interface: - -```go title="x/loan/types/expected_keepers.go" -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - // highlight-start - SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error - // highlight-end -} -``` diff --git a/docs/docs/02-guide/05-loan/03-request.md b/docs/docs/02-guide/05-loan/03-request.md deleted file mode 100644 index d540ab557c..0000000000 --- a/docs/docs/02-guide/05-loan/03-request.md +++ /dev/null @@ -1,118 +0,0 @@ -# Request a loan - -Implement `RequestLoan` keeper method that will be called whenever a user -requests a loan. `RequestLoan` creates a new loan with the provided data, sends -the collateral from the borrower's account to a module account, and adds the -loan to the blockchain's store. - -## Keeper method - -```go title="x/loan/keeper/msg_server_request_loan.go" -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "loan/x/loan/types" -) - -func (k msgServer) RequestLoan(goCtx context.Context, msg *types.MsgRequestLoan) (*types.MsgRequestLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - var loan = types.Loan{ - Amount: msg.Amount, - Fee: msg.Fee, - Collateral: msg.Collateral, - Deadline: msg.Deadline, - State: "requested", - Borrower: msg.Creator, - } - borrower, err := sdk.AccAddressFromBech32(msg.Creator) - if err != nil { - panic(err) - } - collateral, err := sdk.ParseCoinsNormalized(loan.Collateral) - if err != nil { - panic(err) - } - sdkError := k.bankKeeper.SendCoinsFromAccountToModule(ctx, borrower, types.ModuleName, collateral) - if sdkError != nil { - return nil, sdkError - } - k.AppendLoan(ctx, loan) - return &types.MsgRequestLoanResponse{}, nil -} -``` - -The function takes in two arguments: a `context.Context` object and a pointer to -a `types.MsgRequestLoan` struct. It returns a pointer to a -`types.MsgRequestLoanResponse` struct and an `error` object. - -The first thing the function does is create a new `types.Loan` struct with the -data from the input `types.MsgRequestLoan` struct. It sets the `State` field of -`the types.Loan` struct to "requested". - -Next, the function gets the borrower's address from the `msg.Creator` field of -the input `types.MsgRequestLoan` struct. It then parses the `loan.Collateral` -field (which is a string) into `sdk.Coins` using the `sdk.ParseCoinsNormalized` -function. - -The function then sends the collateral from the borrower's account to a module -account using the `k.bankKeeper.SendCoinsFromAccountToModule` function. Finally, -it adds the new loan to a keeper using the `k.AppendLoan` function. The function -returns a `types.MsgRequestLoanResponse` struct and a `nil` error if all goes -well. - -## Basic message validation - -When a loan is created, a certain message input validation is required. You want -to throw error messages in case the end user tries impossible inputs. - -```go title="x/loan/types/message_request_loan.go" -package types - -import ( - // highlight-next-line - "strconv" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -func (msg *MsgRequestLoan) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(msg.Creator) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) - } - // highlight-start - amount, _ := sdk.ParseCoinsNormalized(msg.Amount) - if !amount.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is not a valid Coins object") - } - if amount.Empty() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is empty") - } - fee, _ := sdk.ParseCoinsNormalized(msg.Fee) - if !fee.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "fee is not a valid Coins object") - } - deadline, err := strconv.ParseInt(msg.Deadline, 10, 64) - if err != nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline is not an integer") - } - if deadline <= 0 { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline should be a positive integer") - } - collateral, _ := sdk.ParseCoinsNormalized(msg.Collateral) - if !collateral.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is not a valid Coins object") - } - if collateral.Empty() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is empty") - } - // highlight-end - return nil -} -``` diff --git a/docs/docs/02-guide/05-loan/04-approve.md b/docs/docs/02-guide/05-loan/04-approve.md deleted file mode 100644 index d1ff0d5d5d..0000000000 --- a/docs/docs/02-guide/05-loan/04-approve.md +++ /dev/null @@ -1,98 +0,0 @@ -# Approve a loan - -After a loan request has been made, it is possible for another account to -approve the loan and accept the terms proposed by the borrower. This process -involves the transfer of the requested funds from the lender to the borrower. - -To be eligible for approval, a loan must have a status of "requested." This -means that the borrower has made a request for a loan and is waiting for a -lender to agree to the terms and provide the funds. Once a lender has decided to -approve the loan, they can initiate the transfer of the funds to the borrower. - -Upon loan approval, the status of the loan is changed to "approved." This -signifies that the funds have been successfully transferred and that the loan -agreement is now in effect. - -## Keeper method - -```go title="x/loan/keeper/msg_server_approve_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) ApproveLoan(goCtx context.Context, msg *types.MsgApproveLoan) (*types.MsgApproveLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.State != "requested" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(msg.Creator) - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - amount, err := sdk.ParseCoinsNormalized(loan.Amount) - if err != nil { - return nil, errorsmod.Wrap(types.ErrWrongLoanState, "Cannot parse coins in loan amount") - } - err = k.bankKeeper.SendCoins(ctx, lender, borrower, amount) - if err != nil { - return nil, err - } - loan.Lender = msg.Creator - loan.State = "approved" - k.SetLoan(ctx, loan) - return &types.MsgApproveLoanResponse{}, nil -} -``` - -`ApproveLoan` takes a context and a message of type `*types.MsgApproveLoan` as -input, and returns a pointer to a `types.MsgApproveLoanResponse` and an `error`. - -The function first retrieves a loan object by calling `k.GetLoan(ctx, msg.Id)`, -where `ctx` is a context object, `k` is the `msgServer` object, `GetLoan` is a -method on `k`, and `msg.Id` is a field of the msg object passed as an argument. -If the loan is not found, it returns `nil` and an error wrapped with -`sdkerrors.ErrKeyNotFound`. - -Next, the function checks if the loan's state is `"requested"`. If it is not, it -returns `nil` and an error wrapped with `types.ErrWrongLoanState`. - -If the loan's state is `"requested"`, the function parses the addresses of the -lender and borrower from bech32 strings, and then parses the `amount` of the -loan from a string. If there is an error parsing the coins in the loan amount, -it returns `nil` and an error wrapped with `types.ErrWrongLoanState`. - -Otherwise, the function calls the `SendCoins` method on the `k.bankKeeper` -object, passing it the context, the lender and borrower addresses, and the -amount of the loan. It then updates the lender field of the loan object and sets -its state to `"approved"`. Finally, it stores the updated loan object by calling -`k.SetLoan(ctx, loan)`. - -At the end, the function returns a `types.MsgApproveLoanResponse` object and -`nil` for the error. - -## Register a custom error - -To register the custom error `ErrWrongLoanState` that is used in the -`ApproveLoan` function, modify the "errors.go" file: - -```go title="x/loan/types/errors.go" -package types - -import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -var ( - ErrWrongLoanState = sdkerrors.Register(ModuleName, 2, "wrong loan state") -) -``` diff --git a/docs/docs/02-guide/05-loan/05-repay.md b/docs/docs/02-guide/05-loan/05-repay.md deleted file mode 100644 index 232327d349..0000000000 --- a/docs/docs/02-guide/05-loan/05-repay.md +++ /dev/null @@ -1,98 +0,0 @@ -# Repay a loan - -The `RepayLoan` method is responsible for handling the repayment of a loan. This -involves transferring the borrowed funds, along with any agreed upon fees, from -the borrower to the lender. In addition, the collateral that was provided as -part of the loan agreement will be released from the escrow account and returned -to the borrower. - -It is important to note that the `RepayLoan` method can only be called under -certain conditions. Firstly, the transaction must be signed by the borrower of -the loan. This ensures that only the borrower has the ability to initiate the -repayment process. Secondly, the loan must be in an approved status. This means -that the loan has received approval and is ready to be repaid. - -To implement the `RepayLoan` method, we must ensure that these conditions are -met before proceeding with the repayment process. Once the necessary checks have -been performed, the method can then handle the transfer of funds and the release -of the collateral from the escrow account. - -## Keeper method - -```go title="x/loan/keeper/msg_server_repay_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) RepayLoan(goCtx context.Context, msg *types.MsgRepayLoan) (*types.MsgRepayLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.State != "approved" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(loan.Lender) - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - if msg.Creator != loan.Borrower { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot repay: not the borrower") - } - amount, _ := sdk.ParseCoinsNormalized(loan.Amount) - fee, _ := sdk.ParseCoinsNormalized(loan.Fee) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - err := k.bankKeeper.SendCoins(ctx, borrower, lender, amount) - if err != nil { - return nil, err - } - err = k.bankKeeper.SendCoins(ctx, borrower, lender, fee) - if err != nil { - return nil, err - } - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) - if err != nil { - return nil, err - } - loan.State = "repayed" - k.SetLoan(ctx, loan) - return &types.MsgRepayLoanResponse{}, nil -} -``` - -`RepayLoan` takes in two arguments: a context and a pointer to a -`types.MsgRepayLoan` type. It returns a pointer to a -`types.MsgRepayLoanResponse` type and an `error`. - -The method first retrieves a loan from storage by passing the provided loan ID -to the `k.GetLoan` method. If the loan cannot be found, the method returns an -error wrapped in a `sdkerrors.ErrKeyNotFound` error. - -The method then checks that the state of the loan is "approved". If it is not, -the method returns an error wrapped in a `types.ErrWrongLoanState` error. - -Next, the method converts the lender and borrower addresses stored in the loan -struct to `sdk.AccAddress` types using the `sdk.AccAddressFromBech32` function. -It then checks that the transaction is signed by the borrower of the loan by -comparing the `msg.Creator` field to the borrower address stored in the loan -struct. If these do not match, the method returns an error wrapped in a -`sdkerrors.ErrUnauthorized` error. - -The method then parses the loan amount, fee, and collateral stored in the loan -struct as `sdk.Coins` using the `sdk.ParseCoinsNormalized` function. It then -uses the `k.bankKeeper.SendCoins` function to transfer the loan amount and fee -from the borrower to the lender. It then uses the -`k.bankKeeper.SendCoinsFromModuleToAccount` function to transfer the collateral -from the escrow account to the borrower. - -Finally, the method updates the state of the loan to "repayed" and stores the -updated loan in storage using the `k.SetLoan` method. The method returns a -`types.MsgRepayLoanResponse` and a `nil` error to indicate that the repayment -process was successful. diff --git a/docs/docs/02-guide/05-loan/06-liquidate.md b/docs/docs/02-guide/05-loan/06-liquidate.md deleted file mode 100644 index 419cc740b1..0000000000 --- a/docs/docs/02-guide/05-loan/06-liquidate.md +++ /dev/null @@ -1,90 +0,0 @@ -# Liquidate loan - -The `LiquidateLoan` method is a function that allows the lender to sell off the -collateral belonging to the borrower in the event that the borrower has failed -to repay the loan by the specified deadline. This process is known as -"liquidation" and is typically carried out as a way for the lender to recoup -their losses in the event that the borrower is unable to fulfill their repayment -obligations. - -During the liquidation process, the collateral tokens that have been pledged by -the borrower as security for the loan are transferred from the borrower's -account to the lender's account. This transfer is initiated by the lender and is -typically triggered when the borrower fails to repay the loan by the agreed upon -deadline. Once the collateral has been transferred, the lender can then sell it -off in order to recoup their losses and compensate for the unpaid loan. - -## Keeper method - -```go title="x/loan/keeper/msg_server_liquidate_loan.go" -package keeper - -import ( - "context" - "strconv" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) LiquidateLoan(goCtx context.Context, msg *types.MsgLiquidateLoan) (*types.MsgLiquidateLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.Lender != msg.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot liquidate: not the lender") - } - if loan.State != "approved" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(loan.Lender) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - deadline, err := strconv.ParseInt(loan.Deadline, 10, 64) - if err != nil { - panic(err) - } - if ctx.BlockHeight() < deadline { - return nil, errorsmod.Wrap(types.ErrDeadline, "Cannot liquidate before deadline") - } - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, lender, collateral) - if err != nil { - return nil, err - } - loan.State = "liquidated" - k.SetLoan(ctx, loan) - return &types.MsgLiquidateLoanResponse{}, nil -} -``` - -`LiquidateLoan` takes in a context and a `types.MsgLiquidateLoan` message as input and returns a types.MsgLiquidateLoanResponse message and an error as output. - -The function first retrieves a loan using the `GetLoan` method and the `Id` field of the input message. If the loan is not found, it returns an error using the `errorsmod.Wrap` function and the `sdkerrors.ErrKeyNotFound` error code. - -Next, the function checks that the `Creator` field of the input message is the same as the `Lender` field of the loan. If they are not the same, it returns an error using the `errorsmod.Wrap` function and the `sdkerrors.ErrUnauthorized` error code. - -The function then checks that the State field of the loan is equal to "approved". If it is not, it returns an error using the `errorsmod.Wrapf` function and the `types.ErrWrongLoanState` error code. - -The function then converts the Lender field of the loan to an address using the `sdk.AccAddressFromBech32` function and the `Collateral` field to coins using the `sdk.ParseCoinsNormalized` function. It also converts the `Deadline` field to an integer using the `strconv.ParseInt` function. If this function returns an error, it panics. - -Finally, the function checks that the current block height is greater than or equal to the deadline. If it is not, it returns an error using the `errorsmod.Wrap` function and the `types.ErrDeadline` error code. If all checks pass, the function uses the `bankKeeper.SendCoinsFromModuleToAccount` method to transfer the collateral from the module account to the lender's account and updates the `State` field of the loan to `"liquidated"`. It then stores the updated loan using the `SetLoan` method and returns a `types.MsgLiquidateLoanResponse` message with no error. - -## Register a custom error - -```go title="x/loan/types/errors.go" -package types - -import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -var ( - ErrWrongLoanState = sdkerrors.Register(ModuleName, 2, "wrong loan state") - // highlight-next-line - ErrDeadline = sdkerrors.Register(ModuleName, 3, "deadline") -) -``` diff --git a/docs/docs/02-guide/05-loan/07-cancel.md b/docs/docs/02-guide/05-loan/07-cancel.md deleted file mode 100644 index 9415fe6ada..0000000000 --- a/docs/docs/02-guide/05-loan/07-cancel.md +++ /dev/null @@ -1,74 +0,0 @@ -# Cancel a loan - -As a borrower, you have the option to cancel a loan you have created if you no -longer want to proceed with it. However, this action is only possible if the -loan's current status is marked as "requested". - -If you decide to cancel the loan, the collateral tokens that were being held as -security for the loan will be transferred back to your account from the module -account. This means that you will regain possession of the collateral tokens you -had originally put up for the loan. - -```go title="x/loan/keeper/msg_server_cancel_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) CancelLoan(goCtx context.Context, msg *types.MsgCancelLoan) (*types.MsgCancelLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.Borrower != msg.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot cancel: not the borrower") - } - if loan.State != "requested" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) - if err != nil { - return nil, err - } - loan.State = "cancelled" - k.SetLoan(ctx, loan) - return &types.MsgCancelLoanResponse{}, nil -} -``` - -`CancelLoan` takes in two arguments: a `context.Context` named `goCtx` and a -pointer to a `types.MsgCancelLoan` named `msg`. It returns a pointer to a -`types.MsgCancelLoanResponse` and an error. - -The function begins by using the `sdk.UnwrapSDKContext` method to get the -`sdk.Context` from the `context.Context` object. It then uses the `GetLoan` -method of the `msgServer` type to retrieve a loan identified by the `Id` field -of the `msg` argument. If the loan is not found, the function returns an error -using the `sdk.ErrKeyNotFound` error wrapped with the `errorsmod.Wrap` method. - -Next, the function checks if the `Creator` field of the msg argument is the same -as the `Borrower` field of the loan. If they are not the same, the function -returns an error using the `sdk.ErrUnauthorized` error wrapped with the -`errorsmod.Wrap` method. - -The function then checks if the `State` field of the loan is equal to the string -`"requested"`. If it is not, the function returns an error using the -types.`ErrWrongLoanState` error wrapped with the `errorsmod.Wrapf` method. - -If the loan has the correct state and the creator of the message is the borrower -of the loan, the function proceeds to send the collateral coins held in the -`Collateral` field of the loan back to the borrower's account using the -`SendCoinsFromModuleToAccount` method of the `bankKeeper`. The function then -updates the State field of the loan to the string "cancelled" and sets the -updated loan using the `SetLoan` method. Finally, the function returns a -`types.MsgCancelLoanResponse` object and a nil error. diff --git a/docs/docs/02-guide/05-loan/08-play.md b/docs/docs/02-guide/05-loan/08-play.md deleted file mode 100644 index 40ab5aabf7..0000000000 --- a/docs/docs/02-guide/05-loan/08-play.md +++ /dev/null @@ -1,318 +0,0 @@ -# Play - -Add `10000foocoin` to Alice's account. These tokens will be used as a loan -collateral. - -```yml title="config.yml" -version: 1 -accounts: - - name: alice - coins: - - 20000token - # highlight-next-line - - 10000foocoin - - 200000000stake - - name: bob - coins: - - 10000token - - 100000000stake -client: - openapi: - path: docs/static/openapi.yml -faucet: - name: bob - coins: - - 5token - - 100000stake -validators: - - name: alice - bonded: 100000000stake -``` - -Start a blockchain node: - -``` -ignite chain serve -``` - -## Repaying a loan - -Request a loan of `1000token` with `100token` as a fee and `1000foocoin` as a -collateral from Alice's account. The deadline is set to `500` blocks: - -``` -loand tx loan request-loan 1000token 100token 1000foocoin 500 --from alice --chain-id loan -``` - -``` -loand q loan list-loan -``` - -```yml -Loan: -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "500" - fee: 100token - id: "0" - lender: "" - state: requested -``` - -Please be aware that the addresses displayed in your terminal window (such as those in the `borrower` field) will not match the ones provided in this tutorial. This is because Ignite generates new accounts each time a chain is started, unless an account has a mnemonic specified in the `config.yml` file. - -Approve the loan from Bob's account: - -``` -loand tx loan approve-loan 0 --from bob --chain-id loan -``` - -``` -loand q loan list-loan -``` - -The `lender` field has been updated to Bob's address and the `state` field has -been updated to `approved`: - -```yml -Loan: -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "500" - fee: 100token - id: "0" - # highlight-start - lender: cosmos1qfzpxfhsu2qfy2exkukuanrkzrrexh9yeg2pr4 - state: approved - # highlight-end -``` - -``` -loand q bank balances $(loand keys show alice -a) -``` - -The `foocoin` balance has been updated to `9000`, because `1000foocoin` has been -transferred as collateral to the module account. The `token` balance has been -updated to `21000`, because `1000token` has been transferred from Bob's account -to Alice's account as a loan: - -```yml -balances: - # highlight-start -- amount: "9000" - denom: foocoin - # highlight-end -- amount: "100000000" - denom: stake - # highlight-start -- amount: "21000" - denom: token - # highlight-end -``` - -``` -loand q bank balances $(loand keys show bob -a) -``` - -The `token` balance has been updated to `9000`, because `1000token` has been -transferred from Bob's account to Alice's account as a loan: - -```yml -balances: -- amount: "100000000" - denom: stake - # highlight-start -- amount: "9000" - denom: token - # highlight-end -``` - -Repay the loan from Alice's account: - -``` -loand tx loan repay-loan 0 --from alice --chain-id loan -``` - -``` -loand q loan list-loan -``` - -The `state` field has been updated to `repayed`: - -```yml -Loan: -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "500" - fee: 100token - id: "0" - lender: cosmos1qfzpxfhsu2qfy2exkukuanrkzrrexh9yeg2pr4 - # highlight-next-line - state: repayed -``` - -``` -loand q bank balances $(loand keys show alice -a) -``` - -The `foocoin` balance has been updated to `10000`, because `1000foocoin` has -been transferred from the module account to Alice's account. The `token` balance -has been updated to `19900`, because `1000token` has been transferred from -Alice's account to Bob's account as a repayment and `100token` has been -transferred from Alice's account to Bob's account as a fee: - -```yml -balances: - # highlight-start -- amount: "10000" - denom: foocoin - # highlight-end -- amount: "100000000" - denom: stake - # highlight-start -- amount: "19900" - denom: token - # highlight-end -``` - -``` -loand q bank balances $(loand keys show bob -a) -``` - -The `token` balance has been updated to `10100`, because `1000token` has been -transferred from Alice's account to Bob's account as a repayment and `100token` -has been transferred from Alice's account to Bob's account as a fee: - -```yml -balances: -- amount: "100000000" - denom: stake - # highlight-start -- amount: "10100" - denom: token - # highlight-end -``` - -## Liquidating a loan - -Request a loan of `1000token` with `100token` as a fee and `1000foocoin` as a -collateral from Alice's account. The deadline is set to `20` blocks. The -deadline is set to a very small value, so that the loan can be quickly -liquidated in the next step: - -``` -loand tx loan request-loan 1000token 100token 1000foocoin 20 --from alice --chain-id loan -``` - -``` -loand q loan list-loan -``` - -A loan has been added to the list: - -```yml -Loan: -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "500" - fee: 100token - id: "0" - lender: cosmos1qfzpxfhsu2qfy2exkukuanrkzrrexh9yeg2pr4 - state: repayed - # highlight-start -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "20" - fee: 100token - id: "1" - lender: "" - state: requested - # highlight-end -``` - -Approve the loan from Bob's account: - -``` -loand tx loan approve-loan 1 --from bob --chain-id loan -``` - -Liquidate the loan from Bob's account: - -``` -loand tx loan liquidate-loan 1 --from bob --chain-id loan -``` - -``` -loand q loan list-loan -``` - -The loan has been liquidated: - -```yml -Loan: -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "500" - fee: 100token - id: "0" - lender: cosmos1qfzpxfhsu2qfy2exkukuanrkzrrexh9yeg2pr4 - state: repayed - # highlight-start -- amount: 1000token - borrower: cosmos153dk8qh56v4yg6e4uzrvvqjueu6d36fptlr2kw - collateral: 1000foocoin - deadline: "20" - fee: 100token - id: "1" - lender: cosmos1qfzpxfhsu2qfy2exkukuanrkzrrexh9yeg2pr4 - state: liquidated - # highlight-end -``` - -``` -loand q bank balances $(loand keys show alice -a) -``` - -The `foocoin` balance has been updated to `9000`, because `1000foocoin` has been -transferred from Alice's account to the module account as a collateral. Alice -has lost her collateral, but she has kept the loan amount: - -```yml -balances: - # highlight-start -- amount: "9000" - denom: foocoin - # highlight-end -- amount: "100000000" - denom: stake - # highlight-start -- amount: "20900" - denom: token - # highlight-end -``` - -``` -loand q bank balances $(loand keys show bob -a) -``` - -The `foocoin` balance has been updated to `1000`, because `1000foocoin` has been -transferred from the module account to Bob's account as a collateral. Bob has -gained the collateral, but he has lost the loan amount: - -```yml -balances: - # highlight-start -- amount: "1000" - denom: foocoin - # highlight-end -- amount: "100000000" - denom: stake -- amount: "9100" - denom: token -``` diff --git a/docs/docs/02-guide/05-loan/_category_.json b/docs/docs/02-guide/05-loan/_category_.json deleted file mode 100644 index 0a76ef93ff..0000000000 --- a/docs/docs/02-guide/05-loan/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Advanced Module: DeFi Loan", - "link": null -} \ No newline at end of file From 98c7271562e682aa42ea0dcd6f4e97ada5f8d7b6 Mon Sep 17 00:00:00 2001 From: toschdev <8368497+toschdev@users.noreply.github.com> Date: Thu, 23 Nov 2023 17:44:52 +0100 Subject: [PATCH 02/12] Order message scaffolding --- docs/docs/02-guide/05-loan.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 3507e07e40..fa0b3c5d3a 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -42,24 +42,24 @@ Scaffold the code for handling the messages for requesting, approving, repaying, ignite scaffold message request-loan amount fee collateral deadline ``` -- Approving and Liquidating Loans +- Approving and Canceling Loans ```bash ignite scaffold message approve-loan id:uint ``` ```bash -ignite scaffold message repay-loan id:uint +ignite scaffold message cancel-loan id:uint ``` -- Repaying and Canceling Loans +- Repaying and Liquidating Loans ```bash -ignite scaffold message liquidate-loan id:uint +ignite scaffold message repay-loan id:uint ``` ```bash -ignite scaffold message cancel-loan id:uint +ignite scaffold message liquidate-loan id:uint ``` ## Additional Features From cba477ec0542ac2f5d69584f8009a8b1c086ec81 Mon Sep 17 00:00:00 2001 From: toschdev <8368497+toschdev@users.noreply.github.com> Date: Fri, 24 Nov 2023 10:09:26 +0100 Subject: [PATCH 03/12] Fix BankKeeper error panic: can't resolve type --- docs/docs/02-guide/05-loan.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index fa0b3c5d3a..7a8ed2c698 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -67,6 +67,7 @@ ignite scaffold message liquidate-loan id:uint - **Extend the BankKeeper:** Ignite takes care of adding the `bank` keeper, but you still need to tell the loan module which bank methods you will be using. You will be using three methods: `SendCoins`, `SendCoinsFromAccountToModule`, and `SendCoinsFromModuleToAccount`. +Remove the `SpendableCoins` from the `BankKeeper`. Add these to the `Bankkeeper` interface. ```go title="x/loan/types/expected_keepers.go" @@ -77,7 +78,6 @@ import ( ) type BankKeeper interface { - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error @@ -89,8 +89,6 @@ type BankKeeper interface { When a loan is created, a certain message input validation is required. You want to throw error messages in case the end user tries impossible inputs. ```go title="x/loan/types/message_request_loan.go" -package types - import ( "strconv" @@ -358,7 +356,7 @@ Add the custom errors `ErrWrongLoanState` and `ErrDeadline`: package types import ( - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkerrors "cosmossdk.io/errors" ) var ( From 261d63bf8658033ec5913de84f4eb6333e43e279 Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:10:33 +0100 Subject: [PATCH 04/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 7a8ed2c698..65a59478a1 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -2,7 +2,7 @@ ## Introduction -Decentralized finance (DeFi) is a rapidly growing sector of the blockchain ecosystem that is transforming the way we think about financial instruments and services. DeFi offers a wide range of innovative financial products and services, including lending, borrowing, spot trading, margin trading, and flash loans, that are accessible to anyone with an internet connection and a digital wallet. +Decentralized finance (DeFi) is a rapidly growing sector that is transforming the way we think about financial instruments and provides an array of inventive financial products and services. These include lending, borrowing, spot trading, margin trading, and flash loans, all of which are available to anyone possessing an internet connection. A DeFi loan represents a financial contract in which the borrower receives a specific asset, such as currency or digital tokens. In return, you agree to pay an additional fee and repay the loan within a set period of time. From 6ed2aa87a35210c6b095527fcc3a5ce480e065ca Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:10:51 +0100 Subject: [PATCH 05/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 65a59478a1..7a56d2160c 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -4,7 +4,7 @@ Decentralized finance (DeFi) is a rapidly growing sector that is transforming the way we think about financial instruments and provides an array of inventive financial products and services. These include lending, borrowing, spot trading, margin trading, and flash loans, all of which are available to anyone possessing an internet connection. -A DeFi loan represents a financial contract in which the borrower receives a specific asset, such as currency or digital tokens. +A DeFi loan represents a financial contract where the borrower is granted a certain asset, like currency or digital tokens. In return, you agree to pay an additional fee and repay the loan within a set period of time. To secure a loan, the borrower provides collateral that the lender can claim in the event of default. From 41c355f8e43b912ab723890ace79d7ca05156ab2 Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:11:04 +0100 Subject: [PATCH 06/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 7a56d2160c..a87b3a3715 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -5,7 +5,7 @@ Decentralized finance (DeFi) is a rapidly growing sector that is transforming the way we think about financial instruments and provides an array of inventive financial products and services. These include lending, borrowing, spot trading, margin trading, and flash loans, all of which are available to anyone possessing an internet connection. A DeFi loan represents a financial contract where the borrower is granted a certain asset, like currency or digital tokens. -In return, you agree to pay an additional fee and repay the loan within a set period of time. +In return, the borrower agrees to pay an additional fee and repay the loan within a set period of time. To secure a loan, the borrower provides collateral that the lender can claim in the event of default. ## Setup and Scaffold From 0e6c48818f30f923a2c381811fc7af46d0bb4e6a Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:11:32 +0100 Subject: [PATCH 07/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index a87b3a3715..9e73611bc1 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -18,7 +18,7 @@ ignite scaffold chain loan --no-module && cd loan 2. **Create a Module:** -Create a module with a dependency on the standard Cosmos SDK `bank` module. +Create a new "loan" module that is based on the standard Cosmos SDK `bank` module. ```bash ignite scaffold module loan --dep bank From c0b3317f50163cd99deb4b2725955f6680634562 Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:46:18 +0100 Subject: [PATCH 08/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 9e73611bc1..8aa3b38f2d 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -68,6 +68,7 @@ ignite scaffold message liquidate-loan id:uint Ignite takes care of adding the `bank` keeper, but you still need to tell the loan module which bank methods you will be using. You will be using three methods: `SendCoins`, `SendCoinsFromAccountToModule`, and `SendCoinsFromModuleToAccount`. Remove the `SpendableCoins` from the `BankKeeper`. + Add these to the `Bankkeeper` interface. ```go title="x/loan/types/expected_keepers.go" From 5f60d0584b0609ede9affa9b1ef652f782d7f3a3 Mon Sep 17 00:00:00 2001 From: toschdev <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:04:32 +0100 Subject: [PATCH 09/12] Address feedback --- docs/docs/02-guide/05-loan.md | 43 +++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 8aa3b38f2d..8defb322a4 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -8,6 +8,17 @@ A DeFi loan represents a financial contract where the borrower is granted a cert In return, the borrower agrees to pay an additional fee and repay the loan within a set period of time. To secure a loan, the borrower provides collateral that the lender can claim in the event of default. +## You will learn + +In this tutorial you will learn how to: + +- **Scaffold a DeFi Module:** Learn how to use Ignite CLI to scaffold the basic structure of a DeFi module tailored for loan services. +- **Implement Loan Transactions:** Walk through coding the logic for initiating, managing, and closing loans. +- **Create Custom Tokens:** Understand how to create and manage custom tokens within your DeFi ecosystem, vital for lending and borrowing mechanisms. +- **Integrate Interest Rate Models:** Dive into implementing interest rate models to calculate loan interests dynamically. +- **Ensure Security and Compliance:** Focus on security, ensure your DeFi module is resistent to common vulnerabilites by validating inputs. +- **Test and Debug:** Learn effective strategies for testing your DeFi module and debugging issues that arise during development. + ## Setup and Scaffold 1. **Create a New Blockchain:** @@ -16,6 +27,8 @@ To secure a loan, the borrower provides collateral that the lender can claim in ignite scaffold chain loan --no-module && cd loan ``` +Notice the `--no-module` flag, in the next step we make sure the `bank` dependency is included with scaffolding the module. + 2. **Create a Module:** Create a new "loan" module that is based on the standard Cosmos SDK `bank` module. @@ -67,7 +80,7 @@ ignite scaffold message liquidate-loan id:uint - **Extend the BankKeeper:** Ignite takes care of adding the `bank` keeper, but you still need to tell the loan module which bank methods you will be using. You will be using three methods: `SendCoins`, `SendCoinsFromAccountToModule`, and `SendCoinsFromModuleToAccount`. -Remove the `SpendableCoins` from the `BankKeeper`. +Remove the `SpendableCoins` function from the `BankKeeper`. Add these to the `Bankkeeper` interface. @@ -75,13 +88,29 @@ Add these to the `Bankkeeper` interface. package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" ) +// AccountKeeper defines the expected interface for the Account module. +type AccountKeeper interface { + GetAccount(context.Context, sdk.AccAddress) sdk.AccountI // only used for simulation + // Methods imported from account should be defined here +} + +// BankKeeper defines the expected interface for the Bank module. type BankKeeper interface { - SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + // SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error +} + +// ParamSubspace defines the expected Subspace interface for parameters. +type ParamSubspace interface { + Get(context.Context, []byte, interface{}) + Set(context.Context, []byte, interface{}) } ``` @@ -138,6 +167,8 @@ func (msg *MsgRequestLoan) ValidateBasic() error { Implement `RequestLoan` keeper method that will be called whenever a user requests a loan. `RequestLoan` creates a new loan; Set terms like amount, fee, collateral, and repayment deadline. The collateral from the borrower's account is sent to a module account, and adds the loan to the blockchain's store. +Replace your scaffolded templates with the following code. + ```go title="x/loan/keeper/msg_server_request_loan.go" package keeper @@ -403,6 +434,8 @@ validators: ignite chain serve ``` +If everything works successful, you should see the `Blockchain is running` message in the Terminal. + - **Request a loan:** In a new terminal window, request a loan of `1000token` with `100token` as a fee and `1000foocoin` as a collateral from Alice's account. The deadline is set to `500` blocks: From 4be6df7db4f4ddabf1b29974021e4c659ce4b385 Mon Sep 17 00:00:00 2001 From: toschdev <8368497+toschdev@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:10:53 +0100 Subject: [PATCH 10/12] Add explanation to the validate basic function --- docs/docs/02-guide/05-loan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 8defb322a4..1ed8e63bc7 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -118,6 +118,8 @@ type ParamSubspace interface { When a loan is created, a certain message input validation is required. You want to throw error messages in case the end user tries impossible inputs. +The `ValidateBasic` function plays a crucial role in maintaining the security and compliance of loan input parameters. By implementing comprehensive input validations, you enhance the security of your application. It's important to rigorously verify all user inputs to ensure they align with the established standards and rules of your platform. + ```go title="x/loan/types/message_request_loan.go" import ( "strconv" From f09dabb9adaecd44bac5b6c1e70c2a6d72e80bb7 Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:26:13 +0100 Subject: [PATCH 11/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 1ed8e63bc7..5cf83bdc6c 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -8,7 +8,7 @@ A DeFi loan represents a financial contract where the borrower is granted a cert In return, the borrower agrees to pay an additional fee and repay the loan within a set period of time. To secure a loan, the borrower provides collateral that the lender can claim in the event of default. -## You will learn +## You Will Learn In this tutorial you will learn how to: From 028ea19fe6e1eeb46be286e0a3263208295f7093 Mon Sep 17 00:00:00 2001 From: Tobias <8368497+toschdev@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:26:37 +0100 Subject: [PATCH 12/12] Update docs/docs/02-guide/05-loan.md Co-authored-by: Danny --- docs/docs/02-guide/05-loan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md index 5cf83bdc6c..0573a344fe 100644 --- a/docs/docs/02-guide/05-loan.md +++ b/docs/docs/02-guide/05-loan.md @@ -479,4 +479,5 @@ loand q bank balances $(loand keys show alice -a) ``` ## Conclusion + This tutorial outlines the process of setting up a decentralized platform for digital asset loans using blockchain technology. By following these steps, you can create a DeFi platform that allows users to engage in secure and transparent lending and borrowing activities.