From 77d47ce651c16d0df966adf5c46c15321d264b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Sun, 4 Sep 2016 15:23:04 +0300 Subject: [PATCH] accounts/abi/bind: call gas limit and transact gas tuning --- accounts/abi/bind/base.go | 23 +++++++++--- accounts/abi/bind/bind_test.go | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 965f51e856..51ad61f340 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -33,9 +33,15 @@ import ( // sign the transaction before submission. type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error) +// GasTunerFn is a callback function to modify the gas limit of a transaction +// estimated by the backend node. It's purpose is to allow providing some wiggle +// room for approximate estimations and/or concurrent state changes. +type GasTunerFn func(gas *big.Int) *big.Int + // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { - Pending bool // Whether to operate on the pending state or the last known one + Pending bool // Whether to operate on the pending state or the last known one + GasLimit *big.Int // Gas limit allowance of the call operation (nil = use backend default) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } @@ -47,9 +53,10 @@ type TransactOpts struct { Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state) Signer SignerFn // Method to use for signing the transaction (mandatory) - Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) - GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) - GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%) + Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds) + GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) + GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate) + GasTuner GasTunerFn // Modifier for the gas limit estimated by the node (nil = identity) Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } @@ -108,7 +115,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, return err } var ( - msg = ethereum.CallMsg{To: &c.address, Data: input} + msg = ethereum.CallMsg{To: &c.address, Data: input, Gas: opts.GasLimit} ctx = ensureContext(opts.Context) code []byte output []byte @@ -203,6 +210,12 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i if err != nil { return nil, fmt.Errorf("failed to estimate gas needed: %v", err) } + // If custom gas estimation post-processing was requested, execute it + if opts.GasTuner != nil { + if gasLimit = opts.GasTuner(gasLimit); gasLimit == nil { + return nil, fmt.Errorf("gas tuner returned nil") + } + } } // Create the transaction, sign it and schedule it for execution var rawTx *types.Transaction diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 67c09c3ada..6c5e63f0c5 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -365,6 +365,72 @@ var bindTests = []struct { } `, }, + // Tests that gas limits can be configured for calls, transacts and also estimation tunings + { + `GasLimiter`, + ` + contract GasLimiter { + string public field; + + function SetField(string value) { + // This check will screw gas estimation! Good, good! + if (msg.gas < 100000) { + throw; + } + field = value; + } + } + `, + `606060405261021c806100126000396000f3606060405260e060020a600035046323fcf32a81146100265780634f28bf0e1461007b575b005b6040805160206004803580820135601f8101849004840285018401909552848452610024949193602493909291840191908190840183828082843750949650505050505050620186a05a101561014e57610002565b6100db60008054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281529291908301828280156102145780601f106101e957610100808354040283529160200191610214565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f16801561013b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b505050565b8060006000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106101b557805160ff19168380011785555b506101499291505b808211156101e557600081556001016101a1565b82800160010185558215610199579182015b828111156101995782518260005055916020019190600101906101c7565b5090565b820191906000526020600020905b8154815290600101906020018083116101f757829003601f168201915b50505050508156`, + `[{"constant":false,"inputs":[{"name":"value","type":"string"}],"name":"SetField","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"field","outputs":[{"name":"","type":"string"}],"type":"function"}]`, + ` + // Generate a new random account and a funded simulator + key, _ := crypto.GenerateKey() + auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)}) + + // Deploy a gas limiter contract and check that estimation fails miserably indeed + _, _, limiter, err := DeployGasLimiter(auth, sim) + if err != nil { + t.Fatalf("Failed to deploy limiter contract: %v", err) + } + sim.Commit() + + if _, err := limiter.SetField(auth, "abc"); err != nil { + t.Fatalf("Failed to call bad gased transaction: %v", err) + } + sim.Commit() + + if field, _ := limiter.Field(nil); field != "" { + t.Fatalf("Field mismatch: have %v, want %v", field, "") + } + // Set the field with a manual high gas limit and check that it succeeds + auth.GasLimit = big.NewInt(200000) + if _, err := limiter.SetField(auth, "manual"); err != nil { + t.Fatalf("Failed to call bad gased transaction: %v", err) + } + sim.Commit() + + if field, _ := limiter.Field(nil); field != "manual" { + t.Fatalf("Field mismatch: have %v, want %v", field, "manual") + } + // Set the field with a tuned estimation and chck that it succeeds + auth.GasLimit = nil + auth.GasTuner = func(gas *big.Int) *big.Int { return new(big.Int).Add(gas, big.NewInt(100000)) } + if _, err := limiter.SetField(auth, "tuned"); err != nil { + t.Fatalf("Failed to call bad gased transaction: %v", err) + } + sim.Commit() + + if field, _ := limiter.Field(nil); field != "tuned" { + t.Fatalf("Field mismatch: have %v, want %v", field, "tuned") + } + // Check that manual CALL limiting also works + if _, err := limiter.Field(&bind.CallOpts{GasLimit: big.NewInt(21000)}); err == nil { + t.Fatalf("Gas limited CALL didn't go out of gas") + } + `, + }, } // Tests that packages generated by the binder can be successfully compiled and