[Ethereum] Calling contract method internally from geth code

go-ethereumgolangproject-fork

On developing a custom geth fork there is the need to call a specific hardcoded smart contract when ETH transfer occur. This is not an Ethereum normal behaviour but a new feature we want to implement on the fork. The calling of the contract will be done inside the very transaction that triggers the whole process, so creating a new transaction to call the contract is not an option. The atomic transaction must include the internal call of the contract (this special contract will have a hardcoded address all the time).

The main change of the code in this fork will be at https://github.com/ethereum/go-ethereum/blob/master/core/evm.go#L93

// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
    db.SubBalance(sender, amount)
    db.AddBalance(recipient, amount)
}

to something like this:

// Transfer is performed on an ERC-20 contract
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
    mySpecialContract.transfer(recipient, amount {from: sender})
}

So, questions are:

Is that possible to do?

Should I define the "mySpecialContract" object first? How?

How can GoLang call an specific method of a given contract?

Having the address of the contract, the method name and parameters (the whole ABI actually) is there any example code to invoke this kind of methods?

By the way, I feel like there is a function on geth code that could do that: core\vm\evm.go(43)

// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {

Someone can confirm? Thx!

Best Answer

Here's an example of how to invoke a smart contract method in Go. This should give you an idea of how to go about it.

MyContract.sol example contract

pragma solidity >=0.4.21 <0.6.0;

contract MyContract {
  function transfer(address payable recipient, uint256 amount) public {
    // your code
  }
}

Generate ABI using solc and Go package using abigen:

solc --abi MyContract.sol
solc --bin MyContract.sol
abigen --bin=MyContract_sol_MyContract.bin --abi=MyContract_sol_MyContract.abi --pkg=store --out=contracts/MyContract.go

main.go

package main

import (
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"

    contract "./contracts" // the contract packages you generated with abigen
)

func main() {
    // initialize eth client
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    // load the signing key
    privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
    if err != nil {
        log.Fatal(err)
    }

    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
    }

    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    // estimate gas price
    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    auth := bind.NewKeyedTransactor(privateKey)
    auth.Nonce = big.NewInt(int64(nonce))
    auth.Value = big.NewInt(0)     // in wei
    auth.GasLimit = uint64(300000) // in units
    auth.GasPrice = gasPrice

    // your deployed smart contract address
    address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")

    // get an instance of your smart contract
    instance, err := contract.NewMyContract(address, client)
    if err != nil {
        log.Fatal(err)
    }

    recipient := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")

    amount := new(big.Int)
    amount.SetString("1000000000000000000000", 10) // sets the value to 1000 tokens, in the token denomination

    // calling your smart contract method
    tx, err := instance.Transfer(auth, recipient, amount)
    if err != nil {
        log.Fatal(err)
    }

    // transaction hash
    fmt.Printf("tx sent: %s", tx.Hash().Hex()) 
}
Related Topic