Simple loan contract

Hi Folks,

As part of my learning process of the Move language. I am trying to come up with a simple loan contract that is inspired from the documentation on the libra website. However, i have run into some issues.

This is the module where i plan to include basic procedures:

// SI Module
address 0xc0fce77edef949b6fa9f0ad0c35be2c4 {

module SimpleInterest {

  resource struct Interest<CoinType> {
    interest: u64
  }

  // Set the interest rate
  public fun set_interest<CoinType>(account: &signer, interest: u64) {
    move_to(account, Interest<CoinType> { interest })
  }

  public fun update_rate<CoinType>(account: address, new_interest: u64): u64 acquires Interest {
    let intr = borrow_global_mut<Interest<CoinType>>(account);
    intr.interest = new_interest;
    intr.interest
    
  }

  // Destroy the resource
  //public fun destroy_interest<CoinType>(account: &signer, interest: u64){
  // let intr = move_from<Interest<u64>>(account);
  //let Interest { interest: _ } =  intr ;
  //}

  // Get the interest rate for the loan.
  public fun get_interest<CoinType>(account: address): u64 acquires Interest {
    if (exists<Interest<CoinType>>(account))
      borrow_global<Interest<CoinType>>(account).interest
    else
      0
  }
}
}

This is the transaction script for updating the interest rate

    $ cat update_interest.move 
    script {
    use 0x1::LBR::LBR;
    use 0xc0fce77edef949b6fa9f0ad0c35be2c4::SimpleInterest;
    fun update_rate(lender: address, new_rate: u64): u64
      { SimpleInterest::update_rate<LBR>(lender, new_rate) }
    }

When i compile the update_interest.move , the following error occurs:

libra% dev compile 0 /home/faizancloudcomputing/update_interest.move /home/faizancloudcomputing/SimpleInterest.move /home/faizancloudcomputing/libra/language/stdlib/modules
>> Compiling program
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/move-build /home/faizancloudcomputing/update_interest.move -s c0fce77edef949b6fa9f0ad0c35be2c4 -o /tmp/06796a3d4b54eec4784b87310281a04c -d /home/faizancloudcomputing/SimpleInterest.move -d /home/faizancloudcomputing/libra/language/stdlib/modules`
error: 

   ┌── /home/faizancloudcomputing/update_interest.move:7:5 ───
   │
 7 │ fun update_rate(lender: address, new_rate: u64): u64
   │     ^^^^^^^^^^^ Invalid main return type
   ·
 7 │ fun update_rate(lender: address, new_rate: u64): u64
   │                                                  --- The type: 'u64'
   ·
 7 │ fun update_rate(lender: address, new_rate: u64): u64
   │     ----------- Is not compatible with: '()'
   │
compilation failed

I was wondering whether the return type should be u64 or something else? Also, is it possible to have if statements that would execute some logic based on the time period?

Thanks in advance!

Best regards,
Syed

4 Likes

Hi Syed,

Thanks for the question! For scripts in Move, you cannot return any value from your script. So the return type must be ()

Try changing your script function declaration to

script {
    ...
    fun update_rate(lender: address, new_rate: u64): () {
        ...
    }
}

or

script {
    ...
    fun update_rate(lender: address, new_rate: u64) {
        ...
    }
}

Both are equivalent–when a return type is not specified it is assumed to be unit ()
I’ll go and update the error messages to hopefully make this more clear for future users!

Restrictions aside, did you have any particular design goal in mind when you were trying to return a u64 from a script? We might be able to help you rework that code if need be

2 Likes

I refer to your mail and as a libra developer in my Country and not at all familiar with coding and seeing your interest in loen contracts I wii be glad to known the issues with this.
My interest with you is that I have been contacted by several international institution for global finance and investment in Africa. Maybe you can be of help to structure my startup. As a financial hub Mauritius have a lot to offer and all the structures are already here.
Regards
Harold Marie
:mauritius: Libra Mauritius

1 Like

Hi Todd,

Thanks a lot for your reply! I updated the script as per your suggestion but unfortunately i am still getting the compilation error.

$ cat update_interest.move 

script {

use 0x1::LBR::LBR;
use 0xbf03be483193cac64dccd37ef9ec262b::SimpleInterest;

fun update_rate(lender: address, new_rate: u64): () { 
  SimpleInterest::update_rate<LBR>(lender, new_rate) 
  }

}




libra%   dev compile 0 /home/faizancloudcomputing/update_interest.move /home/faizancloudcomputing/SimpleInterest.move /home/faizancloudcomputing/libra/language/stdlib/modules
>> Compiling program
    Finished dev [unoptimized + debuginfo] target(s) in 0.50s
     Running `target/debug/move-build /home/faizancloudcomputing/update_interest.move -s bf03be483193cac64dccd37ef9ec262b -o /tmp/947c238d0022985ad56814d0e12b57af -d /home/faizancloudcomputing/SimpleInterest.move -d /home/faizancloudcomputing/libra/language/stdlib/modules`
error: 

   ┌── /home/faizancloudcomputing/update_interest.move:8:3 ───
   │
 8 │   SimpleInterest::update_rate<LBR>(lender, new_rate)
   │   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid return expression
   ·
 7 │ fun update_rate(lender: address, new_rate: u64): () {
   │                                                  -- Is not compatible with: '()'
   │

    ┌── /home/faizancloudcomputing/SimpleInterest.move:16:74 ───
    │
 16 │   public fun update_rate<CoinType>(account: address, new_interest: u64): u64 acquires Interest {
    │                                                                          --- The type: 'u64'
    │

compilation failed

OR

$ cat update_interest.move 
script {
use 0x1::LBR::LBR;
use 0xbf03be483193cac64dccd37ef9ec262b::SimpleInterest;
fun update_rate(lender: address, new_rate: u64) { 
  SimpleInterest::update_rate<LBR>(lender, new_rate) 
  }
}

libra% dev compile 0 /home/faizancloudcomputing/update_interest.move /home/faizancloudcomputing/SimpleInterest.move /home/faizancloudcomputing/libra/language/stdlib/modules
>> Compiling program
    Finished dev [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/move-build /home/faizancloudcomputing/update_interest.move -s bf03be483193cac64dccd37ef9ec262b -o /tmp/425e88596b59d4e04ca2b9441fa030fd -d /home/faizancloudcomputing/SimpleInterest.move -d /home/faizancloudcomputing/libra/language/stdlib/modules`
error: 

   ┌── /home/faizancloudcomputing/update_interest.move:8:3 ───
   │
 8 │   SimpleInterest::update_rate<LBR>(lender, new_rate)
   │   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid return expression
   ·
 7 │ fun update_rate(lender: address, new_rate: u64) {
   │     ----------- Is not compatible with: '()'
   │

    ┌── /home/faizancloudcomputing/SimpleInterest.move:16:74 ───
    │
 16 │   public fun update_rate<CoinType>(account: address, new_interest: u64): u64 acquires Interest {
    │                                                                          --- The type: 'u64'
    │

compilation failed

My intention was to update the value of the parameter ‘interest’ in the Interest resource and since its type was declared as u64, i thought i need to specify a u64 return type to be able to execute the script.

My short term goal is to explore whether it is possible to write a loan contract with the current functionality made available by Move. I am still trying to figure out how to automatically execute a transaction based on timestamp. Do you think it’s possible at the moment?

Thanks!

Cheers,
Syed

1 Like

You need a semicolon at the end of the line to ignore the u64 value. So SimpleInterest::update_rate<LBR>(lender, new_rate);

Move is an expression based language. The last expression of any function is the value that will be returned. For example

fun foo(): u64 { 0 }
fun bar(): u64 { foo() }
fun baz(): u64 { 
  let x = 0;
  x
}

All three of these functions return u64 values.

1 Like

You can execute the script. You just need to ignore the return value to get it to type check, since script functions cannot return any values (they need to return () which is a value in some sense but not at the bytecode level)
Hopefully you are all set after that :slight_smile:

1 Like

One other alternative might be to simplify update_rate to

public fun update_rate<CoinType>(account: address, new_interest: u64) acquires Interest {
  let intr = borrow_global_mut<Interest<CoinType>>(account);
  intr.interest = new_interest
}
2 Likes

I would not suggest doing it this way and I’ll get to that shortly. But if you really need to trigger a script at a given timtestamp, I would suggest doing that as a separate off-chain trigger, and not doing it in move.

If I were to do something like this in Move, I would instead try to encode the time trigger inside of the module’s functionality. For a simple example:

address 0x70DD {

module TimedGrabBag {
    use 0x1::Libra::Libra;
    use 0x1::LBR::LBR;

    const NOT_YET: u64 = 0;
    const TOO_LATE: u64 = 1;

    resource struct Bag {
        coin: Libra<LBR>,
        start_time: u64,
        end_time: u64,
    }

    public fun create(account: &signer, coin: Libra<LBR>, start: u64, end: u64) {
        move_to(account, Bag { coin, start_time: start, end_time: end })
    }

    public fun anyone_can_take(bag_account: address): Libra<LBR> acquires Bag {
        let Bag { coin, start_time, end_time } = move_from(bag_account);
        let now = 0x1::LibraTimestamp::now_microseconds();
        assert(now < start_time, NOT_YET);
        assert(now >= end_time, TOO_LATE);
        coin
    }
}

}

Here, the anyone_can_take function will fail if the time is not within bounds. Alternatively, you could imagine a version that instead of failing, cased on the time and did something different in those circumstances.

address 0x70DD {

module TimedGrabBag2 {
    use 0x1::Libra::{Self, Libra};
    use 0x1::LBR::LBR;

    resource struct Bag {
        coin: Libra<LBR>,
        start_time: u64,
        end_time: u64,
    }

    public fun create(account: &signer, coin: Libra<LBR>, start: u64, end: u64) {
        move_to(account, Bag { coin, start_time: start, end_time: end })
    }

    public fun anyone_can_take(bag_account: address): Libra<LBR> acquires Bag {
        let Bag { coin: _, start_time, end_time } = borrow_global(bag_account);
        let now = 0x1::LibraTimestamp::now_microseconds();
        if (!(*start_time <= now && now < *end_time)) {
            Libra::zero()
        } else {
            let Bag { coin, start_time: _, end_time: _ } = move_from(bag_account);
            coin
        }
    }
}

}
2 Likes

Awesome! The script executed successfully after adding the semicolon :slight_smile:

Hi Sam,

I tried this workaround and it also worked.
Thanks a lot for your suggestion! :slight_smile:

Wow! thank you so much todd for being so generous with your time! :slight_smile:

Probably a dumb question: In the function anyone_can_take from the module TimedGrabBag2 , I am wondering why in this line let Bag { coin: _, start_time, end_time } = borrow_global(bag_account); we have to specify that reference of type Libra<LBR> is unused? Is it because else block might never get executed and then coin will remain unused in that case?

1 Like

I have been from some time trying to figure out what can be done in Move at the moment and also which actions are best to be executed off-chain. So far what i have understood is the following, please correct me if i am wrong – With the current functionality in Move, we can in a local environment execute a:

  1. Loan contract
  2. Escrow Account
  3. ERC-20-like interface https://github.com/libra/libra/issues/3820#issuecomment-640943881
  4. Leasing contract?

I am wondering what is that we cannot do with Move that we can do with Solidity. AFAIK, we don’t have floating types but i don’t think it could be a show stopper for any smart contract and strings are now supported (https://github.com/libra/libra/pull/4058). I am not sure which types of contracts are currently not possible?

1 Like

I think all of the use-cases you mention are possible in Move. I do not know of any contract that you can implement in Solidity and not Move. It is often true that the differences in the languages will lead to different design patterns (see https://github.com/libra/libra/issues/4377#issuecomment-643468702 for a few examples).

If you come across a use-case that doesn’t seem possible to implement with Move, please let us know and we will take a look!

1 Like

Thank you so much for your reply, Sam! I have not yet came across any contract which is not possible in Move but if i came across one i will definitely open an issue for it on github.

Thanks again for your continued help!

Best regards,
Syed

2 Likes

Sorry for the very late response!

If I understand your question correctly, its why do we have to specify coin: _ instead of just omitting the field in the binding, i.e. let Bag { start_time, end_time } = ... ?
Assuming I understand the question correctly, this is just a small language design choice. Currently, all fields must be specified in a binding. We might add syntax later to let you omit fields, perhaps something like let Bag { start_time, end_time, .. }. The rationale here being that we want to warn users if they forget a field, and then let them opt out of that warning. We think that having something flipped, were you don’t receive the warning by default would be a frustrating experience. In short, it has nothing to do with the type of the field being not used (in this case &Libra<LBR>).

To add to that a bit, the code written could have been let Bag { coin, start_time, end_time }, but we warn for unused local variables, and then you would have a warning for coin being unused

1 Like