From c0fc3f14c6304eb421a6b209db5b0e884352622f Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 30 May 2024 22:03:31 +0200 Subject: [PATCH] wip - golang tests --- .env.sample | 3 + .gitignore | 2 + README.md | 13 +++ cache/.gitignore | 1 + client.go | 208 ++++++++++++++++++++++++++++++++++++++++++ contracts/Greeter.sol | 27 ++++++ go.mod | 34 +++++++ go.sum | 65 +++++++++++++ 8 files changed, 353 insertions(+) create mode 100644 .env.sample create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cache/.gitignore create mode 100644 client.go create mode 100644 contracts/Greeter.sol create mode 100644 go.mod create mode 100644 go.sum diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..d2b9ceb --- /dev/null +++ b/.env.sample @@ -0,0 +1,3 @@ +PRIVATE_KEY=YOUR_PRIVATE_KEY +INFURA_URL='https://testnet.hashio.io/api' +CHAIN_ID=296 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..431722d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +hedera-golang-example-project diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d12bac --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +1. Install go: https://go.dev/doc/install +2. sudo apt install solc +3. https://docs.soliditylang.org/en/latest/installing-solidity.html +2. go get github.com/ethereum/go-ethereum/ethclient +3. go get github.com/ethereum/go-ethereum + go get github.com/ethereum/go-ethereum/accounts/abi/bind + go get github.com/ethereum/go-ethereum/crypto +4. go get github.com/joho/godotenv +5. export GOPATH=$HOME/go + export PATH=$PATH:$GOPATH/bin +5. go install github.com/ethereum/go-ethereum/cmd/abigen@latest +3. go build . +4. ./hedera-golang-example-project - run tests diff --git a/cache/.gitignore b/cache/.gitignore new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/cache/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/client.go b/client.go new file mode 100644 index 0000000..53793af --- /dev/null +++ b/client.go @@ -0,0 +1,208 @@ +package main + +import ( + "bytes" + "context" + "crypto/ecdsa" + "encoding/json" + "fmt" + "log" + "math/big" + "os" + "os/exec" + "strings" + "io/ioutil" + "path/filepath" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/joho/godotenv" +) + +type CompilationResult struct { + Contracts map[string]struct { + ABI json.RawMessage + Bin string + Metadata string + } `json:"contracts"` +} + +func compileContract(filePath string) (string, []byte, error) { + cmd := exec.Command("solc", "--combined-json", "abi,bin", filePath) + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return "", nil, fmt.Errorf("failed to compile contract: %v", err) + } + var result CompilationResult + err = json.Unmarshal(out.Bytes(), &result) + if err != nil { + return "", nil, fmt.Errorf("failed to parse solc output: %v", err) + } + for _, contract := range result.Contracts { + abiBytes, err := json.Marshal(contract.ABI) + if err != nil { + return "", nil, fmt.Errorf("failed to marshal ABI: %v", err) + } + bin := contract.Bin + return bin, abiBytes, nil + } + + return "", nil, fmt.Errorf("no contract found in solc output") +} + +func loadFromFile(filename string) (string, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "", err + } + return string(data), nil +} + +func saveToFile(filename string, data []byte) error { + return ioutil.WriteFile(filename, data, 0644) +} + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatalf("Error loading .env file") + } + infuraURL := os.Getenv("INFURA_URL") + privateKeyHex := os.Getenv("PRIVATE_KEY") + client, err := ethclient.Dial(infuraURL) + if err != nil { + log.Fatal(err) + } + fmt.Println("we have a connection") + privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x") + privateKey, err := crypto.HexToECDSA(privateKeyHex) + if err != nil { + log.Fatalf("Failed to parse private key: %v", err) + } + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + log.Fatal("Failed to cast public key to ECDSA") + } + fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + fmt.Printf("Using address: %s\n", fromAddress.Hex()) + balance, err := client.BalanceAt(context.Background(), fromAddress, nil) + if err != nil { + log.Fatalf("Failed to get balance: %v", err) + } + fmt.Printf("Account balance: %s\n", balance.String()) + nonce, err := client.PendingNonceAt(context.Background(), fromAddress) + if err != nil { + log.Fatalf("Failed to get nonce: %v", err) + } + fmt.Printf("Using nonce: %d\n", nonce) + gasPrice, err := client.SuggestGasPrice(context.Background()) + if err != nil { + log.Fatalf("Failed to get gas price: %v", err) + } + fmt.Printf("Using gas price: %s\n", gasPrice.String()) + bin, abiBytes, err := compileContract("contracts/Greeter.sol") + if err != nil { + log.Fatalf("Failed to compile contract: %v", err) + } + cacheDir := "cache" + if _, err := os.Stat(cacheDir); os.IsNotExist(err) { + err = os.Mkdir(cacheDir, 0755) + if err != nil { + log.Fatalf("Failed to create cache directory: %v", err) + } + } + err = saveToFile(filepath.Join(cacheDir, "Greeter.bin"), []byte(bin)) + if err != nil { + log.Fatalf("Failed to save bytecode: %v", err) + } + err = saveToFile(filepath.Join(cacheDir, "Greeter.abi"), abiBytes) + if err != nil { + log.Fatalf("Failed to save ABI: %v", err) + } + fmt.Println("Saved ABI and bytecode to cache files.") + parsedABI, err := abi.JSON(bytes.NewReader(abiBytes)) + if err != nil { + log.Fatalf("Failed to parse ABI: %v", err) + } + bytecode := common.FromHex(bin) + auth := bind.NewKeyedTransactor(privateKey) + auth.Nonce = big.NewInt(int64(nonce)) + auth.Value = big.NewInt(0) + auth.GasLimit = uint64(3000000) + auth.GasPrice = gasPrice + totalCost := new(big.Int).Mul(big.NewInt(int64(auth.GasLimit)), gasPrice) + fmt.Printf("Total transaction cost: %s\n", totalCost.String()) + + if balance.Cmp(totalCost) < 0 { + log.Fatalf("Insufficient funds: balance %s, needed %s", balance.String(), totalCost.String()) + } + initialGreeting := "Hello, World!" + fmt.Printf("Transaction details:\n") + fmt.Printf(" From Address: %s\n", fromAddress.Hex()) + fmt.Printf(" Nonce: %d\n", nonce) + fmt.Printf(" Gas Price: %s\n", gasPrice.String()) + fmt.Printf(" Gas Limit: %d\n", auth.GasLimit) + fmt.Printf(" Initial Greeting: %s\n", initialGreeting) + addressFilePath := "cache/address" + var contractAddress common.Address + if _, err := os.Stat(addressFilePath); err == nil { + address, err := loadFromFile(addressFilePath) + if err != nil { + log.Fatalf("Failed to load contract address: %v", err) + } + contractAddress = common.HexToAddress(address) + fmt.Printf("Contract already deployed at address: %s\n", address) + } else { + address, tx, _, err := bind.DeployContract(auth, parsedABI, bytecode, client, initialGreeting) + if err != nil { + log.Fatalf("Failed to deploy contract: %v", err) + } + contractAddress = address + + fmt.Printf("Contract deployed to address: %s\n", address.Hex()) + fmt.Printf("Transaction hash: %s\n", tx.Hash().Hex()) + + err = saveToFile(addressFilePath, []byte(address.Hex())) + if err != nil { + log.Fatalf("Failed to save contract address: %v", err) + } + fmt.Println("Saved contract address to file.") + } + interactWithContract(client, contractAddress, privateKey, parsedABI) +} + +func interactWithContract(client *ethclient.Client, contractAddress common.Address, privateKey *ecdsa.PrivateKey, parsedABI abi.ABI) { + chainId := big.NewInt(296) + auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainId) + auth.GasLimit = uint64(3000000) + newGreeting := "Hello, Ethereum!" + fmt.Printf("Setting new greeting to: %s\n", newGreeting) + + boundContract := bind.NewBoundContract(contractAddress, parsedABI, client, client, client) + + setGreetingTx, err := boundContract.Transact(auth, "setGreeting", newGreeting) + if err != nil { + log.Fatalf("Failed to set greeting: %v", err) + } + fmt.Printf("Set greeting transaction hash: %s\n", setGreetingTx.Hash().Hex()) + callMsg := ethereum.CallMsg{ + To: &contractAddress, + Data: common.FromHex("0xcfae3217"), // greeting method selector. + } + + res, err := client.CallContract(context.Background(), callMsg, nil) + if err != nil { + log.Fatalf("Failed to get greeting: %v", err) + } + length := new(big.Int).SetBytes(res[32:64]).Int64() + greeting := string(res[64 : 64+length]) + greeting = strings.TrimRight(greeting, "\x00") + + log.Printf("Greeting: %s", greeting) +} diff --git a/contracts/Greeter.sol b/contracts/Greeter.sol new file mode 100644 index 0000000..76c8292 --- /dev/null +++ b/contracts/Greeter.sol @@ -0,0 +1,27 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +contract Greeter { + string private greeting; + + event GreetingSet(string greeting); + + //This constructor assigns the initial greeting and emit GreetingSet event + constructor(string memory _greeting) { + greeting = _greeting; + + emit GreetingSet(_greeting); + } + + //This function returns the current value stored in greeting variable + function greet() public view returns (string memory) { + return greeting; + } + + //This function sets the new greeting msg from the one passed down as parameter and emit event + function setGreeting(string memory _greeting) public { + greeting = _greeting; + + emit GreetingSet(_greeting); + } +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..23b2610 --- /dev/null +++ b/go.mod @@ -0,0 +1,34 @@ +module hedera-golang-example-project + +go 1.22.3 + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/ethereum/c-kzg-4844 v1.0.0 // indirect + github.com/ethereum/go-ethereum v1.14.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/holiman/uint256 v1.2.4 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/supranational/blst v0.3.11 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/tools v0.20.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e2fcaaf --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= +github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= +github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/go-ethereum v1.14.3 h1:5zvnAqLtnCZrU9uod1JCvHWJbPMURzYFHfc2eHz4PHA= +github.com/ethereum/go-ethereum v1.14.3/go.mod h1:1STrq471D0BQbCX9He0hUj4bHxX2k6mt5nOQJhDNOJ8= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=