Skip to content

Commit aa82b9a

Browse files
authored
Dryrun: Return EvalDeltas for failed executions in Dryrun (#3929)
Return evaldeltas for failed execution and check global state
1 parent 69f1ec1 commit aa82b9a

File tree

2 files changed

+135
-2
lines changed

2 files changed

+135
-2
lines changed

daemon/algod/api/server/v2/dryrun.go

+3
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,9 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
507507
messages[0] = "ApprovalProgram"
508508
}
509509
pass, delta, err := ba.StatefulEval(ti, ep, appIdx, program)
510+
if !pass {
511+
delta = ep.TxnGroup[ti].EvalDelta
512+
}
510513
result.Disassembly = debug.lines
511514
result.AppCallTrace = &debug.history
512515
result.GlobalDelta = StateDeltaToStateDelta(delta.GlobalDelta)

daemon/algod/api/server/v2/dryrun_test.go

+132-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"encoding/base64"
2222
"encoding/json"
23+
"fmt"
2324
"strconv"
2425
"strings"
2526
"testing"
@@ -374,7 +375,7 @@ func checkLogicSigPass(t *testing.T, response *generated.DryrunResponse) {
374375
}
375376
}
376377

377-
func checkAppCallPass(t *testing.T, response *generated.DryrunResponse) {
378+
func checkAppCallResponse(t *testing.T, response *generated.DryrunResponse, msg string) {
378379
if len(response.Txns) < 1 {
379380
t.Error("no response txns")
380381
} else if len(response.Txns) == 0 {
@@ -387,12 +388,20 @@ func checkAppCallPass(t *testing.T, response *generated.DryrunResponse) {
387388
if response.Txns[idx].AppCallMessages != nil {
388389
messages := *response.Txns[idx].AppCallMessages
389390
assert.GreaterOrEqual(t, len(messages), 1)
390-
assert.Equal(t, "PASS", messages[len(messages)-1])
391+
assert.Equal(t, msg, messages[len(messages)-1])
391392
}
392393
}
393394
}
394395
}
395396

397+
func checkAppCallPass(t *testing.T, response *generated.DryrunResponse) {
398+
checkAppCallResponse(t, response, "PASS")
399+
}
400+
401+
func checkAppCallReject(t *testing.T, response *generated.DryrunResponse) {
402+
checkAppCallResponse(t, response, "REJECT")
403+
}
404+
396405
type expectedSlotType struct {
397406
slot int
398407
tt basics.TealType
@@ -1634,3 +1643,124 @@ int 1`)
16341643
logResponse(t, &response)
16351644
}
16361645
}
1646+
1647+
func checkEvalDelta(t *testing.T,
1648+
response generated.DryrunResponse,
1649+
expectedGlobalDelta generated.StateDelta,
1650+
expectedLocalDelta generated.AccountStateDelta,
1651+
) {
1652+
for _, rt := range response.Txns {
1653+
if rt.GlobalDelta != nil && len(*rt.GlobalDelta) > 0 {
1654+
assert.Equal(t, expectedGlobalDelta, *rt.GlobalDelta)
1655+
} else {
1656+
assert.Nil(t, expectedGlobalDelta)
1657+
}
1658+
1659+
if rt.LocalDeltas != nil {
1660+
for _, ld := range *rt.LocalDeltas {
1661+
assert.Equal(t, expectedLocalDelta.Address, ld.Address)
1662+
assert.Equal(t, expectedLocalDelta.Delta, ld.Delta)
1663+
}
1664+
} else {
1665+
assert.Nil(t, expectedLocalDelta)
1666+
}
1667+
}
1668+
}
1669+
1670+
func TestDryrunCheckEvalDeltasReturned(t *testing.T) {
1671+
partitiontest.PartitionTest(t)
1672+
t.Parallel()
1673+
1674+
var dr DryrunRequest
1675+
var response generated.DryrunResponse
1676+
1677+
// Expected responses.
1678+
expectedByte := b64("val")
1679+
expectedUint := uint64(1)
1680+
expectedGlobalDelta := generated.StateDelta{
1681+
{
1682+
Key: b64("key"),
1683+
Value: generated.EvalDelta{
1684+
Action: uint64(basics.SetBytesAction),
1685+
Bytes: &expectedByte,
1686+
},
1687+
},
1688+
}
1689+
expectedLocalDelta := generated.AccountStateDelta{
1690+
Address: basics.Address{}.String(),
1691+
Delta: generated.StateDelta{
1692+
{
1693+
Key: b64("key"),
1694+
Value: generated.EvalDelta{
1695+
Action: uint64(basics.SetUintAction),
1696+
Uint: &expectedUint,
1697+
},
1698+
},
1699+
},
1700+
}
1701+
1702+
// Test that a PASS and REJECT dryrun both return the dryrun evaldelta.
1703+
for i := range []int{0, 1} {
1704+
ops, _ := logic.AssembleString(fmt.Sprintf(`
1705+
#pragma version 6
1706+
txna ApplicationArgs 0
1707+
txna ApplicationArgs 1
1708+
app_global_put
1709+
int 0
1710+
txna ApplicationArgs 0
1711+
int %d
1712+
app_local_put
1713+
int %d`, expectedUint, i))
1714+
dr.ProtocolVersion = string(dryrunProtoVersion)
1715+
1716+
dr.Txns = []transactions.SignedTxn{
1717+
{
1718+
Txn: transactions.Transaction{
1719+
Type: protocol.ApplicationCallTx,
1720+
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
1721+
ApplicationID: 1,
1722+
ApplicationArgs: [][]byte{
1723+
[]byte("key"),
1724+
[]byte("val"),
1725+
},
1726+
},
1727+
},
1728+
},
1729+
}
1730+
dr.Apps = []generated.Application{
1731+
{
1732+
Id: 1,
1733+
Params: generated.ApplicationParams{
1734+
ApprovalProgram: ops.Program,
1735+
GlobalStateSchema: &generated.ApplicationStateSchema{
1736+
NumByteSlice: 1,
1737+
NumUint: 1,
1738+
},
1739+
LocalStateSchema: &generated.ApplicationStateSchema{
1740+
NumByteSlice: 1,
1741+
NumUint: 1,
1742+
},
1743+
},
1744+
},
1745+
}
1746+
dr.Accounts = []generated.Account{
1747+
{
1748+
Status: "Online",
1749+
Address: basics.Address{}.String(),
1750+
AppsLocalState: &[]generated.ApplicationLocalState{{Id: 1}},
1751+
},
1752+
}
1753+
1754+
doDryrunRequest(&dr, &response)
1755+
if i == 0 {
1756+
checkAppCallReject(t, &response)
1757+
} else {
1758+
checkAppCallPass(t, &response)
1759+
}
1760+
checkEvalDelta(t, response, expectedGlobalDelta, expectedLocalDelta)
1761+
if t.Failed() {
1762+
logResponse(t, &response)
1763+
}
1764+
}
1765+
1766+
}

0 commit comments

Comments
 (0)