DEV Community

Muwawu Moses
Muwawu Moses

Posted on

Vyper: For loops and Arrays.

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.


@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]

Enter fullscreen mode Exit fullscreen mode

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

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()
Enter fullscreen mode Exit fullscreen mode

Result

Terminal Results

Iterating through the values of an array variable

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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

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()
Enter fullscreen mode Exit fullscreen mode

We can also iterate over a literal array:

# declaring variable y
y: public(int128)

@external
def non_dictated() -> int128:
    for x in [34, 76, 89, 45]:
        self.y = x
    return self.y
Enter fullscreen mode Exit fullscreen mode

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.

for i in range(START, STOP):
    ...
Enter fullscreen mode Exit fullscreen mode

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


@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]

Enter fullscreen mode Exit fullscreen mode

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;

for i in range(stop, bound=N):
    ...
Enter fullscreen mode Exit fullscreen mode

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.

# compile-time constant
MAX_SUPPLY: constant(uint256) = 1000000

Enter fullscreen mode Exit fullscreen mode
# 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

Enter fullscreen mode Exit fullscreen mode

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


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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

When we run the above two contracts, the latest_index value returned is 1.

Second pair


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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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 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!

Top comments (0)