Smart Contract for Elrond and Arwen

github twitter github

Let's play with a Smart Contract for Elrond Blockchain executed on the Arwen WASM Virtual Machine.

But before that, I have to drop a disclaimer here. At the moment of writing this, I am in a stage where I am learning Rust and the tech around Smart Contracts. So if you are also like me learning and would like to see some code and see what I've learned, keep reading. Otherwise, please back in a couple of weeks ;)

For this article, I chose Elrond blockchain. For a couple of reasons. The first one is that I had some knowledge already, and the most important one is that they have a lot of examples of Smart Contracts written in Rust. There is also pretty nice tooling, Command-line tools and even VS Code plugin which provides a whole customized IDE for writing Smart Contracts. It looks fantastic for newcomers. The only drawback for me was not complete documentation. But I hope the Elrond team will improve this very soon. They have a lot of essential things on their hands now (Maiar Exchange).

I know that Elrond blockchain isn't so popular, and we even don't know if it will be still around in 2-3 years (I hope so because it looks very promising). Anyway, let's see what we will be able to build.

Rust is treated as the primary language of Smart Contracts in many different projects lately. For example, Polkadot and its Substrate framework. Solana and Terra are also excellent examples. Many less-known ecosystems also bet on Rust.

In this article, I will focus only on the Smart Contract part. Then in the following articles, I will jump into JavaScript tools to interact with the Smart Contract, and I would also like to take a look at the testing approach in the Elrond blockchain.

Worth mentioning is that every blockchain will have a little bit different approach. So using Rust isn't a guarantee that we will write Smart Contracts in the same way. It depends on tooling, library, dependencies, and overall architecture. But in general, knowing the basics of Rust should be enough to jump into the codebase quickly.

What am I building?

I wanted it to be something simple, so I choose to write a PiggyBank Smart Contract. For now, the full functionality will be basic. So the user will be able to create a Piggy and deposit some xEGLD tokens (Elrond testnet uses xEGLD). For now, I will leave all nice features like paying for the Piggy, penalties for early withdrawal of money, etc. I will try to add them later and write the second part of the article.

Ok, let's jump into the actual code and configuration.

In this article, I won't show any user interface tools. We will use erdpy, which is Elrond's command-line tool and not only because you can use it as Python SDK. With erdpy, you can build, deploy, and interact with Smart Contracts in the Elrond ecosystem. In the following articles, we will create some UI for that.

For simplicity, I will show you how to generate a pem key file. This way, it will be simpler to sign transactions. Of course, you can also use the JSON file and password if you want. For such a case, check the erdpy contract deploy --help.

So we will deploy and interact with the Smart Contract using erdpy, and then I will write some words about coding in Rust using Elrond's libraries.

Let's start by installing the erdpy

You can work with the tool on macOS and Linux. Here are the detailed guides on how to install it: https://docs.elrond.com/sdk-and-tools/erdpy/installing-erdpy/.

Installation with erdpy-up should be straightforward. I had no problems with Linux. For macOS, I had to add a configuration line in my zshrc file. You'll find how to do this in Elrond's docs. You can always check the walkthrough video.

Building the Smart Contract

After we have our erdpy installed already, you can clone the PiggyBank Smart Contract repo: https://github.com/juliancwirko/elrond-simple-sc, and in its directory run:

erdpy contract build

Worth mentioning that erdpy allows creating a new Smart Contract using predefined templates. But for simplicity, we will work on my GitHub repository, especially since some templates were also outdated. The ecosystem is very dynamic. So I don't blame it.

The first erdpy build will also handle all dependencies and will prepare the whole environment for us. So it will be a little bit slower than the next ones.

Elrond wallets

First of all, we need to have testnet wallets. You can create them here: https://testnet-wallet.elrond.com/create. Create two, write down secret phrase words in the correct order, then download the JSON files. We will need them to be able to sign transactions. You'll read about it later. Also, save passwords. Check the walkthrough video if you don't know how to do that here.

For a better demo effect, we will use two wallets. The first one will be the Smart Contract owner, and the second one will interact with it.

Both wallets should have pem files derived from seed phrases. You'll see how to do this soon.

Important! You can reconfigure all and use devnet instead of testnet. Switch to --chain="D" and --proxy="https://devnet-gateway.elrond.com" in erdpy commands. Then You would also use devnet wallets.

Fund your testnet wallets

What is important is that we need to have some xELGD on these wallets for transaction fees. Otherwise, we won't be able to do anything. Because this is a testnet, we can get the tokens using a faucet. The only one which I could find is this one: https://r3d4.fr/elrond/testnet/. So let me know if there are any others.

Paste your wallet address there and define how many tokens you want to claim. Be aware of the limits there.

Important! The faucet mentioned here isn't an official tool, so be aware that it can stop working or it won't send you the amount you want at the moment. You can reconfigure all steps mentioned here and use the devnet instead of the testnet. Then you will be able to use the official faucet built into the devnet's web wallet.

Signing transactions

To sign transactions with erdpy, you will need to provide a pem file, or if you want, you can also pass the JSON file with a password file which is more complicated, so let's see how to derive our pem file. Remember that this file should be private, don't publish it anywhere, especially if this is a pem file of a mainnet wallet.

To derive your pem file, you would need to call erdpy:

erdpy --verbose wallet derive ./walletKey.pem --mnemonic

Where ./walletKey.pem is the path and name of your generated pem file. After running this command, you would need to provide all secret words in exact order. You can check the video attached below if you are not sure what to do.

Smart Contract deployment

The first of our wallet addresses will be the owner of the Smart Contract. So we need to get its pem file and deploy the contract. We can do this using:

erdpy --verbose contract deploy --chain="T" --project=elrond-simple-sc --pem="wallets/walletKey.pem" --gas-limit=80000000 --proxy="https://testnet-gateway.elrond.com" --recall-nonce --send

We assumed that our PiggyBank Smart Contract is cloned to the elrond-simple-sc directory, and our pem file is located in wallets/walletKey.pem.

After that, in the terminal, we will get our Smart Contract's address. We will need it later. Please note it down. It should be located at the end of the relatively big response object, under the address key.

Interacting with the Smart Contract

At the current state of the Smart Contract, we can add xEGLD tokens, we can also check how much we have, and we can also do payout if, of course, we will be able to do it. We assume that when creating a Piggy, we will configure the lock time before which we can't withdraw our tokens.

Let's create our Piggy. We can do this using:

erdpy --verbose contract call {smart_contract_address_here} --chain="T" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="create_piggy" --arguments 1628619457 --proxy="https://testnet-gateway.elrond.com" --recall-nonce --send

We are sending a transaction that will call our create_piggy function. You'll read more about it and others in the following sections of the article. For now, let's focus on interaction with the PiggyBank Smart Contract.

As you can see, we have the {smart_contract_address_here} variable. Please replace it with the previously saved Smart Contract address. We also have an argument which is a Unix Timestamp in the future. It is lock time for our PiggyBank. You also probably noted that now we use walletKey2.pem because we use another wallet to interact with our Smart Contract. With this wallet, I am not an owner of the Smart Contract anymore.

When our Piggy is ready, we want to lock some tokens there. You can do this using:

erdpy --verbose contract call {smart_contract_address_here} --chain="T" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="add_amount" --value=1000000000000000000 --proxy="https://testnet-gateway.elrond.com" --recall-nonce --send

As you can see, we pass 1000000000000000000 to our add_amount function. The big number corresponds to 1 xEGLD. The denomination for Elrond's native tokens is 18. We also need to pass the Smart Contract's address and use the second wallet's pem file.

After every transaction, you can check the results in the testnet explorer here. You'll find all erdpy commands in the readme of PiggyBank SC repo. Remember that all is also presented in the walkthrough video.

Ok, let's check what we already have there in our PiggyBank:

erdpy --verbose contract call {smart_contract_address_here} --chain="T" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="amount" --proxy="https://testnet-gateway.elrond.com" --recall-nonce --send

The response from the Smart Contract will be in HEX format. You can find many hex-to-decimal tools on the net to convert it and check that we have the same amount as we sent. So 1000000000000000000.

Suppose you would like to claim back all your tokens. You can do this using:

erdpy --verbose contract call {smart_contract_address_here} --chain="T" --pem="wallets/walletKey2.pem" --gas-limit=5000000 --function="pay_out" --proxy="https://testnet-gateway.elrond.com" --recall-nonce --send

When we call our pay_out function before our lock time, we will get a failure for the transaction. We will get the information that we can't withdraw it yet.

That's all methods on which our PiggyBank Smart Contract reacts.

Hopefully, you are not lost yet. There was a lot of information here. Please watch the walkthrough video if you need it. The following section will be more about the code.

A lot of information, and we even didn't look into the code.

Mainly because we don't have any UI yet, we could hide all that operations under a friendly user interface. But it is good to know how it works under the hood first. It is why I use erdpy here.

I will try to prepare the UI, which will handle all the interactions. It will also be fun to explore Elrond's JavaScript tooling.

Ok, let's see what we have in the repository.

The repository contains a Rust project, which relies on Elrond's dependencies, so writing Smart Contracts is relatively easy, even for someone who doesn't know Rust much.

The main dependency is elrond-wasm-rs, and this is the whole framework. Unfortunately, I wasn't able to find good docs on that part. The only docs which are great, by the way, are about annotations here. But many examples and the possibility of going through the elrond-wasm-rs library's code were enough for me to understand basics concepts. So that's good because it shows that even without good documentation, the overall codebase is in good shape.

Main code concepts in the PiggyBank Smart Contract

There are two main concepts:

  • Each Elrond Smart Contract has access to the on chain storage. I use these to store the information about the amount, owner, and lock time for all users.
  • I use #[endpoint] annotation for my functions to expose them to the public. Endpoints are the public methods of contracts, called using transaction calls the same as described above.

Lets take a look at one of the functions to demonstrate the main idea:

#[endpoint]
fn create_piggy(&self, lock_time: u64) -> SCResult<()> {
  let caller = self.blockchain().get_caller();
  require!(self.is_lock_time_empty(&caller) == true, "You already have one piggy");
  require!(lock_time > self.get_current_time(), "Lock time shouldn't be set for now");
  self.set_lock_time(&caller, lock_time);
  Ok(())
}

Storage is defined here like:

#[storage_set("lock_time")]
fn set_lock_time(&self, piggy_owner: &Address, lock_time: u64);
#[storage_get("lock_time")]
fn get_lock_time(&self, piggy_owner: &Address) -> u64;

As you can see, with annotations delivered by elrond-wasm-rs, it is simple to define methods and storage. Besides annotations, there is a lot of helpful stuff, for example, on the self.blockchain().

All other functions are very similar, so I won't paste them here, but you can always study the code.

Summary

Elrond blockchain gives a lot of great tools. We can use erdpy to build, deploy and interact with Smart Contracts. And this is just one of many tools, a way to start learning.

Of course, there are also things about which I am not sure yet. For example, gas prices calculation. I am not sure how to automate gas price predictions. There is some info in the docs, but this topic is still hard to figure out, at least for me. I will take a closer look later.

The PiggyBank Smart Contract is simple. There is a lot of other exciting stuff, like events and callbacks. I need to learn more about the topic.

What I plan to do next is, for sure, a UI app and maybe some backend app for the PiggyBank. There are also great tools to use. I already had some time to play with them building the frontend for NFT on Elrond. I can reuse a lot of the tech behind it. The Elrond team open-sources a lot of tools. Check out their GitHub.

I would also want to check the whole testing approach for writing Smart Contracts. It is also an essential part, and it seems that Elrond has it sorted out in a friendly way.

I hope that his article will be helpful.

Let me know if you have any questions, and please follow me on Twitter and Github to be up to date with following articles around the topic.