Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions pkg/ride/functions.gen.go

Large diffs are not rendered by default.

32 changes: 20 additions & 12 deletions pkg/ride/generate/internal/functions_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,10 @@ func catalogueV3() map[string]int {
m["@extrNative(1051)"] = 100
m["@extrNative(1052)"] = 100
m["@extrNative(1053)"] = 100
m["@extrUser(getInteger)"] = 10
m["@extrUser(getBoolean)"] = 10
m["@extrUser(getBinary)"] = 10
m["@extrUser(getString)"] = 10
m["@extrUser(getInteger)"] = 30
m["@extrUser(getBoolean)"] = 30
m["@extrUser(getBinary)"] = 30
m["@extrUser(getString)"] = 30
m["@extrUser(addressFromString)"] = 124
m["parseIntValue"] = 20
m["value"] = 13
Expand Down Expand Up @@ -528,6 +528,14 @@ func catalogueV4() map[string]int {
m["@extrNative(1052)"] = 10
m["@extrNative(1053)"] = 10
m["@extrNative(1062)"] = 1
m["getInteger"] = 4
m["getBoolean"] = 4
m["getBinary"] = 4
m["getString"] = 4
m["@extrUser(getInteger)"] = 4
m["@extrUser(getBoolean)"] = 4
m["@extrUser(getBinary)"] = 4
m["@extrUser(getString)"] = 4

m["contains"] = 3
m["containsElement"] = 5
Expand Down Expand Up @@ -957,14 +965,14 @@ func catalogueV9() map[string]int {
m["1214"] = 2 // New function ReplaceFirst
m["1215"] = 2 // New function ReplaceAll

m["getInteger"] = 7
m["getBoolean"] = 7
m["getBinary"] = 7
m["getString"] = 7
m["@extrUser(getInteger)"] = 14
m["@extrUser(getBoolean)"] = 14
m["@extrUser(getBinary)"] = 14
m["@extrUser(getString)"] = 14
m["getInteger"] = 2
m["getBoolean"] = 2
m["getBinary"] = 2
m["getString"] = 2
m["@extrUser(getInteger)"] = 2
m["@extrUser(getBoolean)"] = 2
m["@extrUser(getBinary)"] = 2
m["@extrUser(getString)"] = 2

constructorsCatalogue(ast.LibV9, m)
return m
Expand Down
24 changes: 24 additions & 0 deletions pkg/ride/testData/complexity_v3_get_value_by_index.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{-# STDLIB_VERSION 3 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}

# Functions under test: getIntegerValue(List[DataEntry], Int)
# getBooleanValue(List[DataEntry], Int)
# getBinaryValue(List[DataEntry], Int)
# getStringValue(List[DataEntry], Int)
#
# Discrepancy (by-index throwing variants, V3 only):
# Scala doc complexity = 30 each, Go catalogue (@extrUser) = 10 each
#
# Each function is called exactly once.
# The total delta between Scala and Go estimation = 4 * (30 - 10) = 80.

let iData = [DataEntry("k", 1)]
let bData = [DataEntry("k", true)]
let nData = [DataEntry("k", base58'')]
let sData = [DataEntry("k", "s")]

getIntegerValue(iData, 0) == 1 &&
getBooleanValue(bData, 0) &&
size(getBinaryValue(nData, 0)) == 0 &&
getStringValue(sData, 0) == "s"
33 changes: 33 additions & 0 deletions pkg/ride/testData/complexity_v4_get_by_index.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{-# STDLIB_VERSION 4 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}

# Functions under test (by-index variants, applies to STDLIB_VERSION 4-8):
#
# Option variants: getInteger / getBoolean / getBinary / getString (List, Int)
# Scala doc complexity = 4 each
# Go key: "getInteger", "getBoolean", "getBinary", "getString"
#
# Throwing variants: getIntegerValue / getBooleanValue / getBinaryValue / getStringValue (List, Int)
# Scala doc complexity = 4 each
# Go key: "@extrUser(getInteger)", "@extrUser(getBoolean)", "@extrUser(getBinary)", "@extrUser(getString)"
#
# Each of the 8 functions is called exactly once: 4 * 8 = 32.
# IsDefined costs 1 each, equality checks cost 1 each, size check costs 1: 1 * 4 = 4
# Lists creation is counted as 4*(1+2) = 12.
# Additionally, the script contains 3 equality checks and 1 size check: 3 * 1 + 1 = 4.
# Total estimation complexity: 32 + 4 + 12 + 4 = 52.

let iData = [IntegerEntry("k", 1)] # 1+2 - 1 for List creation and 2 for *Entry constructor.
let bData = [BooleanEntry("k", true)] # 1+2
let nData = [BinaryEntry("k", base58'')] # 1+2
let sData = [StringEntry("k", "s")] # 1+2

isDefined(getInteger(iData, 0)) && # 1+4 - 1 for isDefined and 4 for getInteger.
isDefined(getBoolean(bData, 0)) && # 1+4
isDefined(getBinary(nData, 0)) && # 1+4
isDefined(getString(sData, 0)) && # 1+4
getIntegerValue(iData, 0) == 1 && # 4+1 - 4 for getIntegerValue and 1 for equality check.
getBooleanValue(bData, 0) && # 4
size(getBinaryValue(nData, 0)) == 0 && # 1+4+1 - 1 for size, 4 for getBinaryValue, 1 for equality check.
getStringValue(sData, 0) == "s" # 4+1
13 changes: 13 additions & 0 deletions pkg/ride/testData/complexity_v6_address_from_public_key.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{-# STDLIB_VERSION 6 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}

# Function under test: addressFromPublicKey(ByteVector)
# Applies to STDLIB_VERSION 6-9.
#
# Discrepancy: Scala doc complexity = 1, Go catalogue = 63
# Delta: 63 - 1 = 62 (Go over-estimates by 62 per call)
#
# addressFromPublicKey is called once.
Comment on lines +8 to +11

addressFromPublicKey(tx.senderPublicKey) == this
41 changes: 41 additions & 0 deletions pkg/ride/testData/complexity_v9_get_by_index.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{-# STDLIB_VERSION 9 #-}
{-# CONTENT_TYPE EXPRESSION #-}
{-# SCRIPT_TYPE ACCOUNT #-}

# Functions under test (by-index variants, STDLIB_VERSION 9):
#
# Option variants — getInteger / getBoolean / getBinary / getString (List, Int)
# Scala doc complexity = 2 each
# Go key: "getInteger", "getBoolean", "getBinary", "getString"
#
# Throwing variants — getIntegerValue / getBooleanValue / getBinaryValue / getStringValue (List, Int)
# Scala doc complexity = 2 each
# Go key: "@extrUser(getInteger)", "@extrUser(getBoolean)", "@extrUser(getBinary)", "@extrUser(getString)"
#
# addressFromPublicKey(ByteVector)
# Scala doc complexity = 1
#
# Each of the 8 data-getter functions is called once: 8 * 2 = 16
# Function addressFromPublicKey called once: 1
# Each list creation is counted as 1 + 2: 4 * (1 + 2) = 12
# IsDefined called 4 times: 4 * 1 = 4
# 4 equality checks: 4 * 1 = 4 and 1 inequality check: 1, 5 in total
# One size call: 1
# Total Estimation Complexity: 16 + 1 + 12 + 4 + 5 + 1 = 39.
# Total Evaluation Complexity: 16 + 1 + 8 + 4 + 5 + 1 = 35.
Comment on lines +22 to +25

let iData = [IntegerEntry("k", 1)] # EvC: 1 + 1 = 2, EsC: 1 + 2 = 3
let bData = [BooleanEntry("k", true)] # EvC: 2, EsC: 3
let nData = [BinaryEntry("k", base58'')] # EvC: 2, EsC: 3
let sData = [StringEntry("k", "s")] # EvC: 2, EsC: 3
let addr = addressFromPublicKey(tx.senderPublicKey) # EvC: 1, EsC: 1

isDefined(getInteger(iData, 0)) && # 1 + 2
isDefined(getBoolean(bData, 0)) && # 1 + 2
isDefined(getBinary(nData, 0)) && # 1 + 2
isDefined(getString(sData, 0)) && # 1 + 2
getIntegerValue(iData, 0) == 1 && # 1 + 2
getBooleanValue(bData, 0) && # 2
size(getBinaryValue(nData, 0)) == 0 && # 1 + 2 + 1
getStringValue(sData, 0) == "s" && # 1 + 2
(addr == this || addr != this) # EvC: 1 (only one branch is evaluated), EsC: 1 + 1
12 changes: 6 additions & 6 deletions pkg/ride/tree_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ func EstimateTree(tree *ast.Tree, v int) (TreeEstimation, error) {
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
max, verifier, functions, err := te.estimate()
e, verifier, functions, err := te.estimate()
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
return TreeEstimation{Estimation: max, Verifier: verifier, Functions: functions}, nil
return TreeEstimation{Estimation: e, Verifier: verifier, Functions: functions}, nil
case 2:
id := base64.StdEncoding.EncodeToString(tree.Digest[:])
switch id {
Expand All @@ -50,11 +50,11 @@ func EstimateTree(tree *ast.Tree, v int) (TreeEstimation, error) {
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
max, verifier, functions, err := te.estimate()
e, verifier, functions, err := te.estimate()
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
return TreeEstimation{Estimation: max, Verifier: verifier, Functions: functions}, nil
return TreeEstimation{Estimation: e, Verifier: verifier, Functions: functions}, nil
case 3:
te, err := newTreeEstimatorV3(tree)
if err != nil {
Expand All @@ -70,11 +70,11 @@ func EstimateTree(tree *ast.Tree, v int) (TreeEstimation, error) {
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
max, verifier, functions, err := te.estimate()
e, verifier, functions, err := te.estimate()
if err != nil {
return TreeEstimation{}, errors.Wrapf(err, "failed to estimate with tree estimator V%d", v)
}
return TreeEstimation{Estimation: max, Verifier: verifier, Functions: functions}, nil
return TreeEstimation{Estimation: e, Verifier: verifier, Functions: functions}, nil
default:
return TreeEstimation{}, errors.Errorf("unsupported version of tree estimator '%d'", v)
}
Expand Down
30 changes: 30 additions & 0 deletions pkg/ride/tree_estimation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/base64"
stderrs "errors"
"fmt"
"io"
"os"
"testing"
"time"

Expand Down Expand Up @@ -678,3 +680,31 @@ func TestRideV9ScalaCompatibility(t *testing.T) {
assert.Equal(t, 51, est.Functions["addAdditionalRewards"])
assert.Equal(t, 36280, est.Functions["distributeTokens"])
}

func TestUserFunctionEstimation(t *testing.T) {
for _, test := range []struct {
fn string
estimatorVersion int
expectedComplexity int
}{
{fn: "testData/complexity_v3_get_value_by_index.ride", estimatorVersion: 4, expectedComplexity: 140},
{fn: "testData/complexity_v4_get_by_index.ride", estimatorVersion: 4, expectedComplexity: 52},
{fn: "testData/complexity_v6_address_from_public_key.ride", estimatorVersion: 4, expectedComplexity: 2},
{fn: "testData/complexity_v9_get_by_index.ride", estimatorVersion: 4, expectedComplexity: 39},
} {
t.Run(fmt.Sprintf("Estimation test '%s' with estimatorV%d", test.fn, test.estimatorVersion),
func(t *testing.T) {
f, err := os.Open(test.fn)
require.NoError(t, err)
src, err := io.ReadAll(f)
require.NoError(t, err)
Comment on lines +697 to +700

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here also

code := string(src)
tree, errs := ridec.CompileToTree(code)
require.Empty(t, errs)
e, err := EstimateTree(tree, test.estimatorVersion)
require.NoError(t, err)
assert.Equal(t, test.expectedComplexity, e.Estimation)
assert.Equal(t, test.expectedComplexity, e.Verifier)
})
}
}
38 changes: 38 additions & 0 deletions pkg/ride/tree_evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
stderrs "errors"
"fmt"
"io"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -6463,3 +6465,39 @@ func TestCallVerifierWithThrow(t *testing.T) {
assert.Equal(t, UserError, GetEvaluationErrorType(err))
})
}

func TestUserFunctionEvaluationComplexities(t *testing.T) {
dApp1 := newTestAccount(t, "DAPP1") // 3MzDtgL5yw73C2xVLnLJCrT5gCL4357a4sz

for _, test := range []struct {
fn string
expectedComplexity int
}{
{fn: "testData/complexity_v4_get_by_index.ride", expectedComplexity: 48},
{fn: "testData/complexity_v6_address_from_public_key.ride", expectedComplexity: 2},
{fn: "testData/complexity_v9_get_by_index.ride", expectedComplexity: 34},
} {
const complexityLimit = 1000
t.Run(fmt.Sprintf("Evaluation test '%s'", test.fn),
func(t *testing.T) {
f, err := os.Open(test.fn)
require.NoError(t, err)
src, err := io.ReadAll(f)
require.NoError(t, err)
Comment on lines +6483 to +6486

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use t.Cleanup

code := string(src)
tree, errs := ridec.CompileToTree(code)
require.Empty(t, errs)

env := newTestEnv(t).withLibVersion(tree.LibVersion).withComplexityLimit(complexityLimit).
withBlockV5Activated().withProtobufTx().withRideV6Activated().
withDataEntriesSizeV2().withMessageLengthV3().withValidateInternalPayments().
withThis(dApp1).withDApp(dApp1).withSender(dApp1).
withInvocation("verify", withTransactionID(crypto.Digest{})).withTree(dApp1, tree).
withWrappedState()

res, err := CallVerifier(env.toEnv(), tree)
require.NoError(t, err)
assert.Equal(t, test.expectedComplexity, res.Complexity())
})
}
}
Loading