4

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!

1 Answer 1

3

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()) 
}
3
  • 1
    This code seems ok to launch new transactions from goLang code, but this is not what I need. Instead, I want to call a contract method without initiating a new transaction with a new txHash, just diverting the flow of geth code to execute the method inside the very tx that is currently running, that is an internal transaction. Mar 28, 2019 at 13:14
  • 1
    @JuanIgnacioPérezSacristán did you found your answer ? I have a similar need to read a smart contract from within a fork consensus. The smart contract is at a known fixed address Nov 28, 2021 at 19:49
  • Hi Guenole, I used evm.callCode and it worked ok. You may explore the code at github.com/juanmarketpayio/go-ethereum/blob/… Nov 30, 2021 at 12:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.