Remix IDE - Your first Vyper smart contract

Remix IDE - Your first Vyper smart contract

The easiest place to start writing smart contracts in Vyper in with the online Remix IDE.

As it's an online IDE, there's no need for installation or development environment setup, you can open the site and get started!

Remix provides tools for debugging, static analysis, and deployment all within the online environment. To use Remix with Vyper, you first need to enable the Vyper plugin from the Plugin Manager tab.

You can find the source code used in this tutorial on GitHub.

Before we get started, a quick reminder of what we are building:

Clear the content of the editor, and name the file Bounty.vy.

We first define structs for the contract.

Structs are custom defined types that can group several variables.

struct Bounty:
    issuer: address
    deadline: timestamp
    data: bytes32
    status: uint256
    amount: wei_value

struct Fulfillment:
    accepted: bool
    fulfiller_address: address
    data: bytes32

To test if everything is working, click the Compile button to compile the contract.

If everything is ok, you should see output in the Bytecode, Runetime Bytecode and LLL tabs, this indicates the compilation was successful.

If there is an error, you should see an error message when you open any of the tab links.

Issuing a Bounty

Now that we have the basic skeleton of our smart contract, we can start adding functions. First we tackle allowing a user to issue a bounty.

Declare state variables

Just like in Solidity, Vyper has state variables. State variables are values which are permanently stored in a contract storage.

You can read a full list of Vyper types in the documentation

Next we define events for the contract. Vyper can log events caught during runtime and display it for the user.

BountyIssued: event({_id: int128, _issuer: indexed(address), _amount: wei_value, data: bytes32 })
BountyCancelled: event({ _id: int128, _issuer: indexed(address), _amount: uint256 })
BountyFulfilled: event({ _bountyId: int128, _issuer: indexed(address), _fulfiller: indexed(address), _fulfillmentId: int128, _amount: uint256})
FulfillmentAccepted: event({ _bountyId: int128, _issuer: indexed(address), _fulfiller: indexed(address), _fulfillmentId: int128, _amount: uint256 })

We have four events that have different fields. A client may want to listen to the events for changes on the contract.

Declare constant values which we use to keep track of a bounties state:

CREATED: constant(uint256) = 0
ACCEPTED: constant(uint256) = 1
CANCELLED: constant(uint256) = 2

Define 2 arrays where we store data about each issued bounty and the fulfillment:

bounties: map(int128, Bounty)
fulfillments: map(int128, Fulfillment)

Define indexes for each fulfillment and bounty. We need this to get the current position of the fulfillment and bounty that exists.

nextBountyIndex: int128
nextFulfillmentIndex: int128

Note: If you fail to define the indexes, you'll encounter this error at the end when you attempt test the contract.

Persistent variable undeclared: nextBountyIndex

Define indexes for each fulfillment and bounty. We need this to get the current position of the fulfillment and bounty that exists.

nextBountyIndex: int128
nextFulfillmentIndex: int128

Issue Bounty Function

Now that we have declared our state variables we can add functions to allow users to interact with our smart contract

Add the public decorator to the function so that external users can call it from the contract. In order to send ETH to our contract we need to add the payable keyword to the function. Without this payable keyword the contract rejects all attempts to send ETH to it via this function. Read more about decorators.

@public
@payable
def issueBounty(_data: bytes32, _deadline: timestamp):
    assert msg.value > 0
    assert _deadline > block.timestamp

    bIndex: int128 = self.nextBountyIndex

    self.bounties[bIndex] = Bounty({ issuer: msg.sender, deadline: _deadline, data: _data, status: 0, amount: msg.value })
    self.nextBountyIndex = bIndex + 1

    log.BountyIssued(bIndex, msg.sender, msg.value, _data)

The function issueBounty receives a bytes32 variable called _data and a timestamp _deadline as arguments.

assert msg.value > 0
assert _deadline > block.timestamp

Since Vyper does not support modifiers, we use the assert keyword check to ensure that the every condition is met. The function returns an error if any of the conditions are not met.

bIndex: int128 = self.nextBountyIndex

We define a variable of int128 to hold the current Index position of the bounty. This is necessary because we need to use it to store the new bounty on the bounties list.

The body of our function has two lines:

self.bounties[bIndex] = Bounty({ issuer: msg.sender, deadline: _deadline, data: _data, status: 0, amount: msg.value })
self.nextBountyIndex = bIndex + 1

First we insert a new Bounty into our bounties array using the bIndex, setting the BountyStatus to CREATED.

In Vyper, msg.sender is automatically set as the address of the sender, and msg.value is set to the amount of Wei (1 ETH = 1000000000000000000 Wei).

We set the msg.sender as the issuer and the msg.value as the bounty amount.

log.BountyIssued(bIndex, msg.sender, msg.value, _data)

Finally, we log the event BountyIssued for the user to subscribe to.

Try it yourself

Now that you have seen how to add a function to issue a bounty, try adding the following functions to the Bounties contract:

You can find the complete Bounties.vy file here for reference.

Next Steps

If you enjoyed this guide, or have any suggestions or questions, let me know in the comments.

If you found any errors, feel free to update this guide by selecting the 'Update Article' option in the right hand menu, and/or update the code