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
12 changes: 12 additions & 0 deletions crypto/onetimesig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"testing"

"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
"github.com/algorand/go-algorand/test/partitiontest"
)

Expand Down Expand Up @@ -140,6 +141,17 @@ func testOneTimeSignVerifyNewStyle(t *testing.T, c *OneTimeSignatureSecrets, c2
}
}

func TestHeartbeatProofRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)

toOTS := func(h HeartbeatProof) OneTimeSignature { return h.ToOneTimeSignature() }
toProof := func(ots OneTimeSignature) HeartbeatProof { return ots.ToHeartbeatProof() }

// Test with an empty proof as example, NearZeros will test each field
var emptyProof HeartbeatProof
roundtrip.Check(t, emptyProof, toOTS, toProof)
}

func BenchmarkOneTimeSigBatchVerification(b *testing.B) {
for _, enabled := range []bool{false, true} {
b.Run(fmt.Sprintf("batch=%v", enabled), func(b *testing.B) {
Expand Down
133 changes: 125 additions & 8 deletions daemon/algod/api/server/v2/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,31 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/basics/testing/roundtrip"
ledgertesting "github.com/algorand/go-algorand/ledger/testing"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)

// makeAccountConverters creates conversion functions for round-trip testing between
// basics.AccountData and model.Account.
func makeAccountConverters(t *testing.T, addrStr string, round basics.Round, proto *config.ConsensusParams, withoutRewards basics.MicroAlgos) (
toModel func(basics.AccountData) model.Account,
toBasics func(model.Account) basics.AccountData,
) {
toModel = func(ad basics.AccountData) model.Account {
converted, err := AccountDataToAccount(addrStr, &ad, round, proto, withoutRewards)
require.NoError(t, err)
return converted
}
toBasics = func(acc model.Account) basics.AccountData {
converted, err := AccountToAccountData(&acc)
require.NoError(t, err)
return converted
}
return toModel, toBasics
}

func TestAccount(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand Down Expand Up @@ -106,8 +126,9 @@ func TestAccount(t *testing.T) {
b := a.WithUpdatedRewards(proto.RewardUnit, 100)

addr := basics.Address{}.String()
conv, err := AccountDataToAccount(addr, &b, round, &proto, a.MicroAlgos)
require.NoError(t, err)
toModel, toBasics := makeAccountConverters(t, addr, round, &proto, a.MicroAlgos)

conv := toModel(b)
require.Equal(t, addr, conv.Address)
require.Equal(t, b.MicroAlgos.Raw, conv.Amount)
require.Equal(t, a.MicroAlgos.Raw, conv.AmountWithoutPendingRewards)
Expand Down Expand Up @@ -145,6 +166,21 @@ func TestAccount(t *testing.T) {
verifyCreatedApp(0, appIdx1, appParams1)
verifyCreatedApp(1, appIdx2, appParams2)

appRoundTrip := func(idx basics.AppIndex, params basics.AppParams) {
roundtrip.Check(t, params,
func(ap basics.AppParams) model.Application {
return AppParamsToApplication(addr, idx, &ap)
},
func(app model.Application) basics.AppParams {
converted, err := ApplicationParamsToAppParams(&app.Params)
require.NoError(t, err)
return converted
})
}

appRoundTrip(appIdx1, appParams1)
appRoundTrip(appIdx2, appParams2)

makeTKV := func(k string, v interface{}) model.TealKeyValue {
value := model.TealValue{}
switch v.(type) {
Expand Down Expand Up @@ -198,8 +234,8 @@ func TestAccount(t *testing.T) {
verifyCreatedAsset(0, assetIdx1, assetParams1)
verifyCreatedAsset(1, assetIdx2, assetParams2)

c, err := AccountToAccountData(&conv)
require.NoError(t, err)
// Verify round-trip conversion works for the manually constructed account
c := toBasics(toModel(b))
require.Equal(t, b, c)

t.Run("IsDeterministic", func(t *testing.T) {
Expand All @@ -223,11 +259,92 @@ func TestAccountRandomRoundTrip(t *testing.T) {
for addr, acct := range accts {
round := basics.Round(2)
proto := config.Consensus[protocol.ConsensusFuture]
conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos)
require.NoError(t, err)
c, err := AccountToAccountData(&conv)
require.NoError(t, err)
toModel, toBasics := makeAccountConverters(t, addr.String(), round, &proto, acct.MicroAlgos)
// Test the randomly-generated account round-trips correctly
c := toBasics(toModel(acct))
require.Equal(t, acct, c)
}
}
}

func TestConvertTealKeyValueRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

t.Run("nil input", func(t *testing.T) {
require.Nil(t, convertTKVToGenerated(nil))
result, err := convertGeneratedTKV(nil)
require.NoError(t, err)
require.Nil(t, result)
})

t.Run("empty map treated as nil", func(t *testing.T) {
empty := basics.TealKeyValue{}
require.Nil(t, convertTKVToGenerated(&empty))
result, err := convertGeneratedTKV(convertTKVToGenerated(&empty))
require.NoError(t, err)
require.Nil(t, result)
})

t.Run("round-trip non-empty map", func(t *testing.T) {
kv := basics.TealKeyValue{
"alpha": {Type: basics.TealUintType, Uint: 17},
"beta": {Type: basics.TealBytesType, Bytes: "\x00\x01binary"},
}

toGenerated := func(val basics.TealKeyValue) *model.TealKeyValueStore {
return convertTKVToGenerated(&val)
}
toBasics := func(store *model.TealKeyValueStore) basics.TealKeyValue {
converted, err := convertGeneratedTKV(store)
require.NoError(t, err)
return converted
}

// Test the manually constructed map round-trips correctly
result := toBasics(toGenerated(kv))
require.Equal(t, kv, result)
})
}

func TestAppLocalStateRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

appIdx := basics.AppIndex(42)
cases := map[string]basics.AppLocalState{
"empty kv": {
Schema: basics.StateSchema{NumUint: 1, NumByteSlice: 0},
KeyValue: nil,
},
"mixed kv": {
Schema: basics.StateSchema{NumUint: 2, NumByteSlice: 3},
KeyValue: basics.TealKeyValue{
"counter": {Type: basics.TealUintType, Uint: 99},
"note": {Type: basics.TealBytesType, Bytes: "hello world"},
},
},
}

for name, state := range cases {
t.Run(name, func(t *testing.T) {
modelState := AppLocalState(state, appIdx)
modelStates := []model.ApplicationLocalState{modelState}

acc := model.Account{
Status: basics.Offline.String(),
Amount: 0,
AppsLocalState: &modelStates,
}

ad, err := AccountToAccountData(&acc)
require.NoError(t, err)

require.NotNil(t, ad.AppLocalStates)
got, ok := ad.AppLocalStates[appIdx]
require.True(t, ok)
require.Equal(t, state.Schema, got.Schema)
require.Equal(t, state.KeyValue, got.KeyValue)
})
}
}
26 changes: 26 additions & 0 deletions daemon/algod/api/server/v2/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,32 @@ func TestStateDeltaToStateDelta(t *testing.T) {
gsd := globalDeltaToStateDelta(sd)
require.Equal(t, 3, len(gsd))

decode := func(msd model.StateDelta) basics.StateDelta {
if len(msd) == 0 {
return nil
}
result := make(basics.StateDelta, len(msd))
for _, kv := range msd {
keyBytes, err := base64.StdEncoding.DecodeString(kv.Key)
require.NoError(t, err)
vd := basics.ValueDelta{Action: basics.DeltaAction(kv.Value.Action)}
if kv.Value.Bytes != nil {
decoded, err := base64.StdEncoding.DecodeString(*kv.Value.Bytes)
require.NoError(t, err)
vd.Bytes = string(decoded)
}
if kv.Value.Uint != nil {
vd.Uint = *kv.Value.Uint
}
result[string(keyBytes)] = vd
}
return result
}

// Test the manually constructed StateDelta round-trips correctly
result := decode(globalDeltaToStateDelta(sd))
require.Equal(t, sd, result)

var keys []string
// test with a loop because sd is a map and iteration order is random
for _, item := range gsd {
Expand Down
68 changes: 68 additions & 0 deletions data/basics/teal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,36 @@ import (
"testing"

"github.com/stretchr/testify/require"
"pgregory.net/rapid"

"github.com/algorand/go-algorand/test/partitiontest"
)

// genTealValue generates a valid TealValue with proper Type/field correspondence.
func genTealValue() *rapid.Generator[TealValue] {
return rapid.Custom(func(t *rapid.T) TealValue {
tealType := rapid.OneOf(rapid.Just(TealUintType), rapid.Just(TealBytesType)).Draw(t, "type")

if tealType == TealUintType {
return TealValue{Type: TealUintType, Uint: rapid.Uint64().Draw(t, "uint")}
}
return TealValue{Type: TealBytesType, Bytes: rapid.String().Draw(t, "bytes")}
})
}

// genValueDelta generates a valid ValueDelta with proper Action/field correspondence.
// Note: DeleteAction is excluded as it doesn't round-trip to TealValue.
func genValueDelta() *rapid.Generator[ValueDelta] {
return rapid.Custom(func(t *rapid.T) ValueDelta {
action := rapid.OneOf(rapid.Just(SetUintAction), rapid.Just(SetBytesAction)).Draw(t, "action")

if action == SetUintAction {
return ValueDelta{Action: SetUintAction, Uint: rapid.Uint64().Draw(t, "uint")}
}
return ValueDelta{Action: SetBytesAction, Bytes: rapid.String().Draw(t, "bytes")}
})
}

func TestStateDeltaEqual(t *testing.T) {
partitiontest.PartitionTest(t)

Expand Down Expand Up @@ -60,3 +86,45 @@ func TestStateDeltaEqual(t *testing.T) {
d2 = StateDelta{"test": {Action: SetBytesAction, Bytes: "val1"}}
a.False(d1.Equal(d2))
}

func TestTealValueRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)

// Test with a simple example value
example := TealValue{Type: TealUintType, Uint: 17}

// TealValue has constraints (Type determines valid fields), so test the specific example
toVD := func(tv TealValue) ValueDelta { return tv.ToValueDelta() }
toTV := func(vd ValueDelta) TealValue {
tv, ok := vd.ToTealValue()
require.True(t, ok)
return tv
}
result := toTV(toVD(example))
require.Equal(t, example, result)
}

func TestValueDeltaRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)

// Test with a simple example value
example := ValueDelta{Action: SetUintAction, Uint: 42}

// ValueDelta has constraints (Action determines valid fields), so test the specific example
toTV := func(vd ValueDelta) TealValue {
tv, ok := vd.ToTealValue()
require.True(t, ok)
return tv
}
toVD := func(tv TealValue) ValueDelta { return tv.ToValueDelta() }
result := toVD(toTV(example))
require.Equal(t, example, result)
}

func TestValueDeltaDeleteDoesNotRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)

vd := ValueDelta{Action: DeleteAction}
_, ok := vd.ToTealValue()
require.False(t, ok)
}
30 changes: 0 additions & 30 deletions data/basics/testing/copiers.go

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package testing
package roundtrip

import (
"reflect"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package testing
package roundtrip

import (
"testing"
Expand Down
Loading