Learning a programming language requires you to write and play around with code to get to know different ways to use it and understand its quirks. Learning Solidity is no exception to that.

However, when just starting out learning Web3 development, you need to deal with numerous technicalities before being able to actually write and run your Smart Contract. From gathering all the necessary tools to finding trusted blockchain service providers, it takes a lot of time and effort to set up your environment and get to the fun part – coding in Web3.

But, what if you could skip the lengthy setup part of the process and run your code in an instant, without sacrificing the quality of your work and learning experience? What if you could have everything already laid out for you so you can jump into the prototyping cycle and focus on the creative part?

Tenderly Sandbox allows you to simplify the process and skip time-consuming tasks such as setting up a MetaMask account, connecting to a test network, or dealing with any local dependencies. It allows you to jump right in and start experimenting with Solidity Smart Contracts. It’s a powerful, zero-setup, isolated environment powered by Tenderly Forks, bringing Solidity and JavaScript together.

What does the prototyping cycle look like in Sandbox?

At the beginning of your learning and prototyping journey, you’re likely to go through a typical prototyping cycle: set up your environment → write → deploy → run → examine the result → solve bugs → refactor and improve → deploy → repeat.

Of course, you want to speed up this process and start playing around with actual Solidity code instead of doing the development grind of environment setup. And you also want to be as nimble as possible, which depends on short feedback time: the time between making a change and getting the results after you run your code.

Sandboxes make the prototyping cycle significantly more efficient as you can focus more on the creative part, coding, rather than technicalities. Once in Sandbox, you can jump straight to writing, while the deploy and run stage happen seamlessly and extremely fast. The feedback cycle becomes shorter, enabling faster iterations on your code.

And, let’s face it, as you’re learning and prototyping your Smart Contracts, you’ll experience failures and errors. To address these, you can jump right into Tenderly Debugger and other features that can help you understand which piece of code failed, why it failed, and how to fix it.

Once you’ve figured out how to adjust your contract, you want a fast way to re-run it and ensure it works. Simply go back to your Sandbox environment, make any necessary adjustments, and re-run the Sandbox to verify your solution. Such immediate deployments and runs allow you to see how your contract executes and identify any potential bugs from the start.

How to build & interact with a Smart Contract?

As we mentioned, the best way to learn coding in Web3 is to actually do it. So, ready to get down to business?

The problem: Message Box works as a postal box. Writers can store an integer value for a recipient address. Writers can store a value for themselves. A reader can get the value stored for them (based on their Ethereum address), if any exists.

Iteration 1: Create the first prototype

The first quest: Find a way to simultaneously store a value for multiple addresses. In other words, you need a mapping from address to a uint32 value, as well as the set and get methods. Let’s take the simplest case in the first iteration: set stores a message for the sender and get reads the one previously stored. We’ll then improve it in the second iteration.

Here’s the first draft of the Smart Contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
 
contract MessageBox {
   mapping(address => uint32) messages;
 
   function set(uint32 val) public {
       messages[msg.sender] = val;
   }
 
   function get() public view returns (uint) {
       return messages[msg.sender];
   }
}
 

Let’s explore this contract a bit:

  • The messages is the mapping allowing to store a single value (uint32 part of the mapping) for different addresses.
  • The msg.sender in set is the address that sent and signed the transaction. It serves as a key for storing the given value val. Essentially, the sender is leaving a message to themselves.
  • The msg.sender in get enables access to the value stored in the storedValues mapping. Effectively, the caller of get receives the message stored for them.

To put this into practice, head over to the Message Box Starter example in Sandbox. On the left, you’ll see your Smart Contract. On the right, you’ll see a minimal example demonstrating how to set and retrieve a value.

Run 1

Next, click Run and you’ll see a list of transactions in just a few seconds. These transactions are the state-changing calls the JS script made.

Your Sandbox view should look like the following screenshot:

The first transaction on the list is a Contract Creation transaction. This one comes from await factory.deploy() and it “installs” the bytecode of your contract on the blockchain, making it executable. The second transaction calls the set method on SimpleStorage.

The transactions you see here are, in fact, sent to a Tenderly Fork which acts in place of a network and simulates transaction execution. Every time you Run your Sandbox, a new set of contracts is deployed and a new set of transactions is executed. Being executed on a Fork, these transactions aren’t visible on block explorers.

Iteration 2: Expand the contract

So far so good, but let’s make things interesting by adding another functionality.

The second quest: Make it possible to store a value for a recipient. Think of it as leaving a message for somebody. When the recipient address calls get, they should get the value stored here.

To achieve this, let’s introduce a function setForAddress that accepts an address for the recipient and the value they should receive.

function setForAddress(address recipientAddress, uint32 val) public {
   storedValues[recipientAddress] = val;
   emit Stored(msg.sender, recipientAddress, val);
}

Here, we’re defining a Solidity event Stored which gets emitted upon calling setForAddress.

To make it a bit cleaner, let’s extract the assignment to the storedValues in a separate internal function. Here’s the complete refactored Smart Contract (ignore the Stored event for now):

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;

contract MessageBox {
    mapping(address => uint32) storedValues;
    function set(uint32 val) public {
        store(msg.sender, val);
    }
    function setForAddress(address recipientAddress, uint32 val) public {
        store(recipientAddress, val);
    }
    function store(address recipientAddress, uint32 val) internal {
        storedValues[recipientAddress] = val;
        emit Stored(msg.sender, recipientAddress, val);
    }
    function get() public view returns (uint) {
        return storedValues[msg.sender];
    }
    event Stored(address from, address to, uint32 val);
    } 
    event Stored(address from, address to, uint32 val);
}
 


Run 2

Once again, click the Run button and the list of the transactions your Sandbox code executed will show up shortly. Click the Expand button in the Transaction section to get a full-screen view of the transactions. Here, you’ll see two calls: set and setForAddress.

Click the second transaction (setForAddress), which will take you to the Tenderly Dashboard. Next, click the View input data button for additional information. This way, you can see the exact arguments that were sent along with the transaction.

Here’s what your view should look like:

Examine your transaction data

While you’re in the Tenderly Dashboard, you can gather other important information about your executed transactions:

State Changes: To understand how your code affects the state of the blockchain, open the State Changes tab. You should see something similar to the screenshot below. It shows that the storedValues storage variable is modified. The entry under the key 0x3dfeb36487c8b5839a0a2ab65ce4e55dbea753fa is equal to the set value – 42.

Events: The second iteration of the storage includes sending a Stored event which captures the value (val), the address that stored a value (from), and the address this value is meant for (to). If you click the Events tab, you’ll see this exact information:

To access the events in JavaScript code, add this to the bottom of the JS part of the Sandbox and observe the output:

 
const setFourTx = await simpleStorage.setForAddress(RECIPIENT_ADDRESS, 4);
// wait for transaction receipt - it holds the events
const setFourReceipt = await setFourTx.wait();
// find the first (the one and only) Stored event
const storedEvent = setFourReceipt.events.filter(e => e.event == 'Stored')[0];
 
// treat storedEvents.args as a tuple whose components
// are defined by the Event
const [addressFrom, addressTo, value] = storedEvent.args;
console.log(`Address ${addressFrom} stored value ${value} for ${addressTo} to read`);


In this example, we decided not to store information about the sender of the stored value, as it has no impact on the execution of the Smart Contract. We did emit an event Stored. You can think of it as a tuple (from, to, value). This allows us to complement the State Changes with additional information, without storing the data in the Smart Contract state.

Feel free to check out the complete Sandbox solution.

How to monitor Solidity Events?

Events are a helpful way to keep logs of major events that resulted in state changes. Event logs are complementary information to the changes of Smart Contract State. You can subscribe to these events using Ethers in your client application so you’re aware of them as soon as they happen. Alternatively, you can explore how you can set up a Tenderly Web3 Action to keep track of on-chain events and build a serverless Web3 backend for your Smart Contract.

Get to the fun part faster with Tenderly Sandbox

Tenderly Sandbox is a great option to experiment when learning Solidity and explore how different concepts of the language work together. As an already set-up environment, it allows you to dedicate most of the prototyping cycle to writing code and finding creative solutions. Plus, since other Tenderly tools are just a click away, you can easily get the valuable information you need.

However, the best way to learn and discover what this environment has to offer is through practice. So, why not try to write a voting Smart Contract using Sandbox or take a look at this example of creating a subcurrency contract? Take distractions out of the equation and zero in on actual Solidity coding.