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]
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()
Result
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
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
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()
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
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):
...
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]
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):
...
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 withmin(stop, N)
as the argument to range, likerange(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
# 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
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
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
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
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 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)