Vyper: For loops and Arrays.
Tags: vyper, web3, ethereum, smartcontract
Published: 2024-05-29 06:54:36

Vyper is a pythonic language but does not support dynamic arrays or strings as extensively as Python. Therefore, there are certain guidelines to follow if one is to play around with `for loops` and `arrays`. In this tutorial, we are going use examples for a better understanding of how things work.
```python

@external
def iterate_array_variable() -> int128:
    marks: int128[4] = [45, 67, 90, 36]
    result: int128[4] = [0, 0, 0, 0]
    for x in range(4):
        result[x] = marks[x] * 2
    return result[2]

```
In the example above, we define a function `iterate_array_variable` that returns a variable of type `int128`. We then define an array `marks` which must contain `4` literals of type `int128`. `result` is also an array defined just like `marks`. Their only difference are the values in these two arrays i.e `[45, 67, 90, 36]` and `[0, 0, 0, 0]`. The aim of this function is give the result array new values from `result[x] = marks[x] * 2`.

We expect the function to return the `result` to have a new value for each iteration performed. For example, at the third position(`result[2]`), the value must change from `0` to `180`


## Interacting with the contract
```python
import sys
from web3 import Web3

# Connect to BSC node (Binance Smart Chain)
bsc_node_url = 'https://data-seed-prebsc-1-s1.binance.org:8545/'  # Replace with your BSC node URL
web3 = Web3(Web3.HTTPProvider(bsc_node_url))

# Set the private key directly (For demonstration purposes only, do not hardcode in production)
private_key = 'Your_private_key'  # Replace with your actual private key
account = web3.eth.account.from_key(private_key)

# Contract ABI
contract_abi = [
	Your_abi
]
# Contract address
contract_address = web3.to_checksum_address('your_contract_address')  # Replace with your contract's address

# Create contract instance
contract = web3.eth.contract(address=contract_address, abi=contract_abi)


# Function to set a choice
def call_iterate_array_variable():
    nonce = web3.eth.get_transaction_count(account.address)
    tx = contract.functions.iterate_array_variable().build_transaction({
        'chainId': 97,  # BSC testnet
        'gas': 3000000,
        'gasPrice': web3.to_wei('5', 'gwei'),
        'nonce': nonce,
    })
    signed_tx = web3.eth.account.sign_transaction(tx, private_key)
    tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    result = contract.functions.iterate_array_variable().call()
    return result

def main():
    result = call_iterate_array_variable()
    print(f'Result of the calculation: {result}')

if __name__ == "__main__":
    main()
```

## Result

![Terminal Results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/posgroa4j7ahh6spgqme.png)

## Iterating through the values of an array variable

```python
y: public(int128)
@external
def iterate_array_variable() -> int128:
    marks: int128[4] = [45, 67, 90, 36]
    for x in marks:
        self.y = x
    return self.y
```
In the above example, we iterate through the values of the variable `marks`. We expect the function to return `36` since it will be the last value assigned to `y` after the iteration.

One may ask, what if we want to return a given value from such an array? The answer lies in the `assert` statement which is used for boolean operations.
```python
y: public(int128)

@external
def get_mark(index: int128) -> int128:
    marks: int128[4] = [45, 67, 90, 36]
    assert 0 <= index, "Index out of lower limit"
    assert index < 4, "Index out of upper limit"
    self.y = marks[index]
    return self.y
```
In the above example, it's quite evident that we can explicitly determine the value we want to access by simply providing the index of that value from an array. `assert 0 <= 4` makes sure that the index is greater or equal to zero. If not, the execution of the contract shall be **reverted** with the 'Index out of lower limit' error message. `assert index < 4` behaves in the same manner but with different logic.

## Interacting with the contract
```python
from web3 import web3

"""
.......
......
Other code here
......
....
"""

# Create contract instance
contract = web3.eth.contract(address=contract_address, abi=contract_abi)


# Function to set a choice
def call_get_mark(index):
    nonce = web3.eth.get_transaction_count(account.address)
    tx = contract.functions.get_mark(index).build_transaction({
        'chainId': 97,  # BSC testnet
        'gas': 3000000,
        'gasPrice': web3.to_wei('5', 'gwei'),
        'nonce': nonce,
    })
    signed_tx = web3.eth.account.sign_transaction(tx, private_key)
    tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    result = contract.functions.get_mark(index).call()
    return result

def main():
    index = int(input("Enter the index (0 to 3): "))
    result = call_get_mark(index)
    print(f'Result of the calculation: {result}')

if __name__ == "__main__":
    main()
```
We can also iterate over a literal array:
```python
# declaring variable y
y: public(int128)

@external
def non_dictated() -> int128:
    for x in [34, 76, 89, 45]:
        self.y = x
    return self.y
```
The above code will return `45` since it's the last value that will be stored in variable `y`.

## Range Iteration

At the very beginning of this article, we saw one user case of range, `for x in range(4):`. Ranges are created using the `range` function. The example above follows a structure; `for i in range(STOP):` where `STOP` is a literal integer greater than zero.

Another use of range can be with `START` and `STOP` bounds. 
```python
for i in range(START, STOP):
    ...
```
Here, `START` and `STOP` are literal integers, with `STOP` being a greater value than `START`. `i` begins as `START` and increments by one until it is equal to `STOP`.

**Example**
```python

@external
def iterate_array_variable() -> int128:
    marks: int128[4] = [45, 67, 90, 36]
    result: int128[4] = [0, 0, 0, 0]
    for x in range(1, 2):
        result[x] = marks[x] * 2
    return result[3]

```
When run, the above code will throw an error since the index specified by `result[3]`, fourth position, exceeds the range of two positions from second to the third one.

Another important example is;
```python
for i in range(stop, bound=N):
    ...
```

> Here, stop can be a variable with integer type, greater than zero. N must be a compile-time constant. i begins as zero and increments by one until it is equal to stop. If stop is larger than N, execution will revert at runtime. In certain cases, you may not have a guarantee that stop is less than N, but still want to avoid the possibility of runtime reversion. To accomplish this, use the `bound=` keyword in combination with `min(stop, N)` as the argument to range, like `range(min(stop, N), bound=N)`. This is helpful for use cases like chunking up operations on larger arrays across multiple transactions.

To better understand this, we need to first understand **compile-time constant** and **run-time reversion**.

Runtime reversion refers to the behavior of the contract when it encounters an error during execution. If a condition in the contract is not met or an exception is thrown, the contract will revert to its previous state before the transaction. This ensures that no partial or erroneous changes are made to the blockchain state. For example, using assert or require statements can trigger a reversion if the condition fails.
```python
# compile-time constant
MAX_SUPPLY: constant(uint256) = 1000000

```
```python
# if amount is not greater than zero, 
# the transaction will revert at runtime, 
# ensuring the contract state remains unchanged.

@external
def transfer(amount: uint256):
    assert amount > 0, "Amount must be greater than zero"
    # transfer logic

```
We are going to use the following four examples so as to fully understand how range can be used in this manner. In both examples, the compile time constant `N` is `4`. What changes is the `stop` value. The first pair has a stop value of `2` and the other has a stop value of `84`. 

## First Pair
```python

latest_index: public(int128)

N: constant(int128) = 4  # compile time constant

@external
def process_chunk() -> int128:
    for i in range(2, bound=N):
        self.latest_index = i
    return self.latest_index

```

```python

latest_index: public(int128)

N: constant(int128) = 4  # compile time constant

@external
def process_chunk() -> int128:
    for i in range(min(2, N), bound=N):
        self.latest_index = i
    return self.latest_index
```
When we run the above two contracts, the `latest_index` value returned is `1`.

## Second pair
```python

latest_index: public(int128)

N: constant(int128) = 4  # compile time constant

@external
def process_chunk() -> int128:
    for i in range(84, bound=N):
        self.latest_index = i
    return self.latest_index

```
```python

latest_index: public(int128)

N: constant(int128) = 4  # compile time constant

@external
def process_chunk() -> int128:
    for i in range(min(84, N), bound=N):
        self.latest_index = i
    return self.latest_index

```
When we run the first contract in this pair, it throws a `ContractLogicError: Execution Reverted`.

However, the last contract returns a latest_index value of `3` when run. 

## Explanation
In the first pair of examples, we get 1 as as the returned value simply because the `stop` value doesn't exceed constant `N`.

In the second pair, we can see that we get an error where as the second example compiles well. The reason behind this is the use of `min(stop, N)` as an argument. It simply minimizes the stop value in respect to the value of `N` provided. Therefore, the minimum possible value of `stop` in this case will be `4` hence the returning an index value of `3`.

For more information, please visit the official vyper documentation and I would also recommend my [previous article](https://dev.to/mosesmuwawu/mastering-vyper-functionspart1--1144) for a deeper understanding of vyperlang. If you found this article helpful, i would be delighted if you gave me a heart. Follow for more. Thank you!