Executing A Smart Contract in geth

Since Binance Smart Chain and more specifically DeFi on BSC are a thing, I’ve been learning the amazing ways of Smart Contracts, and while many documentation exist on this topic, I didn’t find a clear one on how to “simply” execute a Smart Contract on geth, aka Go Ethereum, the most used Ethereum implementation and client.

Geth can interact with the Ethereum blockchain using, well… Javascript, and more specifically an (old) implementation of web3.js.

We will use Bunny Pancake as our DEX example, and more precisely the BUNNY pool smart contract, aka 0xCADc8CB26c8C7cB46500E61171b5F27e9bd7889D.

On BscScan you can directly call functions from any Smart Contract using the ContractRead Contract menu, for example, to know a wallet balance on Bunny Pancake, simply enter the wallet address in the balanceOf area (the example address was taken from the latest transaction, i.e. it’s not mine):

balanceOf

That’s already super cool, but what if we want to query this Smart Contract using geth, for example to query an archive node in order to check an ancient transaction?

This turned out to be quite a maze, and it took me a long time to understand that geth web3 implementation was 0.2, while current version is 1.3.
First thing to bookmark is the actual web3.js 0.2 documentation, and then some this useful threads on Stack Exchange.

Putting the pieces together, I came out with a working implementation, let’s go over it.

The first data we need is the ABI of the Smart Contract, simply put, the SC’s functions prototypes. Once again, BscScan to the rescue; In the ContractCode section, you will find an Contract ABI zone, where you can copy or export this ABI. We will create a simple bunny.abi file where we will have the following:

abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},...

Now for the main script, we want to use the contract function to instanciate the Smart Contract, and we can see that this function takes 2 arguments, the abi and the contract address. We got the first from BscScan, and we obviously have the address of the SC we want to query.
Here’s what we have from now:

loadScript("bunny.abi")
caddr = "0xCADc8CB26c8C7cB46500E61171b5F27e9bd7889D"
c = web3.eth.contract(abi).at(caddr)

Note that this can be tested live on the geth JavaScript console.

Web3’s call() method looks like this:

web3.eth.call({to: caddr, data: d})

The first parameter is the SC’s address (yes, once again, don’t ask…) and the second is an object returned by the getData method, provided by every function. It is called with the wanted function parameters, here, balanceOf() takes one parameter, the wallet address we want to check. Here’s the corresponding code:

d = c.balanceOf.getData("0xB1518fBA56E6a718f4e65456D6c8e1eCbA3EF47A")
r = web3.eth.call({to: caddr, data: d})

Again, this address used is taken from BscScan latest transactions (i.e. NOT MINE).

In order to convert the value returned by the call() function to actual coin value, we should use web3.toWei(), so the last line will transform to:

r = web3.toWei(web3.eth.call({to: caddr, data: d}))

Last but not least, if we have access to an archive node (if you know a free BSC archive node please let me know!), we can call this function for any given time / bloc by setting eth.defaultBlock to the desired block, i.e.

eth.defaultBlock = web3.toHex(block)

There we go, happy tracking!