Skip to content
4 changes: 4 additions & 0 deletions daemon/algod/api/algod.oas2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,10 @@
"protocol-version": {
"description": "Protocol version is the protocol version Dryrun was operated under.",
"type": "string"
},
"cost": {
"description": "Cost is the execution call of a stateful evaluation of an app call.",
Comment thread
algorandskiy marked this conversation as resolved.
Outdated
"type": "integer"
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions daemon/algod/api/algod.oas3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@
"application/json": {
"schema": {
"properties": {
"cost": {
"description": "Cost is the execution call of a stateful evaluation of an app call.",
"type": "integer"
},
"error": {
"type": "string"
},
Expand Down Expand Up @@ -2925,6 +2929,10 @@
"application/json": {
"schema": {
"properties": {
"cost": {
"description": "Cost is the execution call of a stateful evaluation of an app call.",
"type": "integer"
},
"error": {
"type": "string"
},
Expand Down
38 changes: 31 additions & 7 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,30 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
return
}
proto := config.Consensus[protocol.ConsensusVersion(dr.ProtocolVersion)]
Comment thread
algorandskiy marked this conversation as resolved.

userCostPoolingValue := proto.EnableAppCostPooling
proto.EnableAppCostPooling = true //hardcoded so that dryrun can calculate cost using statefuleval
response.Txns = make([]generated.DryrunTxnResult, len(dr.Txns))
/*** dryrun specific arbitrarily large budget is set here so that execution passes.
maxCost is calculated from the tx group and dryrun returns failure if the Cost exceeds this ***/
maxBudget := uint64(proto.MaxAppProgramCost * proto.MaxTxGroupSize)
evalBudget := maxBudget
response.Cost = nil //only stateful evaluation returns cost
numAppCalls := 0
for _, stxn := range dr.Txns {
if stxn.Txn.Type == protocol.ApplicationCallTx {
numAppCalls++
}
}
maxCost := uint64(proto.MaxAppProgramCost * numAppCalls)
for ti, stxn := range dr.Txns {
pse := logic.MakePastSideEffects(len(dr.Txns))
ep := logic.EvalParams{
Txn: &stxn,
Proto: &proto,
TxnGroup: dr.Txns,
GroupIndex: ti,
PastSideEffects: pse,
Txn: &stxn,
Proto: &proto,
TxnGroup: dr.Txns,
GroupIndex: ti,
PastSideEffects: pse,
PooledApplicationBudget: &evalBudget,
}
var result generated.DryrunTxnResult
if len(stxn.Lsig.Logic) > 0 {
Expand Down Expand Up @@ -521,7 +535,17 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
}
result.LocalDeltas = &localDeltas
}

//calculate cost used by execution, and budget constraints from dryrun request
c := maxBudget - evalBudget
response.Cost = &c
if userCostPoolingValue == false {
if c > uint64(proto.MaxAppProgramCost) {
pass = false
}
}
if c > maxCost {
pass = false
}
var err3 error
result.Logs, err3 = DeltaLogToLog(delta.Logs, appIdx)
if err3 != nil {
Expand Down
101 changes: 99 additions & 2 deletions daemon/algod/api/server/v2/dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ func logResponse(t *testing.T, response *generated.DryrunResponse) {
}
}

var dryrunProtoVersion protocol.ConsensusVersion = "dryrunTestProto"
var dryrunProtoVersion protocol.ConsensusVersion = protocol.ConsensusFuture
var dryrunMakeLedgerProto protocol.ConsensusVersion = "dryrunMakeLedgerProto"

func TestDryrunLogicSig(t *testing.T) {
// {"txns":[{"lsig":{"l":"AiABASI="},"txn":{}}]}
Expand Down Expand Up @@ -353,7 +354,7 @@ func init() {
proto.MaxAppBytesValueLen = 64
proto.MaxAppSumKeyValueLens = 128

config.Consensus[dryrunProtoVersion] = proto
config.Consensus[dryrunMakeLedgerProto] = proto
}

func checkLogicSigPass(t *testing.T, response *generated.DryrunResponse) {
Expand Down Expand Up @@ -1201,3 +1202,99 @@ return
assert.NotContains(t, encoded, "logs")

}

func TestDryrunCost(t *testing.T) {
t.Parallel()
ops, err := logic.AssembleString(`
#pragma version 5
byte "H"
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
keccak256
pop
int 1
`)

require.NoError(t, err)
approval := ops.Program
ops, err = logic.AssembleString("int 1")
clst := ops.Program
ops, err = logic.AssembleString("#pragma version 5 \nint 1 \nint 2 \npop")
approv := ops.Program
var appIdx basics.AppIndex = 1
creator := randomAddress()
sender := randomAddress()
dr := DryrunRequest{
Txns: []transactions.SignedTxn{
{
Txn: transactions.Transaction{
Header: transactions.Header{Sender: sender},
Type: protocol.ApplicationCallTx,
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
ApplicationID: appIdx,
OnCompletion: transactions.OptInOC,
},
},
},
{
Txn: transactions.Transaction{
Header: transactions.Header{Sender: sender},
Type: protocol.ApplicationCallTx,
ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
ApplicationID: appIdx + 1,
OnCompletion: transactions.OptInOC,
},
},
},
},
Apps: []generated.Application{
{
Id: uint64(appIdx),
Params: generated.ApplicationParams{
Creator: creator.String(),
ApprovalProgram: approval,
ClearStateProgram: clst,
LocalStateSchema: &generated.ApplicationStateSchema{NumByteSlice: 1},
},
},
{
Id: uint64(appIdx + 1),
Params: generated.ApplicationParams{
Creator: creator.String(),
ApprovalProgram: approv,
ClearStateProgram: clst,
LocalStateSchema: &generated.ApplicationStateSchema{NumByteSlice: 1},
},
},
},
Accounts: []generated.Account{
{
Address: sender.String(),
Status: "Online",
Amount: 10000000,
},
},
}
dr.ProtocolVersion = string(dryrunProtoVersion)
var response generated.DryrunResponse
doDryrunRequest(&dr, &response)
//dryrun call will execute but fail because the first program exceeds max possible cost
messages := *response.Txns[0].AppCallMessages
assert.GreaterOrEqual(t, len(messages), 1)
assert.Equal(t, "REJECT", messages[len(messages)-1])
require.NotNil(t, response.Cost)
require.Equal(t, uint64(1566), *response.Cost)
require.NoError(t, err)
if t.Failed() {
logResponse(t, &response)
}
}
Comment thread
algorandskiy marked this conversation as resolved.
Loading