diff --git a/api/swagger/server/restapi/resource/swagger.yml b/api/swagger/server/restapi/resource/swagger.yml index d4619f545..11e982ee0 100644 --- a/api/swagger/server/restapi/resource/swagger.yml +++ b/api/swagger/server/restapi/resource/swagger.yml @@ -71,7 +71,6 @@ paths: maxGas: $ref: "#/definitions/Amount" description: Maximum number of gas unit that a operation will be able consume. - default: 700000000 # DefaultGasLimit coins: #$ref: "#/definitions/Amount" description: Set the coin amount that will be transferred to the smartContract. diff --git a/api/test/robot_tests/cmd/cmd.robot b/api/test/robot_tests/cmd/cmd.robot index ee0c874f3..4291babff 100644 --- a/api/test/robot_tests/cmd/cmd.robot +++ b/api/test/robot_tests/cmd/cmd.robot @@ -107,7 +107,7 @@ POST /cmd/executeFunction with invalid address Should Be Equal ${response.json()["code"]} Execute-0001 Should Contain ... ${response.json()["message"]} - ... Error: callSC failed: calling EstimateGasCost for function + ... Error: callSC failed: estimating Call SC gas cost for function Should Contain ... ${response.json()["message"]} ... receiving execute_read_only_call with diff --git a/int/api/cmd/execute_function.go b/int/api/cmd/execute_function.go index cfb6dae85..fd2f96e86 100644 --- a/int/api/cmd/execute_function.go +++ b/int/api/cmd/execute_function.go @@ -40,8 +40,7 @@ func (e *executeFunction) Handle(params operations.CmdExecuteFunctionParams) mid fee = parsedFee } - // convert maxGas to uint64 - maxGas := uint64(sendOperation.DefaultGasLimitCallSC) + maxGas := uint64(0) if string(params.Body.MaxGas) != "" { parsedMaxGas, err := strconv.ParseUint(string(params.Body.MaxGas), 10, 64) @@ -49,7 +48,7 @@ func (e *executeFunction) Handle(params operations.CmdExecuteFunctionParams) mid return operations.NewCmdExecuteFunctionBadRequest().WithPayload( &models.Error{ Code: errorInvalidMaxGas, - Message: "Error during amount conversion: " + err.Error(), + Message: "Error during max gas conversion: " + err.Error(), }) } diff --git a/pkg/node/sendoperation/interfaces.go b/pkg/node/sendoperation/interfaces.go index 78186c8d9..2507ae59b 100644 --- a/pkg/node/sendoperation/interfaces.go +++ b/pkg/node/sendoperation/interfaces.go @@ -5,6 +5,7 @@ import ( "fmt" ) +// ReadOnlyCallParams is the struct used to send a read only callSC to the node. type ReadOnlyCallParams struct { MaxGas int `json:"max_gas"` Coins string `json:"coins"` @@ -15,13 +16,17 @@ type ReadOnlyCallParams struct { CallerAddress string `json:"caller_address"` } -// Read only call response (chatgpt4 generated). -type ReadOnlyCallResponse struct { - JSONRPC string `json:"jsonrpc"` - Result []ReadOnlyCallResult `json:"result"` +// ReadOnlyExecuteParams is the struct used to send a read only executeSC to the node. +type ReadOnlyExecuteParams struct { + MaxGas int `json:"max_gas"` + Coins string `json:"coins"` + Fee string `json:"fee"` + Address string `json:"address"` + Bytecode JSONableSlice `json:"bytecode"` + OperationDatastore JSONableSlice `json:"operation_datastore"` } -type ReadOnlyCallResult struct { +type ReadOnlyResult struct { ExecutedAt Timestamp `json:"executed_at"` Result Result `json:"result"` OutputEvents []Event `json:"output_events"` @@ -66,7 +71,7 @@ func (r *Result) UnmarshalJSON(data []byte) error { return nil } - return nil // Or return an error if neither key is present + return nil } type Timestamp struct { diff --git a/pkg/node/sendoperation/readonlycall.go b/pkg/node/sendoperation/readonlycall.go index 335547bcb..62023b3f2 100644 --- a/pkg/node/sendoperation/readonlycall.go +++ b/pkg/node/sendoperation/readonlycall.go @@ -63,7 +63,7 @@ func ReadOnlyCallSC( fee string, callerAddr string, client *node.Client, -) (*ReadOnlyCallResult, error) { +) (*ReadOnlyResult, error) { readOnlyCallParams := [][]ReadOnlyCallParams{ { ReadOnlyCallParams{ @@ -95,7 +95,7 @@ func ReadOnlyCallSC( ) } - var resp []ReadOnlyCallResult + var resp []ReadOnlyResult err = rawResponse.GetObject(&resp) if err != nil { @@ -109,3 +109,94 @@ func ReadOnlyCallSC( return &resp[0], nil } + +func EstimateGasCostExecuteSC( + nickname string, + contract []byte, + datastore []byte, + maxCoins uint64, + fee uint64, + client *node.Client, +) (uint64, error) { + acc, err := wallet.Fetch(nickname) + if err != nil { + return 0, fmt.Errorf("loading wallet '%s': %w", nickname, err) + } + + coinsString, err := NanoToMas(maxCoins) + if err != nil { + return 0, fmt.Errorf("converting maxCoins to mas: %w", err) + } + + feeString, err := NanoToMas(fee) + if err != nil { + return 0, fmt.Errorf("converting fee to mas: %w", err) + } + + result, err := ReadOnlyExecuteSC(contract, datastore, coinsString, feeString, acc.Address, client) + if err != nil { + return 0, fmt.Errorf("ReadOnlyExecuteSC error: %w, caller address is %s", err, acc.Address) + } + + estimatedGasCost := uint64(result.GasCost) + + return addXPercentage(estimatedGasCost, PercentageGasLimit), nil +} + +// ReadOnlyExecuteSC calls execute_read_only_bytecode jsonrpc method. +// coins and fee must be in MAS. +func ReadOnlyExecuteSC( + contract []byte, + datastore []byte, + coins string, + fee string, + callerAddr string, + client *node.Client, +) (*ReadOnlyResult, error) { + if datastore == nil { + datastore = []byte{0} + } + + readOnlyExecuteParams := [][]ReadOnlyExecuteParams{ + { + ReadOnlyExecuteParams{ + Coins: coins, + MaxGas: MaxGasAllowedExecuteSC, + Fee: fee, + Address: callerAddr, + Bytecode: contract, + OperationDatastore: datastore, + }, + }, + } + + rawResponse, err := client.RPCClient.Call( + context.Background(), + "execute_read_only_bytecode", + readOnlyExecuteParams, + ) + if err != nil { + return nil, fmt.Errorf("calling execute_read_only_bytecode jsonrpc: %w", err) + } + + if rawResponse.Error != nil { + return nil, fmt.Errorf( + "receiving execute_read_only_bytecode response: %w, %v", + rawResponse.Error, + fmt.Sprint(rawResponse.Error.Data), + ) + } + + var resp []ReadOnlyResult + + err = rawResponse.GetObject(&resp) + if err != nil { + return nil, fmt.Errorf("parsing execute_read_only_bytecode jsonrpc response '%+v': %w", rawResponse, err) + } + + if resp[0].Result.Error != "" { + return nil, fmt.Errorf("ReadOnlyExecuteSC error: %s, caller address is %s", resp[0].Result.Error, callerAddr) + } + + return &resp[0], nil +} diff --git a/pkg/node/sendoperation/sendoperation.go b/pkg/node/sendoperation/sendoperation.go index 8aed8e81d..44821d37e 100644 --- a/pkg/node/sendoperation/sendoperation.go +++ b/pkg/node/sendoperation/sendoperation.go @@ -14,9 +14,8 @@ import ( ) const ( - DefaultGasLimitExecuteSC = 1_000_000_000 + MaxGasAllowedExecuteSC = 3_980_167_295 MaxGasAllowedCallSC = 4_294_167_295 - DefaultGasLimitCallSC = 700_000_000 DefaultExpiryInSlot = 3 DefaultFee = 0 accountCreationStorageCost = 1_000_000 diff --git a/pkg/onchain/dns/dns.go b/pkg/onchain/dns/dns.go index a6b396520..2af730fd1 100644 --- a/pkg/onchain/dns/dns.go +++ b/pkg/onchain/dns/dns.go @@ -56,7 +56,7 @@ func SetRecord( "dns1_setResolver", rec, sendoperation.DefaultFee, - sendoperation.DefaultGasLimitCallSC, + 0, sendoperation.OneMassa, sendoperation.DefaultExpiryInSlot, false, diff --git a/pkg/onchain/sc.go b/pkg/onchain/sc.go index 58a217e8b..c47e28094 100644 --- a/pkg/onchain/sc.go +++ b/pkg/onchain/sc.go @@ -5,7 +5,6 @@ import ( "regexp" "strings" - "github.com/massalabs/station/pkg/logger" "github.com/massalabs/station/pkg/node" sendOperation "github.com/massalabs/station/pkg/node/sendoperation" "github.com/massalabs/station/pkg/node/sendoperation/callsc" @@ -37,14 +36,12 @@ func CallFunction( description string, ) (*OperationWithEventResponse, error) { // Calibrate max_gas - if maxGas == sendOperation.DefaultGasLimitCallSC || maxGas == 0 { + if maxGas == 0 { estimatedGasCost, err := sendOperation.EstimateGasCostCallSC(nickname, addr, function, parameter, coins, fee, client) if err != nil { - return nil, fmt.Errorf("calling EstimateGasCost for function '%s' at '%s': %w", function, addr, err) + return nil, fmt.Errorf("estimating Call SC gas cost for function '%s' at '%s': %w", function, addr, err) } - logger.Debugf("Estimated gas cost for %s at %s: %d", function, addr, estimatedGasCost) - maxGas = estimatedGasCost } @@ -109,7 +106,7 @@ func CallFunctionSuccess( // The smart contract is deployed with the given account nickname. func DeploySC(client *node.Client, nickname string, - gasLimit uint64, + maxGas uint64, maxCoins uint64, fee uint64, expiry uint64, @@ -119,9 +116,19 @@ func DeploySC(client *node.Client, signer signer.Signer, description string, ) (*sendOperation.OperationResponse, []node.Event, error) { + // Calibrate max_gas + if maxGas == 0 { + estimatedGasCost, err := sendOperation.EstimateGasCostExecuteSC(nickname, contract, datastore, maxCoins, fee, client) + if err != nil { + return nil, nil, fmt.Errorf("estimating Execute SC gas cost: %w", err) + } + + maxGas = estimatedGasCost + } + exeSC := executesc.New( contract, - gasLimit, + maxGas, maxCoins, datastore) diff --git a/pkg/onchain/website/uploader.go b/pkg/onchain/website/uploader.go index 67d97aa41..9e1fc3909 100644 --- a/pkg/onchain/website/uploader.go +++ b/pkg/onchain/website/uploader.go @@ -82,7 +82,7 @@ func PrepareForUpload( operationResponse, events, err := onchain.DeploySC( client, nickname, - sendOperation.DefaultGasLimitExecuteSC, + 0, uint64(totalStorageCost), sendOperation.DefaultFee, sendOperation.DefaultExpiryInSlot, @@ -189,7 +189,7 @@ func upload( "appendBytesToWebsite", params, sendOperation.DefaultFee, - sendOperation.DefaultGasLimitCallSC, + 0, uint64(uploadCost), sendOperation.DefaultExpiryInSlot, false, @@ -287,7 +287,7 @@ func uploadMissedChunks( "appendBytesToWebsite", params, sendOperation.DefaultFee, - sendOperation.DefaultGasLimitCallSC, + 0, uint64(uploadCost), sendOperation.DefaultExpiryInSlot, false,