DEV Community

Cover image for Solidity Basics for JavaScript Devs Part 2
K (he/him) for Fullstack Frontend

Posted on

Solidity Basics for JavaScript Devs Part 2

After my first article about Solidity basics for JavaScript devs got so much attention, I'm writing a second one!

I'm currently working through a beginner smart contract development book, and now I'm doing the main project, a DApp fundraiser.

The book is written for Truffle, web3.js, and JavaScript, and I replaced the tools with Hardhat, Ethers.js, and TypeScript to spice things up a bit.

Here are my last findings that threw me off a bit, so I think they might be interesting for newcomers!

Solidity Events are for the Frontend

Solidity has an event construct/type. It allows you to define specific events for your smart contract that can emit when things you deem interesting.

event MyEvent( uint256 value1, uint256 value2);

function f() public {
  emit MyEvent(123, 456);
}
Enter fullscreen mode Exit fullscreen mode

Interesting for whom? For your frontend code!

If I understood it correctly, event data would be stored in the blockchain, but it isn't accessible within smart contracts.

Event data is for listeners from outside the blockchain.

Your frontend can add event listeners for these events, and then, when it starts a transaction, these events will be emitted, and you can do things in the frontend.

smartContract.on("MyEvent", (valueA, valueB) => {
  console.log(valueA, valueB);
})

await smartContract.f();
Enter fullscreen mode Exit fullscreen mode

Ethers.js Uses BigNumber Instead of BigInt

Solidity usually has to handle huge integers, too big for the Number type of JavaScript. That's why Ethers.js created their type, called BigNumber, to get around this problem.

Today, modern JavaScript engines all have a BigInt type that can handle such values no problem, but this wasn't always the case, and Ethers.js wanted to be backward compatible.

I don't know why they didn't use a BigInt polyfill instead, but at least they offer a method toBigInt(). But you have to use BigNumber methods for calculations!

const value1 = ethers.utils.parseEther("1")
const value2 = ethers.utils.parseEther("2")

const result = value1.add(value2)

console.log(result.toBigInt())
Enter fullscreen mode Exit fullscreen mode

Anyway, don't mistake BigNumber for BigInt or you'll have a bad time!

Setting the msg Object from Ether.js

There are some global variables inside your Solidity smart contract generated automatically before your function is called.

One of them is called msg, and it contains data that isn't explicitly passed via function arguments, like msg.sender for the address that called the function or msg.value for the amount of Ether that was sent with the function call.

function f(uint256 arg1, uint256 arg2) public payable {
  // These are obviously arguments
  uint256 a = arg1 + arg2;

  // But where do these come from?
  address x = msg.sender; 
  uint256 y = msg.value;
}
Enter fullscreen mode Exit fullscreen mode

As this data isn't a function argument, how do you pass it to the smart contract from the Ethers.js side?

An overrides object is passed as the last argument to such a (payable) function, after all the regular arguments. Other values, like msg.sender are implicitly set on the smart contract side of things.

const overrides = {value: 123}
await smartContract.payableFunction(arg1, arg2, overrides)
Enter fullscreen mode Exit fullscreen mode

Multiple returns will become an Array in Ethers.js

Solidity allows returning multiple values from one function.

function f() public returns(uint256 a, uint256 b) {
  return (123, 456);
}
Enter fullscreen mode Exit fullscreen mode

I saw some examples, seemingly for web3.js, that would use an object as a return value on the JavaScript side.

const {a, b} = await smartContract.f();
Enter fullscreen mode Exit fullscreen mode

This didn't work for me; I used an array to extract the return values depending on their position.

const [a, b] = await smartContract.f();
Enter fullscreen mode Exit fullscreen mode

Using Waffle with Chai for Tests

The book I'm reading used low-level assertions with some try-catch constructs to test smart contract-specific behavior. I guess Waffle wasn't a thing back then.

To test events, you can use an asynchronous call to expect.

it("emits", async () => {
  await expect(smartContract.f()).to.emit("EventType")
})
Enter fullscreen mode Exit fullscreen mode

You can use an asynchronous call to expect with reverted to test that your contract reverts correctly.

it("emits", async () => {
  await expect(smartContract.f()).to.be.revertedWith("Error Message")
})
Enter fullscreen mode Exit fullscreen mode

Summary

Web3 is an interesting topic, and Solidity is certainly a different language than I expected. It's simple in the sense that JavaScript is simple, but the devil lies in the detail.

I hope I could clear some things up for you.

Discussion (2)

Collapse
nedgar profile image
Nick Edgar • Edited on

Yes, the BigNumber / BigInt thing is a bit of a pain, particularly when dealing with async results, as one often has to with Ethers.js. e.g. const supply = (await contract.totalSupply()).toBigInt(). It's pretty tedious.

I suggest either converting to BigInt early (for more modern JS) in the rest of the code, or to mostly ignore BigInt and just stick with BigNumber, like some dev from 2 years ago lol.

Adapting your example above to first approach:

const result = value1.toBigInt() + value2.toBigInt();
console.log(result);
Enter fullscreen mode Exit fullscreen mode

Fortunately, Ethers.js now considers BigInt to be "BigNumberIsh", so it accepts BigInts as arguments for uint etc.

Collapse
nedgar profile image
Nick Edgar • Edited on

First snippet there could also be done as:
const supply = await contract.totalSupply().then(BigInt)
or
const supply = BigInt(await contract.totalSupply()).