Skip to content

Commit

Permalink
REST API: fix check for record.AppParams in AccountApplicationInforma…
Browse files Browse the repository at this point in the history
…tion (#3707)

## Summary

Follow-on to #3702, the copy-pasted code in AccountApplicationInformation was checking the wrong value before returning AppParams.
  • Loading branch information
cce authored Mar 3, 2022
1 parent c43c5e0 commit 09b6c38
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 24 deletions.
4 changes: 2 additions & 2 deletions daemon/algod/api/server/v2/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func (v2 *Handlers) AccountInformation(ctx echo.Context, address string, params
Message: "Result limit exceeded",
MaxResults: &maxResults,
TotalAssetsOptedIn: &record.TotalAssets,
TotalCreatedAssets: &record.TotalAppLocalStates,
TotalCreatedAssets: &record.TotalAssetParams,
TotalAppsOptedIn: &record.TotalAppLocalStates,
TotalCreatedApps: &record.TotalAppParams,
})
Expand Down Expand Up @@ -501,7 +501,7 @@ func (v2 *Handlers) AccountApplicationInformation(ctx echo.Context, address stri
// prepare JSON response
response := generated.AccountApplicationResponse{Round: uint64(lastRound)}

if record.AssetParams != nil {
if record.AppParams != nil {
app := AppParamsToApplication(addr.String(), basics.AppIndex(applicationID), record.AppParams)
response.CreatedApp = &app.Params
}
Expand Down
190 changes: 168 additions & 22 deletions daemon/algod/api/server/v2/test/handlers_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -64,7 +65,26 @@ func (l *mockLedger) ConsensusParams(r basics.Round) (config.ConsensusParams, er
func (l *mockLedger) Latest() basics.Round { return l.latest }

func (l *mockLedger) LookupResource(rnd basics.Round, addr basics.Address, aidx basics.CreatableIndex, ctype basics.CreatableType) (ar ledgercore.AccountResource, err error) {
panic("not implemented")
ad, ok := l.accounts[addr]
if !ok {
return ledgercore.AccountResource{}, nil
}
if ctype == basics.AppCreatable {
if ap, ok := ad.AppParams[basics.AppIndex(aidx)]; ok {
ar.AppParams = &ap
}
if ls, ok := ad.AppLocalStates[basics.AppIndex(aidx)]; ok {
ar.AppLocalState = &ls
}
} else {
if ap, ok := ad.AssetParams[basics.AssetIndex(aidx)]; ok {
ar.AssetParams = &ap
}
if ah, ok := ad.Assets[basics.AssetIndex(aidx)]; ok {
ar.AssetHolding = &ah
}
}
return ar, nil
}
func (l *mockLedger) BlockCert(rnd basics.Round) (blk bookkeeping.Block, cert agreement.Certificate, err error) {
panic("not implemented")
Expand Down Expand Up @@ -109,14 +129,51 @@ func randomAccountWithResources(N int) basics.AccountData {
return a
}

func setupTestForLargeResources(t *testing.T, acctSize, maxResults int) (handlers v2.Handlers, ctx echo.Context, rec *httptest.ResponseRecorder, fakeAddr basics.Address) {
func randomAccountWithAssets(N int) basics.AccountData {
a := ledgertesting.RandomAccountData(0)
a.Assets = make(map[basics.AssetIndex]basics.AssetHolding)
for i := 0; i < N; i++ {
a.Assets[basics.AssetIndex(i*4)] = ledgertesting.RandomAssetHolding(false)
}
return a
}

func randomAccountWithAssetParams(N int) basics.AccountData {
a := ledgertesting.RandomAccountData(0)
a.AssetParams = make(map[basics.AssetIndex]basics.AssetParams)
for i := 0; i < N; i++ {
a.AssetParams[basics.AssetIndex(i*4+1)] = ledgertesting.RandomAssetParams()
}
return a
}

func randomAccountWithAppLocalState(N int) basics.AccountData {
a := ledgertesting.RandomAccountData(0)
a.AppLocalStates = make(map[basics.AppIndex]basics.AppLocalState)
for i := 0; i < N; i++ {
a.AppLocalStates[basics.AppIndex(i*4+2)] = ledgertesting.RandomAppLocalState()
}
return a
}

func randomAccountWithAppParams(N int) basics.AccountData {
a := ledgertesting.RandomAccountData(0)
a.AppParams = make(map[basics.AppIndex]basics.AppParams)
for i := 0; i < N; i++ {
a.AppParams[basics.AppIndex(i*4+3)] = ledgertesting.RandomAppParams()
}
return a
}

func setupTestForLargeResources(t *testing.T, acctSize, maxResults int, accountMaker func(int) basics.AccountData) (handlers v2.Handlers, fakeAddr basics.Address, acctData basics.AccountData) {
ml := mockLedger{
accounts: make(map[basics.Address]basics.AccountData),
latest: basics.Round(10),
}
fakeAddr = ledgertesting.RandomAddress()

ml.accounts[fakeAddr] = randomAccountWithResources(acctSize)
acctData = accountMaker(acctSize)
ml.accounts[fakeAddr] = acctData

mockNode := makeMockNode(&ml, t.Name(), nil)
mockNode.config.MaxAPIResourcesPerAccount = uint64(maxResults)
Expand All @@ -126,56 +183,145 @@ func setupTestForLargeResources(t *testing.T, acctSize, maxResults int) (handler
Log: logging.Base(),
Shutdown: dummyShutdownChan,
}
return
}

func newReq(t *testing.T) (ctx echo.Context, rec *httptest.ResponseRecorder) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec = httptest.NewRecorder()
ctx = e.NewContext(req, rec)

return
}

func accountInformationResourceLimitsTest(t *testing.T, acctSize, maxResults int, exclude string, expectedCode int) {
handlers, ctx, rec, addr := setupTestForLargeResources(t, acctSize, maxResults)
func accountInformationResourceLimitsTest(t *testing.T, accountMaker func(int) basics.AccountData, acctSize, maxResults int, exclude string, expectedCode int) {
handlers, addr, acctData := setupTestForLargeResources(t, acctSize, maxResults, accountMaker)
params := generatedV2.AccountInformationParams{}
if exclude != "" {
params.Exclude = &exclude
}
ctx, rec := newReq(t)
err := handlers.AccountInformation(ctx, addr.String(), params)
require.NoError(t, err)
require.Equal(t, expectedCode, rec.Code)

var ret map[string]interface{}
var ret struct {
TotalApps int `json:"total-apps-opted-in"`
TotalAssets int `json:"total-assets-opted-in"`
TotalCreatedApps int `json:"total-created-apps"`
TotalCreatedAssets int `json:"total-created-assets"`
MaxResults int `json:"max-results"`
Apps []interface{} `json:"apps-local-state"`
Assets []interface{} `json:"assets"`
CreatedApps []interface{} `json:"createdApps"`
CreatedAssets []interface{} `json:"createdAssets"`
}

err = json.Unmarshal(rec.Body.Bytes(), &ret)
require.NoError(t, err)

// totals should be present in both 200 and 400
require.Equal(t, float64(acctSize), ret["total-apps-opted-in"].(float64)+ret["total-assets-opted-in"].(float64)+ret["total-created-apps"].(float64)+ret["total-created-assets"].(float64))
require.Equal(t, acctSize, ret.TotalApps+ret.TotalAssets+ret.TotalCreatedApps+ret.TotalCreatedAssets, "totals incorrect: %+v", ret)

// check totals
switch rec.Code {
case 400:
require.Equal(t, float64(maxResults), ret["max-results"])
require.Equal(t, maxResults, ret.MaxResults)
case 200:
if exclude == "all" {
require.Nil(t, ret["apps-local-state"])
require.Nil(t, ret["assets"])
require.Nil(t, ret["created-apps"])
require.Nil(t, ret["created-assets"])
require.Nil(t, ret.Apps)
require.Nil(t, ret.Assets)
require.Nil(t, ret.CreatedApps)
require.Nil(t, ret.CreatedAssets)
} else if exclude == "none" {
require.Equal(t, acctSize, len(ret["apps-local-state"].([]interface{}))+
len(ret["assets"].([]interface{}))+
len(ret["created-apps"].([]interface{}))+
len(ret["created-assets"].([]interface{})))
require.Equal(t, acctSize, len(ret.Apps)+len(ret.Assets)+len(ret.CreatedApps)+len(ret.CreatedAssets))
}
}

// check individual assets/apps
for i := 0; i < ret.TotalAssets; i++ {
ctx, rec = newReq(t)
aidx := basics.AssetIndex(i * 4)
err = handlers.AccountAssetInformation(ctx, addr.String(), uint64(aidx), generatedV2.AccountAssetInformationParams{})
require.NoError(t, err)
require.Equal(t, 200, rec.Code)
var ret generatedV2.AccountAssetResponse
err = json.Unmarshal(rec.Body.Bytes(), &ret)
require.NoError(t, err)
assert.Nil(t, ret.CreatedAsset)
assert.Equal(t, ret.AssetHolding, &generatedV2.AssetHolding{
Amount: acctData.Assets[aidx].Amount,
AssetId: uint64(aidx),
IsFrozen: acctData.Assets[aidx].Frozen,
})
}
for i := 0; i < ret.TotalCreatedAssets; i++ {
ctx, rec = newReq(t)
aidx := basics.AssetIndex(i*4 + 1)
err = handlers.AccountAssetInformation(ctx, addr.String(), uint64(aidx), generatedV2.AccountAssetInformationParams{})
require.NoError(t, err)
require.Equal(t, 200, rec.Code)
var ret generatedV2.AccountAssetResponse
err = json.Unmarshal(rec.Body.Bytes(), &ret)
require.NoError(t, err)
assert.Nil(t, ret.AssetHolding)
ap := acctData.AssetParams[aidx]
assetParams := v2.AssetParamsToAsset(addr.String(), aidx, &ap)
assert.Equal(t, ret.CreatedAsset, &assetParams.Params)
}
for i := 0; i < ret.TotalApps; i++ {
ctx, rec = newReq(t)
aidx := basics.AppIndex(i*4 + 2)
err = handlers.AccountApplicationInformation(ctx, addr.String(), uint64(aidx), generatedV2.AccountApplicationInformationParams{})
require.NoError(t, err)
require.Equal(t, 200, rec.Code)
var ret generatedV2.AccountApplicationResponse
err = json.Unmarshal(rec.Body.Bytes(), &ret)
require.NoError(t, err)
assert.Nil(t, ret.CreatedApp)
require.NotNil(t, ret.AppLocalState)
assert.Equal(t, uint64(aidx), ret.AppLocalState.Id)
ls := acctData.AppLocalStates[aidx]
assert.Equal(t, ls.Schema.NumByteSlice, ret.AppLocalState.Schema.NumByteSlice)
assert.Equal(t, ls.Schema.NumUint, ret.AppLocalState.Schema.NumUint)
}
for i := 0; i < ret.TotalCreatedApps; i++ {
ctx, rec = newReq(t)
aidx := basics.AppIndex(i*4 + 3)
err = handlers.AccountApplicationInformation(ctx, addr.String(), uint64(aidx), generatedV2.AccountApplicationInformationParams{})
require.NoError(t, err)
require.Equal(t, 200, rec.Code)
var ret generatedV2.AccountApplicationResponse
err = json.Unmarshal(rec.Body.Bytes(), &ret)
require.NoError(t, err)
assert.Nil(t, ret.AppLocalState)
ap := acctData.AppParams[aidx]
expAp := v2.AppParamsToApplication(addr.String(), aidx, &ap)
assert.EqualValues(t, expAp.Params.ApprovalProgram, ret.CreatedApp.ApprovalProgram)
assert.EqualValues(t, expAp.Params.ClearStateProgram, ret.CreatedApp.ClearStateProgram)
assert.EqualValues(t, expAp.Params.Creator, ret.CreatedApp.Creator)
}
}

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

accountInformationResourceLimitsTest(t, 99, 100, "", 200) // under limit
accountInformationResourceLimitsTest(t, 101, 100, "", 400) // over limit
accountInformationResourceLimitsTest(t, 100, 100, "", 200) // at limit
accountInformationResourceLimitsTest(t, 101, 100, "all", 200) // over limit with exclude=all
accountInformationResourceLimitsTest(t, 101, 100, "none", 400) // over limit with exclude=none
for _, tc := range []struct {
name string
accountMaker func(int) basics.AccountData
}{
{name: "all", accountMaker: randomAccountWithResources},
{name: "assets", accountMaker: randomAccountWithAssets},
{name: "applocal", accountMaker: randomAccountWithAppLocalState},
{name: "assetparams", accountMaker: randomAccountWithAssetParams},
{name: "appparams", accountMaker: randomAccountWithAppParams},
} {
t.Run(tc.name, func(t *testing.T) {
accountInformationResourceLimitsTest(t, tc.accountMaker, 99, 100, "", 200) // under limit
accountInformationResourceLimitsTest(t, tc.accountMaker, 101, 100, "", 400) // over limit
accountInformationResourceLimitsTest(t, tc.accountMaker, 100, 100, "", 200) // at limit
accountInformationResourceLimitsTest(t, tc.accountMaker, 101, 100, "all", 200) // over limit with exclude=all
accountInformationResourceLimitsTest(t, tc.accountMaker, 101, 100, "none", 400) // over limit with exclude=none
})
}
}

0 comments on commit 09b6c38

Please sign in to comment.