Vyper for Beginners — Dynamic Arrays

Welcome back to Vyper for Beginners! This lesson covers dynamic arrays, a key data type that was recently introduced with Vyper 0.3.2.

This builds off the previous lessons, so please review the series below if you need a refresher or encounter anything here that is unfamiliar.

NOTE: Vyper 0.3.3 was recently released, which fixes a storage allocation bug with dynamic arrays. If you haven’t already, please update your local copy using pip install -U vyperlang.

Vyper for Beginners

Dynamic Arrays

A dynamic array is a data structure that can hold an arbitrary amount of elements of a common type (ignoring storage and gas limits). Rather than thinking of a dynamic array as a distinct data type, it may be useful to picture it as a container for holding a string with several pieces of individual data.

The most similar Python data type is a list, and Vyper’s dynamic array implementation reuses some common method names and syntax.

A dynamic array in Vyper is declared with the format variable_name: DynArray[type, max_length] where type is any valid Vyper data type (address, uint256, int64, bytes32, etc.) with three exceptions: variable-length data types (Bytes[n] and String[n]) and the reference data type mapping. Solidity has similar limitations on data type.

One difference between Vyper and Solidity is that Vyper requires you to define the maximum length of your dynamic array within the contract, where Solidity does not. There is a storage and gas limit to how large an array can grow, so Solidity does not truly allow an unlimited length dynamic array. Vyper is just more explicit about it.

Example:

# declare a dynamic array that can hold up to 1024 token balances of type uint256
tokenBalances: DynArray[uint256, 1024]

The equivalent Solidity declaration would be:

// declare a fixed size array that can hold up to 1024 token balances of type uint256
uint256[1024] tokenBalances;

or if you wanted Solidity to manage the length:

// declare a dynamically-size array to hold token balances of type uint256
uint256[] tokenBalances;

Vyper has no equivalent to this declaration.

Manipulating a Dynamic Array

Vyper allows direct access (reading and writing) to all element in the array using index notation. Vyper uses zero indexing, so the 1st element of an array is accessed using arrayName[0]. Accessing the 4th value of an array can be done using arrayName[3], and so on.

Solidity provides the following dynamic array methods:

  • pop() — remove the last element of the array
  • push() — add a new value as the last element of the array
  • length() — returns the maximum length of the array

Vyper provides the following dynamic array methods:

  • pop() — return the last element of the array, then remove it
  • append() — add a new value as the last element of the array

A difference between Solidity and Vyper is that pop() in Vyper will return the value and delete it, whereas in Solidity you would need to read it first, then save it, then call pop().

Also Vyper has no dedicated length() method for the dynamic array type, but provides access to the same information using the built-in len() function. It accepts a reference to the dynamic array variable as an argument.

Let’s take our tokenBalances variable from before, declare it inside a function, then manipulate it using various expressions.

# declare a dynamic array in memory that can hold up to 1024 token balances of type uint256
# note that a memory variable requires an initial value, which we declare as an empty list
tokenBalances: DynArray[uint256, 1024] = []          

tokenBalances.append(69)            # tokenBalances now holds the data [69]
tokenBalances.append(420)           # tokenBalances now holds the data [69, 420]

# declare a new uint256 memory variable to store the array's length
arrayLength: uint256 = 0

arrayLength = len(tokenBalances)    # arrayLength now holds the data 2
tokenBalances.append(2035)          # tokenBalances now holds the data [69, 420, 2035]
arrayLength = len(tokenBalances)    # arrayLength now holds the data 3

# declare a new uint256 memory variable to store the individual values pulled from the array
elementValue: uint256 = 0 

elementValue = tokenBalances[0]     # elementValue now holds the data 69
elementValue = tokenBalances[1]     # elementValue now holds the data 420
elementValue = tokenBalances[2]     # elementValue now holds the data 2035

elementValue = tokenBalances.pop()  # elementValue now holds the data 2035
elementValue = tokenBalances.pop()  # elementValue now holds the data 420
elementValue = tokenBalances.pop()  # elementValue now holds the data 69

arrayLength = len(tokenBalances)    # arrayLength now holds the data 0

Contract Example

Testing smart contracts is difficult because you can’t debug them in the usual way. For one, there’s no debugger to set breakpoints and step into, through, and over functions.

There’s a certain amount of “thinking like a computer” that is required when writing a smart contract. It’s getting easier though!

Vyper 0.3.3 recently added support for a simple print() statement that is compatible with Hardhat. I am using Brownie and Ganache, so I don’t have access to print() yet, so instead I’ll share my hacky method for simple contract debugging ­— the debug-value event.

Vyper allows you to define an event, which publishes a value under a pre-defined topic. These events exist outside the blockchain and can be read by block explorers, nodes, and clients.

Let’s define two simple events called printUint and printDynArray that will publish the values for each data type as the contract progresses.

event printUint:
  value: uint256

event printDynArray:
  value: DynArray[uint256, 1024]

To emit either event, simply pass a variable of the appropriate type into a statement log printDynArray() or log printUint()

Now let’s write all of this to a contract :

# @version >=0.3.3

event printUint:
  value: uint256

event printDynArray:
  value: DynArray[uint256, 1024]

@external
def test_arrays():
    # declare a dynamic array in memory that can hold up to 1024 token balances of type uint256
    # note that a memory variable requires an initial value, which we declare as an empty list
    tokenBalances: DynArray[uint256, 1024] = []        
    log printDynArray(tokenBalances)

    tokenBalances.append(69)                             # tokenBalances now holds the data [69]
    log printDynArray(tokenBalances)
    tokenBalances.append(420)                            # tokenBalances now holds the data [69, 420]
    log printDynArray(tokenBalances)

    # declare a new uint256 memory variable to store the array's length
    arrayLength: uint256 = 0
    log printUint(arrayLength)
    arrayLength = len(tokenBalances)                     # arrayLength now holds the data 2
    log printUint(arrayLength)
    tokenBalances.append(2035)                           # tokenBalances now holds the data [69, 420, 2035]
    log printDynArray(tokenBalances)
    arrayLength = len(tokenBalances)                     # arrayLength now holds the data 3
    log printUint(arrayLength)

    # declare a new uint256 memory variable to store the individual values pulled from the array
    elementValue: uint256 = 0 
    log printUint(elementValue)

    elementValue = tokenBalances[0]                      # elementValue now holds the data 69
    log printUint(elementValue)
    elementValue = tokenBalances[1]                      # elementValue now holds the data 420
    log printUint(elementValue)
    elementValue = tokenBalances[2]                      # elementValue now holds the data 2035
    log printUint(elementValue)

    elementValue = tokenBalances.pop()                   # elementValue now holds the data 2035
    log printUint(elementValue)
    elementValue = tokenBalances.pop()                   # elementValue now holds the data 420
    log printUint(elementValue)
    elementValue = tokenBalances.pop()                   # elementValue now holds the data 69
    log printUint(elementValue)

    arrayLength = len(tokenBalances)                     # arrayLength now holds the data 0
    log printUint(arrayLength)

Now launch Brownie and deploy this to a local fork:

devil@hades:~/flasharb$ brownie console --network mainnet-fork
Brownie v1.18.1 - Python development framework for Ethereum

Compiling contracts...
  Vyper version: 0.3.3
Generating build data...
 - dyn_array

DynArray is the active project.

Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --server.port 8545 --miner.blockGasLimit 12000000 --wallet.totalAccounts 10 --hardfork istanbul --wallet.mnemonic brownie --fork.url https://rpc.ankr.com/eth --chain.chainId 1'...
Brownie environment is ready.

>>> dyn_array.deploy({'from':accounts[0]})
Transaction sent: 0xb4121c24ff84735018548ab25ec71d52cbb8469fac89f274bc8aa637103c3bc0
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 2
  dyn_array.constructor confirmed   Block: 14643478   Gas used: 346841 (2.89%)
  dyn_array deployed at: 0xE7eD6747FaC5360f88a2EFC03E00d25789F69291

With the contract deployed, we can call the test_arrays() function, then inspect the transaction using the info() method.

>>> dyn_array[0].test_arrays().info()
Transaction sent: 0x85b82f410258ca362eb9959cb0cbe0d70fbb059fc9547eb07429160d513722ea
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 3
  dyn_array.test_arrays confirmed   Block: 14643488   Gas used: 45875 (0.38%)

Transaction was Mined 
---------------------
Tx Hash: 0x85b82f410258ca362eb9959cb0cbe0d70fbb059fc9547eb07429160d513722ea
From: 0x66aB6D9362d4F35596279692F0251Db635165871
To: 0xE7eD6747FaC5360f88a2EFC03E00d25789F69291
Value: 0
Function: dyn_array.test_arrays
Block: 14643488
Gas Used: 45875 / 12000000 (0.4%)

Events In This Transaction
--------------------------
└── dyn_array (0xE7eD6747FaC5360f88a2EFC03E00d25789F69291)
    ├── printDynArray
    │   └── value: ()
    ├── printDynArray
    │   └── value: (69,)
    ├── printDynArray
    │   └── value: (69, 420)
    ├── printUint
    │   └── value: 0
    ├── printUint
    │   └── value: 2
    ├── printDynArray
    │   └── value: (69, 420, 2035)
    ├── printUint
    │   └── value: 3
    ├── printUint
    │   └── value: 0
    ├── printUint
    │   └── value: 69
    ├── printUint
    │   └── value: 420
    ├── printUint
    │   └── value: 2035
    ├── printUint
    │   └── value: 2035
    ├── printUint
    │   └── value: 420
    ├── printUint
    │   └── value: 69
    └── printUint
        └── value: 0

All of these values match what we expect. Hooray!

Moving Forward

Now that we’ve learned how to manipulate dynamic arrays, we will explore some of the more exotic data types like structs and mappings.

Photo of author

Written By BowTiedDevil

Degenerate coder, open source software maximalist, engineer, turbo autist.

Disclosure

This article may contain links to third-party websites or other content for information purposes. BowTiedIsland may receive a commission at no cost to you if you purchase a product after clicking one of these links. The Third-Party Sites are not under the control of BowTiedIsland, and BowTiedIsland is not responsible for the content of any Third-Party Site. All information contained herein is the opinion of the writer and does not constitute financial advice. We aim to act as a neutral third party and aid in your research and analysis.


The Jungle


Crypto, Investing, and E-Commerce with BowTied Bull

The future is internet based, therefore we have a triangle based approach with crypto, e-commerce business making and Investing in traditional assets

Break Into a 6-Figure Wall Street Career

Learn how to efficiently break into investment banking and private equity. The Banker’s Bible is a definitive resource that offers an   A-Z blueprint and also gives you 24/7 access to real finance professionals. 

Fitness With BowTiedOx

BowTiedOx provides you a place to find all of his latest programs and guides.

Weekly newsletters that cover fitness, health, and mindset, all grounded in the fundamentals of physiology.

Media Production with BowTied Turkey and BowTied Tamarin

Video is no longer optional.

Don’t get left behind.

Your brand deserves professional videos to engage your audience.

Art & Graphic Design with BowTied Patriot

BowTied Patriot is a graphic artist who specializes in photography, mixed medium custom artwork, and NFT creation.

Join BowTiedPatriot as he dives into making Art in Web3.0 and The Metaverse.

Cooking with BowTiedOctopod

Learn secrets from a fine dining chef for maximum flavor and time-saving efficiency

Newsletters on Ingredients, Techniques and Flavor hacks that will have you eating better. We will never eat bugs!

Meme Warfare with DgenFren

Increase your online engagement, organically influence narratives, and build your online persona by using marketing that your target audience actually wants: memes.

Learn How to Sell with BowTiedSalesGuy

Sales is one of the most transferrable life skills, yet few know how to actually sell.

Traditional sales tactics don’t cut it in today’s hyper competitive world.

Learn the secrets from a Chad Salesman and change your Life forever.

Ecommerce with BowTiedOpossum

Learn the skills to start and build your first online business.

Want to build a business that travels with you?

Learn from an industry veteran that has worked on and with brands you already know.