Skip to content

Commit

Permalink
fixed errors in the course (#300)
Browse files Browse the repository at this point in the history
  • Loading branch information
manangouhari authored Mar 19, 2021
1 parent 2f52903 commit 3631f14
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 65 deletions.
70 changes: 39 additions & 31 deletions lessons/inter-contract-calling/05/05.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Inter-contract calling, part-1
chapter: Chapter 5
slug: chapter-05
filterBy: module-02
isCode: true
isCode: false
editor:
language: python
startingCode: |
Expand Down Expand Up @@ -91,7 +91,7 @@ editor:
# test code over here.
answer: |
answer: |
import smartpy as sp
class Cryptobot(sp.Contract):
Expand All @@ -113,7 +113,7 @@ editor:
# add market_address over here.
market_address = market_address,
# add active_powerup here.
active_powerup = sp.record(power = '', duration = 0)
active_powerup = sp.record(power = "", duration = 0)
)
Expand All @@ -139,7 +139,7 @@ editor:
# 4. Define data_to_be_sent - record that holds a variable powerup which is equal to powerup accepted in the parameter.
# 5. Call the market_contract with powerup and 0 mutez.
data_type = sp.TRecord(powerup = sp.TString)
market_contract = sp.contract(data_type, self.data.market_address, 'send_powerup').open_some()
market_contract = sp.contract(data_type, self.data.market_address).open_some()
self.data.mutez_points -= sp.mutez(3000)
data_to_be_sent = sp.record(powerup = powerup)
sp.transfer(data_to_be_sent, sp.mutez(0), market_contract)
Expand Down Expand Up @@ -179,17 +179,18 @@ editor:
scenario += test_bot
# test code over here.
scenario += test_bot.buy_powerup('time_freeze')
scenario += test_bot.buy_powerup("time_freeze")
scenario.verify(test_bot.data.mutez_points == sp.mutez(2000))
---

## Intro

How do you plan on winning the war if you can't order `powerups` from the Market?
**Inter-contract calling** is here to save us.
It can make your smart contracts go *super saiyan* 🔥.

It can make your smart contracts go _super saiyan_ 🔥.

## Study time

Inter-contract calling is simply the method of invoking the entry point of a contract, from another smart contract.
This means, your `Cryptobot` will be able to call an entry point that exists in `Market`.

Expand All @@ -198,66 +199,70 @@ Inter-contract calling not only **helps two contracts talk to each other** but a
> Note - You can learn about oracles through this [article](https://academy.binance.com/en/articles/blockchain-oracles-explained).
To implement inter-contract calling, you need to understand `sp.transfer` first. As the name suggests it transfers some data and amount to a destination contract(this is what calls the desired entry point).

```python
sp.transfer(data_to_be_sent, amount_of_tezos, destination_contract)
```

It calls the destination contract with `data_to_be_sent` as a parameter while sending the specified `amount_of_tezos` to it.
`destination_contract` needs to be of type, `sp.TContract` created with `sp.contract`.

```python
sp.contract(t, address, entry_point = "")
```

Don't worry if it looks overwhelming at first glance, we're going to break it down :)

`sp.contract` takes three arguments -
`sp.contract` takes three arguments -

1. `t` - it's the **type** of data that the contract is accepting. `t` has to match the type of `data_to_be_sent` in the `sp.transfer`.
2. `address` - this specifies the adress of the contract inside which you'll be calling an entry point. Needs to be of the type `sp.TAdress`.
3. `entry_point` - this is to specify exactly which entry point you want to call in the contract.
3. `entry_point` - this is an optional parameter which specifies which entry point do you want to call. The catch is, you don't need to specify the entry point if the contract only has 1 entry point (like our `Market` contract).

The result of `sp.contract` is passed to `destination_contract` in `sp.transfer`.

```python
@sp.entry_point
def send(self):

target_contract = sp.contract(sp.TString,
self.data.target_address,
entry_point = "recieve").open_some()
target_contract = sp.contract(sp.TString,
self.data.target_address).open_some()

data_to_be_sent = "This message should be sent!"
sp.transfer(data_to_be_sent,
sp.mutez(0),
sp.transfer(data_to_be_sent,
sp.mutez(0),
target_contract)
```

- This entry point calls the `recieve` entry point in the `target` contract with the data `"This message should be sent!"` as the parameter to `recieve`.
- `.open_some` chained to `sp.contract` simply checks if the specified entry point in the `target` contract is accepting a string(the specified data type which is the first argument to `sp.contract`) or not.
- Look how the type of `data_to_be_sent` matches the type specified in `sp.contract`, this is mandatory.


The full example below will clear up things even more :dart:
The full example below will clear up things even more :dart:

### Show me an example

```python
class Sender(sp.Contract):

def __init__(self, target_address):
self.init(target = target_address)

@sp.entry_point
def send(self, payload):
data_type = sp.TRecord(payload = sp.TString)
c = sp.contract(data_type, self.data.target, entry_point = "recieve").open_some()
c = sp.contract(data_type, self.data.target).open_some()
data_to_be_sent = sp.record(payload = payload)
sp.transfer(data_to_be_sent, sp.mutez(0), c)

class Target(sp.Contract):
def __init__(self):
self.init(msg = "")

@sp.entry_point
def recieve(self, params):
self.data.msg = params.payload

@sp.add_test(name = "Test")
def test():
scenario = sp.test_scenario()
Expand All @@ -266,7 +271,7 @@ def test():
test_sender = Sender(target_address = test_target.address)
scenario += test_sender
scenario += test_sender.send('this should be sent to target.')

scenario.verify(test_target.data.msg == 'this should be sent to target.')
```

Expand All @@ -276,24 +281,27 @@ This is a bare-bones example, we covered the `send` function in the section abov
2. Inside the `Sender` contract, we're calling the `receive` entry point present inside the `Target` contract.
3. `recieve` entry point simply assigns `msg` the value it's being sent as the `payload`.


> Note: If the `Target` contract had more than one entry point. We would also need to specify the entry point in `sp.contract`
## #buidl-ing time

### New feature request!

It's time to build the functionality of buying a powerup from the `Market` finally 🚀
Let's give our `Cryptobot` the ability to buy a `powerup`.

### Step by step walkthrough

1. Accept `market_address` as an arugment for `Cryptobot` and initialize the `Cryptobot` with `market_address` equal to `market.address`.(Look at the example above to see how we store `target_address` inside the `Sender` contract.)
2. Add `active_powerup` to `Cryptobot`'s contract storage and initialize it to be a record which holds -
* power which is an empty string
* duration which is equal to 0
- power which is an empty string
- duration which is equal to 0
3. Implement `buy_powerup` inside `Cryptobot`
- Define `data_type` as a record which holds a variable called `powerup` of type string.
- Define the `market_contract` using `Market's` address that is pointed at the entry point `send_powerup` and accepts a data type of string. Don't forget about `.open_some()`
- Reduce 3000 mutez from `mutez_points` in `Cryptobot` as each `powerup` costs 3000 mutez.
- Define `data_to_be_sent` which is a record inside which a variable `powerup` is to be set equal to the parameter accepted through the function.
- Finally, send the `data_to_be_sent` to the Market(`market_contract`) using `sp.transfer` along with 0 Mutez.
- Define `data_type` as a record which holds a variable called `powerup` of type string.
- Define the `market_contract` using `Market's` address which accepts a data type of string. Don't forget about `.open_some()`
- Reduce 3000 mutez from `mutez_points` in `Cryptobot` as each `powerup` costs 3000 mutez.
- Define `data_to_be_sent` which is a record inside which a variable `powerup` is to be set equal to the parameter accepted through the function.
- Finally, send the `data_to_be_sent` to the Market(`market_contract`) using `sp.transfer` along with 0 Mutez.

<br />

Expand All @@ -302,4 +310,4 @@ Let's give our `Cryptobot` the ability to buy a `powerup`.
### Testing our code

- Invoke `buy_powerup` with `"time_freeze"` as the argument and add it to the scenario.
- Use `scenario.verify` to confirm whether `mutez_points` in `Cryptobot` are equal to 2000 mutez.
- Use `scenario.verify` to confirm whether `mutez_points` in `Cryptobot` are equal to 2000 mutez.
51 changes: 34 additions & 17 deletions lessons/inter-contract-calling/06/06.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ editor:
},
mutez_points = sp.mutez(initial_mutez),
market_address = market_address,
active_powerup = sp.record(power = '', duration = 0)
active_powerup = sp.record(power = "", duration = 0)
)
Expand All @@ -46,7 +46,7 @@ editor:
@sp.entry_point
def buy_powerup(self, powerup):
data_type = sp.TRecord(powerup = sp.TString)
market_contract = sp.contract(data_type, self.data.market_address, 'send_powerup').open_some()
market_contract = sp.contract(data_type, self.data.market_address).open_some()
self.data.mutez_points -= sp.mutez(3000)
Expand Down Expand Up @@ -105,7 +105,7 @@ editor:
test_bot = Cryptobot(manager_address = my_account.address,life_state = True, initial_mutez=5000, market_address = market.address)
scenario += test_bot
answer: |
answer: |
import smartpy as sp
class Cryptobot(sp.Contract):
Expand All @@ -124,7 +124,7 @@ editor:
},
mutez_points = sp.mutez(initial_mutez),
market_address = market_address,
active_powerup = sp.record(power = '', duration = 0)
active_powerup = sp.record(power = "", duration = 0)
)
Expand All @@ -145,7 +145,7 @@ editor:
@sp.entry_point
def buy_powerup(self, powerup):
data_type = sp.TRecord(powerup = sp.TString)
market_contract = sp.contract(data_type, self.data.market_address, 'send_powerup').open_some()
market_contract = sp.contract(data_type, self.data.market_address).open_some()
self.data.mutez_points -= sp.mutez(3000)
Expand Down Expand Up @@ -175,7 +175,7 @@ editor:
# define a local variable called `powerup_to_send`
# powerup should be a record which holds a variable called power(equal to an empty string) and another variable called duration(equal to 0).
powerup_to_send = sp.local('powerup_to_send', sp.record(power = '', duration = 0))
powerup_to_send = sp.local("powerup_to_send", sp.record(power = "", duration = 0))
# loop through the powerups in the contract storage.
# inside the loop, if it matches the powerup we're looking for set `powerup_to_send` equal to the powerup.
Expand Down Expand Up @@ -211,34 +211,42 @@ editor:
---

## Intro

We'll take a short detour from inter-contract calling to learn and explore a few other topics that will help you build the `Cryptobot` of your dream to absolutely dominate the war against the aliens 🤩

In this one we'll cover a slew of different topics including `sub_entry_point` and `sp.for`.

## Study time

### 1. Sub entry point

Sub entry points allow you to define functions in a smart contract that can <span class="string-highlight">**ONLY**</span> be called from other entry points of the same contract.
But why would you even need something like this?

1. **Modularization** - Break down your code into different pieces to keep the code in your entry points clean and to the point. Makes the code easier to maintain and more readable.
2. **Re-usability** - Once implemented, you can use them where ever needed. I think of them as **utility functions** that play a role in the success of other functions but aren't of much use indepedently.

<br />

Here's how you define a sub entry point -
Here's how you define a sub entry point -

```python
@sp.sub_entry_point
def double(self, x):
sp.result(x * 2)
```

Notice, **`sp.result`** is used to return from a sub entry point instead of `return`.

And it can be invoked like this -
And it can be invoked like this -

```python
@sp.entry_point
def update_result(self, num):
# Assume double is a sub entry point already implemented in the smart contract.
self.data.result = self.double(num)
```

Sub entry points can access the contract storage - if your function doesn't need to access the contract storage, you should use [`sp.global_lamba` ](https://smartpy.io/dev/reference.html#_lambdas).

### 2. Looping with `sp.for`
Expand All @@ -247,29 +255,35 @@ Sub entry points can access the contract storage - if your function doesn't need

Remember how we needed to use `sp.if` instead of `if` in SmartPy?
Similarly, `for` and `while` have their SmartPy counter-parts as `sp.for` and `sp.while`.

```python
transfers = [-12, 15, -10, 20, 30]
sp.for transfer in transfers:
self.data.total += transfer
```

You need to use `sp.range` instead of `range`.

### 3. Local variables using `sp.local`

<br />

You can define local variables in SmartPy using `sp.local` -
You can define local variables in SmartPy using `sp.local` -

```python
total = sp.local("total", 0)
```

But why not just define a normal python variable like - `total = 0`?
Because normal python variables can't be updated during execution of a smart contract but variables defined using `sp.local` can be updated.

- First argument to `sp.local` is the name which will be used to refer to the variable in error message.
- Second argument is the initial value.

<br />

**Value of a local variable is accessed through `.value`** -
**Value of a local variable is accessed through `.value`** -

```python
x = sp.local("x", 0)
# now to access or modify the value of x, you need to use `x.value`
Expand All @@ -282,31 +296,34 @@ This is mostly helpful with loops wherever you need to keep track of something.
Don't worry, we'll use this in our `Market` smart contract. That'll bring more clarity.

### Show me an example

```python
@sp.sub_entry_point
def add_to_total_received(self, amount):
self.data.total_received += amount

@sp.entry_point
def calculate_received(self):
sp.for transaction in self.data.transactions:
sp.if transaction.action == "received":
self.add_to_total_received(transaction.amount)
```

Imagine this as a part of a `Ledger` smart contract.
Imagine this as a part of a `Ledger` smart contract.
We've got an entry point `calculate_received` that loops over the transactions, if the transaction was received - it adds the amount to the total through the sub entry point `add_to_total_received`.

## #buidl-ing time

### New feature request!

We'll have to help the `Market` peeps find which `powerup` we want before they can send it over to your `Cryptobot`.

Let's put our knowledge of sub entry point and loops to use 🚀

### Step by step walkthrough

- Implement the sub entry point `find_powerup` in the `Market` contract.
1. Define a local variable called `powerup_to_send` which should be a record which holds a variable called power(equal to an empty string) and another variable called duration(equal to 0).
2. Loop through the `powerups` in the contract storage.
3. Inside the loop, if it matches the `powerup` we're looking for set `powerup_to_send` equal to the `powerup`.
4. Return the value of `powerup_to_send`(remember to use `sp.result`).
1. Define a local variable called `powerup_to_send` which should be a record which holds a variable called power(equal to an empty string) and another variable called duration(equal to 0).
2. Loop through the `powerups` in the contract storage.
3. Inside the loop, if it matches the `powerup` we're looking for set `powerup_to_send` equal to the `powerup`.
4. Return the value of `powerup_to_send`(remember to use `sp.result`).
Loading

0 comments on commit 3631f14

Please sign in to comment.