diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index dda9a79766..3f5aa8503a 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -172,10 +172,11 @@ func buildSyntaxHighlight() *tmLanguage { Name: "keyword.other.unit.teal", Match: fmt.Sprintf("^(%s)\\b", strings.Join(names, "|")), }) - // For these four, accumulate into allArithmetics, + // For these, accumulate into allArithmetics, // and only add to keyword.Patterns later, when all // have been collected. - case "Arithmetic", "Byte Array Slicing", "Byteslice Arithmetic", "Byteslice Logic": + case "Arithmetic", "Byte Array Slicing", "Byte Array Arithmetic", + "Byte Array Logic", "Inner Transactions": escape := map[rune]bool{ '*': true, '+': true, diff --git a/config/consensus.go b/config/consensus.go index 3fb9f3714f..30a655b1e7 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -283,6 +283,9 @@ type ConsensusParams struct { // maximum sum of the lengths of the key and value of one app state entry MaxAppSumKeyValueLens int + // maximum number of inner transactions that can be created by an app call + MaxInnerTransactions int + // maximum number of applications a single account can create and store // AppParams for at once MaxAppsCreated int @@ -431,6 +434,9 @@ var MaxStateDeltaKeys int // any version, used only for decoding purposes. Never decrease this value. var MaxLogCalls int +// MaxInnerTransactions is the maximum number of inner transactions that may be created in an app call. +var MaxInnerTransactions int + // MaxLogicSigMaxSize is the largest logical signature appear in any of the supported // protocols, used for decoding purposes. var MaxLogicSigMaxSize int @@ -494,6 +500,7 @@ func checkSetAllocBounds(p ConsensusParams) { // There is no consensus parameter for MaxLogCalls and MaxAppProgramLen as an approximation // Its value is much larger than any possible reasonable MaxLogCalls value in future checkSetMax(p.MaxAppProgramLen, &MaxLogCalls) + checkSetMax(p.MaxInnerTransactions, &MaxInnerTransactions) } // SaveConfigurableConsensus saves the configurable protocols file to the provided data directory. @@ -1015,6 +1022,7 @@ func initConsensusProtocols() { // Enable App calls to pool budget in grouped transactions vFuture.EnableAppCostPooling = true + vFuture.MaxInnerTransactions = 16 // Allow 50 app opt ins vFuture.MaxAppsOptedIn = 50 diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 6aa5f510c2..b9e09c707f 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -811,7 +811,10 @@ ], "responses": { "200": { - "$ref": "#/responses/PendingTransactionResponse" + "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.", + "schema": { + "$ref": "#/definitions/PendingTransactionResponse" + } }, "400": { "description": "Bad Request", @@ -1748,7 +1751,7 @@ "logs": { "type": "array", "items": { - "$ref": "#/definitions/LogItem" + "type": "string" } }, "cost": { @@ -1921,21 +1924,79 @@ } } }, - "LogItem": { - "description": "Application Log", + "PendingTransactionResponse": { + "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", "type": "object", "required": [ - "id", - "value" + "txn", + "pool-error" ], "properties": { - "id": { - "description": "unique application identifier", + "asset-index": { + "description": "The asset index if the transaction was found and it created an asset.", + "type": "integer" + }, + "application-index": { + "description": "The application index if the transaction was found and it created an application.", "type": "integer" }, - "value": { - "description": " base64 encoded log message", + "close-rewards": { + "description": "Rewards in microalgos applied to the close remainder to account.", + "type": "integer" + }, + "closing-amount": { + "description": "Closing amount for the transaction.", + "type": "integer" + }, + "asset-closing-amount": { + "description": "The number of the asset's unit that were transferred to the close-to address.", + "type": "integer" + }, + "confirmed-round": { + "description": "The round where this transaction was confirmed, if present.", + "type": "integer" + }, + "pool-error": { + "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", "type": "string" + }, + "receiver-rewards": { + "description": "Rewards in microalgos applied to the receiver account.", + "type": "integer" + }, + "sender-rewards": { + "description": "Rewards in microalgos applied to the sender account.", + "type": "integer" + }, + "local-state-delta": { + "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", + "type": "array", + "items": { + "$ref": "#/definitions/AccountStateDelta" + } + }, + "global-state-delta": { + "description": "\\[gd\\] Global state key/value changes for the application being executed by this transaction.", + "$ref": "#/definitions/StateDelta" + }, + "logs": { + "description": "\\[lg\\] Logs for the application being executed by this transaction.", + "type": "array", + "items": { + "type": "string" + } + }, + "inner-txns": { + "description": "Inner transactions produced by application execution.", + "type": "array", + "items": { + "$ref": "#/definitions/PendingTransactionResponse" + } + }, + "txn": { + "description": "The raw signed transaction.", + "type": "object", + "x-algorand-format": "SignedTransaction" } } } @@ -2278,78 +2339,6 @@ } } }, - "PendingTransactionResponse": { - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round \u003e 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error.", - "schema": { - "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", - "type": "object", - "required": [ - "txn", - "pool-error" - ], - "properties": { - "asset-index": { - "description": "The asset index if the transaction was found and it created an asset.", - "type": "integer" - }, - "application-index": { - "description": "The application index if the transaction was found and it created an application.", - "type": "integer" - }, - "close-rewards": { - "description": "Rewards in microalgos applied to the close remainder to account.", - "type": "integer" - }, - "closing-amount": { - "description": "Closing amount for the transaction.", - "type": "integer" - }, - "asset-closing-amount": { - "description": "The number of the asset's unit that were transferred to the close-to address.", - "type": "integer" - }, - "confirmed-round": { - "description": "The round where this transaction was confirmed, if present.", - "type": "integer" - }, - "pool-error": { - "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", - "type": "string" - }, - "receiver-rewards": { - "description": "Rewards in microalgos applied to the receiver account.", - "type": "integer" - }, - "sender-rewards": { - "description": "Rewards in microalgos applied to the sender account.", - "type": "integer" - }, - "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", - "type": "array", - "items": { - "$ref": "#/definitions/AccountStateDelta" - } - }, - "global-state-delta": { - "description": "\\[gd\\] Global state key/value changes for the application being executed by this transaction.", - "$ref": "#/definitions/StateDelta" - }, - "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", - "type": "array", - "items": { - "$ref": "#/definitions/LogItem" - } - }, - "txn": { - "description": "The raw signed transaction.", - "type": "object", - "x-algorand-format": "SignedTransaction" - } - } - } - }, "PendingTransactionsResponse": { "description": "A potentially truncated list of transactions currently in the node's transaction pool. You can compute whether or not the list is truncated if the number of elements in the **top-transactions** array is fewer than **total-transactions**.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 5d3b1dc892..4c12214584 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -453,82 +453,6 @@ } } }, - "PendingTransactionResponse": { - "content": { - "application/json": { - "schema": { - "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", - "properties": { - "application-index": { - "description": "The application index if the transaction was found and it created an application.", - "type": "integer" - }, - "asset-closing-amount": { - "description": "The number of the asset's unit that were transferred to the close-to address.", - "type": "integer" - }, - "asset-index": { - "description": "The asset index if the transaction was found and it created an asset.", - "type": "integer" - }, - "close-rewards": { - "description": "Rewards in microalgos applied to the close remainder to account.", - "type": "integer" - }, - "closing-amount": { - "description": "Closing amount for the transaction.", - "type": "integer" - }, - "confirmed-round": { - "description": "The round where this transaction was confirmed, if present.", - "type": "integer" - }, - "global-state-delta": { - "$ref": "#/components/schemas/StateDelta" - }, - "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/AccountStateDelta" - }, - "type": "array" - }, - "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/LogItem" - }, - "type": "array" - }, - "pool-error": { - "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", - "type": "string" - }, - "receiver-rewards": { - "description": "Rewards in microalgos applied to the receiver account.", - "type": "integer" - }, - "sender-rewards": { - "description": "Rewards in microalgos applied to the sender account.", - "type": "integer" - }, - "txn": { - "description": "The raw signed transaction.", - "properties": {}, - "type": "object", - "x-algorand-format": "SignedTransaction" - } - }, - "required": [ - "pool-error", - "txn" - ], - "type": "object" - } - } - }, - "description": "Given a transaction id of a recently submitted transaction, it returns information about it. There are several cases when this might succeed:\n- transaction committed (committed round > 0)\n- transaction still in the pool (committed round = 0, pool error = \"\")\n- transaction removed from pool due to error (committed round = 0, pool error != \"\")\n\nOr the transaction may have happened sufficiently long ago that the node no longer remembers it, and this will return an error." - }, "PendingTransactionsResponse": { "content": { "application/json": { @@ -1281,7 +1205,7 @@ }, "logs": { "items": { - "$ref": "#/components/schemas/LogItem" + "type": "string" }, "type": "array" } @@ -1344,21 +1268,79 @@ ], "type": "object" }, - "LogItem": { - "description": "Application Log", + "PendingTransactionResponse": { + "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", "properties": { - "id": { - "description": "unique application identifier", + "application-index": { + "description": "The application index if the transaction was found and it created an application.", "type": "integer" }, - "value": { - "description": " base64 encoded log message", + "asset-closing-amount": { + "description": "The number of the asset's unit that were transferred to the close-to address.", + "type": "integer" + }, + "asset-index": { + "description": "The asset index if the transaction was found and it created an asset.", + "type": "integer" + }, + "close-rewards": { + "description": "Rewards in microalgos applied to the close remainder to account.", + "type": "integer" + }, + "closing-amount": { + "description": "Closing amount for the transaction.", + "type": "integer" + }, + "confirmed-round": { + "description": "The round where this transaction was confirmed, if present.", + "type": "integer" + }, + "global-state-delta": { + "$ref": "#/components/schemas/StateDelta" + }, + "inner-txns": { + "description": "Inner transactions produced by application execution.", + "items": { + "$ref": "#/components/schemas/PendingTransactionResponse" + }, + "type": "array" + }, + "local-state-delta": { + "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", + "items": { + "$ref": "#/components/schemas/AccountStateDelta" + }, + "type": "array" + }, + "logs": { + "description": "\\[lg\\] Logs for the application being executed by this transaction.", + "items": { + "type": "string" + }, + "type": "array" + }, + "pool-error": { + "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", "type": "string" + }, + "receiver-rewards": { + "description": "Rewards in microalgos applied to the receiver account.", + "type": "integer" + }, + "sender-rewards": { + "description": "Rewards in microalgos applied to the sender account.", + "type": "integer" + }, + "txn": { + "description": "The raw signed transaction.", + "properties": {}, + "type": "object", + "x-algorand-format": "SignedTransaction" } }, "required": [ - "id", - "value" + "pool-error", + "txn" ], "type": "object" }, @@ -3339,144 +3321,12 @@ "content": { "application/json": { "schema": { - "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", - "properties": { - "application-index": { - "description": "The application index if the transaction was found and it created an application.", - "type": "integer" - }, - "asset-closing-amount": { - "description": "The number of the asset's unit that were transferred to the close-to address.", - "type": "integer" - }, - "asset-index": { - "description": "The asset index if the transaction was found and it created an asset.", - "type": "integer" - }, - "close-rewards": { - "description": "Rewards in microalgos applied to the close remainder to account.", - "type": "integer" - }, - "closing-amount": { - "description": "Closing amount for the transaction.", - "type": "integer" - }, - "confirmed-round": { - "description": "The round where this transaction was confirmed, if present.", - "type": "integer" - }, - "global-state-delta": { - "$ref": "#/components/schemas/StateDelta" - }, - "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/AccountStateDelta" - }, - "type": "array" - }, - "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/LogItem" - }, - "type": "array" - }, - "pool-error": { - "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", - "type": "string" - }, - "receiver-rewards": { - "description": "Rewards in microalgos applied to the receiver account.", - "type": "integer" - }, - "sender-rewards": { - "description": "Rewards in microalgos applied to the sender account.", - "type": "integer" - }, - "txn": { - "description": "The raw signed transaction.", - "properties": {}, - "type": "object", - "x-algorand-format": "SignedTransaction" - } - }, - "required": [ - "pool-error", - "txn" - ], - "type": "object" + "$ref": "#/components/schemas/PendingTransactionResponse" } }, "application/msgpack": { "schema": { - "description": "Details about a pending transaction. If the transaction was recently confirmed, includes confirmation details like the round and reward details.", - "properties": { - "application-index": { - "description": "The application index if the transaction was found and it created an application.", - "type": "integer" - }, - "asset-closing-amount": { - "description": "The number of the asset's unit that were transferred to the close-to address.", - "type": "integer" - }, - "asset-index": { - "description": "The asset index if the transaction was found and it created an asset.", - "type": "integer" - }, - "close-rewards": { - "description": "Rewards in microalgos applied to the close remainder to account.", - "type": "integer" - }, - "closing-amount": { - "description": "Closing amount for the transaction.", - "type": "integer" - }, - "confirmed-round": { - "description": "The round where this transaction was confirmed, if present.", - "type": "integer" - }, - "global-state-delta": { - "$ref": "#/components/schemas/StateDelta" - }, - "local-state-delta": { - "description": "\\[ld\\] Local state key/value changes for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/AccountStateDelta" - }, - "type": "array" - }, - "logs": { - "description": "\\[lg\\] Logs for the application being executed by this transaction.", - "items": { - "$ref": "#/components/schemas/LogItem" - }, - "type": "array" - }, - "pool-error": { - "description": "Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error.\n", - "type": "string" - }, - "receiver-rewards": { - "description": "Rewards in microalgos applied to the receiver account.", - "type": "integer" - }, - "sender-rewards": { - "description": "Rewards in microalgos applied to the sender account.", - "type": "integer" - }, - "txn": { - "description": "The raw signed transaction.", - "properties": {}, - "type": "object", - "x-algorand-format": "SignedTransaction" - } - }, - "required": [ - "pool-error", - "txn" - ], - "type": "object" + "$ref": "#/components/schemas/PendingTransactionResponse" } } }, diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 71dffc9c17..943b5c6a25 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -556,7 +556,7 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) { cumulativeCost += cost var err3 error - result.Logs, err3 = DeltaLogToLog(delta.Logs, appIdx) + result.Logs, err3 = DeltaLogToLog(delta.Logs) if err3 != nil { messages = append(messages, err3.Error()) } @@ -603,18 +603,14 @@ func StateDeltaToStateDelta(sd basics.StateDelta) *generated.StateDelta { return &gsd } -// DeltaLogToLog EvalDelta.Logs to generated.LogItem -func DeltaLogToLog(logs []basics.LogItem, appIdx basics.AppIndex) (*[]generated.LogItem, error) { +// DeltaLogToLog base64 encode the logs +func DeltaLogToLog(logs []string) (*[]string, error) { if len(logs) == 0 { return nil, nil } - encodedLogs := make([]generated.LogItem, 0, len(logs)) - for _, log := range logs { - if log.ID != 0 { - return nil, fmt.Errorf("logging for a foreign app is not supported") - } - msg := base64.StdEncoding.EncodeToString([]byte(log.Message)) - encodedLogs = append(encodedLogs, generated.LogItem{Id: uint64(appIdx), Value: msg}) + encodedLogs := make([]string, len(logs)) + for i, log := range logs { + encodedLogs[i] = base64.StdEncoding.EncodeToString([]byte(log)) } return &encodedLogs, nil } diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go index 6bcf2c9051..db816469f6 100644 --- a/daemon/algod/api/server/v2/dryrun_test.go +++ b/daemon/algod/api/server/v2/dryrun_test.go @@ -1193,7 +1193,7 @@ return logs := *response.Txns[0].Logs assert.Equal(t, 32, len(logs)) for i, m := range logs { - assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), m.Value) + assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), m) } encoded := string(protocol.EncodeJSON(response.Txns[0])) assert.Contains(t, encoded, "logs") diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index b1265860b9..1b98bd28d5 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -235,139 +235,135 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "AAA=", + "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index f1b7847c2c..c336a14a86 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -310,7 +310,7 @@ type DryrunTxnResult struct { LocalDeltas *[]AccountStateDelta `json:"local-deltas,omitempty"` LogicSigMessages *[]string `json:"logic-sig-messages,omitempty"` LogicSigTrace *[]DryrunState `json:"logic-sig-trace,omitempty"` - Logs *[]LogItem `json:"logs,omitempty"` + Logs *[]string `json:"logs,omitempty"` } // ErrorResponse defines model for ErrorResponse. @@ -340,14 +340,50 @@ type EvalDeltaKeyValue struct { Value EvalDelta `json:"value"` } -// LogItem defines model for LogItem. -type LogItem struct { +// PendingTransactionResponse defines model for PendingTransactionResponse. +type PendingTransactionResponse struct { - // unique application identifier - Id uint64 `json:"id"` + // The application index if the transaction was found and it created an application. + ApplicationIndex *uint64 `json:"application-index,omitempty"` + + // The number of the asset's unit that were transferred to the close-to address. + AssetClosingAmount *uint64 `json:"asset-closing-amount,omitempty"` + + // The asset index if the transaction was found and it created an asset. + AssetIndex *uint64 `json:"asset-index,omitempty"` - // base64 encoded log message - Value string `json:"value"` + // Rewards in microalgos applied to the close remainder to account. + CloseRewards *uint64 `json:"close-rewards,omitempty"` + + // Closing amount for the transaction. + ClosingAmount *uint64 `json:"closing-amount,omitempty"` + + // The round where this transaction was confirmed, if present. + ConfirmedRound *uint64 `json:"confirmed-round,omitempty"` + + // Application state delta. + GlobalStateDelta *StateDelta `json:"global-state-delta,omitempty"` + + // Inner transactions produced by application execution. + InnerTxns *[]PendingTransactionResponse `json:"inner-txns,omitempty"` + + // \[ld\] Local state key/value changes for the application being executed by this transaction. + LocalStateDelta *[]AccountStateDelta `json:"local-state-delta,omitempty"` + + // \[lg\] Logs for the application being executed by this transaction. + Logs *[]string `json:"logs,omitempty"` + + // Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error. + PoolError string `json:"pool-error"` + + // Rewards in microalgos applied to the receiver account. + ReceiverRewards *uint64 `json:"receiver-rewards,omitempty"` + + // Rewards in microalgos applied to the sender account. + SenderRewards *uint64 `json:"sender-rewards,omitempty"` + + // The raw signed transaction. + Txn map[string]interface{} `json:"txn"` } // StateDelta defines model for StateDelta. @@ -552,49 +588,6 @@ type NodeStatusResponse struct { TimeSinceLastRound uint64 `json:"time-since-last-round"` } -// PendingTransactionResponse defines model for PendingTransactionResponse. -type PendingTransactionResponse struct { - - // The application index if the transaction was found and it created an application. - ApplicationIndex *uint64 `json:"application-index,omitempty"` - - // The number of the asset's unit that were transferred to the close-to address. - AssetClosingAmount *uint64 `json:"asset-closing-amount,omitempty"` - - // The asset index if the transaction was found and it created an asset. - AssetIndex *uint64 `json:"asset-index,omitempty"` - - // Rewards in microalgos applied to the close remainder to account. - CloseRewards *uint64 `json:"close-rewards,omitempty"` - - // Closing amount for the transaction. - ClosingAmount *uint64 `json:"closing-amount,omitempty"` - - // The round where this transaction was confirmed, if present. - ConfirmedRound *uint64 `json:"confirmed-round,omitempty"` - - // Application state delta. - GlobalStateDelta *StateDelta `json:"global-state-delta,omitempty"` - - // \[ld\] Local state key/value changes for the application being executed by this transaction. - LocalStateDelta *[]AccountStateDelta `json:"local-state-delta,omitempty"` - - // \[lg\] Logs for the application being executed by this transaction. - Logs *[]LogItem `json:"logs,omitempty"` - - // Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error. - PoolError string `json:"pool-error"` - - // Rewards in microalgos applied to the receiver account. - ReceiverRewards *uint64 `json:"receiver-rewards,omitempty"` - - // Rewards in microalgos applied to the sender account. - SenderRewards *uint64 `json:"sender-rewards,omitempty"` - - // The raw signed transaction. - Txn map[string]interface{} `json:"txn"` -} - // PendingTransactionsResponse defines model for PendingTransactionsResponse. type PendingTransactionsResponse struct { diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index 1d4bd8824e..2f93b647f7 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -616,182 +616,172 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index f36c3733a6..30a434e744 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -310,7 +310,7 @@ type DryrunTxnResult struct { LocalDeltas *[]AccountStateDelta `json:"local-deltas,omitempty"` LogicSigMessages *[]string `json:"logic-sig-messages,omitempty"` LogicSigTrace *[]DryrunState `json:"logic-sig-trace,omitempty"` - Logs *[]LogItem `json:"logs,omitempty"` + Logs *[]string `json:"logs,omitempty"` } // ErrorResponse defines model for ErrorResponse. @@ -340,14 +340,50 @@ type EvalDeltaKeyValue struct { Value EvalDelta `json:"value"` } -// LogItem defines model for LogItem. -type LogItem struct { +// PendingTransactionResponse defines model for PendingTransactionResponse. +type PendingTransactionResponse struct { - // unique application identifier - Id uint64 `json:"id"` + // The application index if the transaction was found and it created an application. + ApplicationIndex *uint64 `json:"application-index,omitempty"` + + // The number of the asset's unit that were transferred to the close-to address. + AssetClosingAmount *uint64 `json:"asset-closing-amount,omitempty"` + + // The asset index if the transaction was found and it created an asset. + AssetIndex *uint64 `json:"asset-index,omitempty"` - // base64 encoded log message - Value string `json:"value"` + // Rewards in microalgos applied to the close remainder to account. + CloseRewards *uint64 `json:"close-rewards,omitempty"` + + // Closing amount for the transaction. + ClosingAmount *uint64 `json:"closing-amount,omitempty"` + + // The round where this transaction was confirmed, if present. + ConfirmedRound *uint64 `json:"confirmed-round,omitempty"` + + // Application state delta. + GlobalStateDelta *StateDelta `json:"global-state-delta,omitempty"` + + // Inner transactions produced by application execution. + InnerTxns *[]PendingTransactionResponse `json:"inner-txns,omitempty"` + + // \[ld\] Local state key/value changes for the application being executed by this transaction. + LocalStateDelta *[]AccountStateDelta `json:"local-state-delta,omitempty"` + + // \[lg\] Logs for the application being executed by this transaction. + Logs *[]string `json:"logs,omitempty"` + + // Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error. + PoolError string `json:"pool-error"` + + // Rewards in microalgos applied to the receiver account. + ReceiverRewards *uint64 `json:"receiver-rewards,omitempty"` + + // Rewards in microalgos applied to the sender account. + SenderRewards *uint64 `json:"sender-rewards,omitempty"` + + // The raw signed transaction. + Txn map[string]interface{} `json:"txn"` } // StateDelta defines model for StateDelta. @@ -552,49 +588,6 @@ type NodeStatusResponse struct { TimeSinceLastRound uint64 `json:"time-since-last-round"` } -// PendingTransactionResponse defines model for PendingTransactionResponse. -type PendingTransactionResponse struct { - - // The application index if the transaction was found and it created an application. - ApplicationIndex *uint64 `json:"application-index,omitempty"` - - // The number of the asset's unit that were transferred to the close-to address. - AssetClosingAmount *uint64 `json:"asset-closing-amount,omitempty"` - - // The asset index if the transaction was found and it created an asset. - AssetIndex *uint64 `json:"asset-index,omitempty"` - - // Rewards in microalgos applied to the close remainder to account. - CloseRewards *uint64 `json:"close-rewards,omitempty"` - - // Closing amount for the transaction. - ClosingAmount *uint64 `json:"closing-amount,omitempty"` - - // The round where this transaction was confirmed, if present. - ConfirmedRound *uint64 `json:"confirmed-round,omitempty"` - - // Application state delta. - GlobalStateDelta *StateDelta `json:"global-state-delta,omitempty"` - - // \[ld\] Local state key/value changes for the application being executed by this transaction. - LocalStateDelta *[]AccountStateDelta `json:"local-state-delta,omitempty"` - - // \[lg\] Logs for the application being executed by this transaction. - Logs *[]LogItem `json:"logs,omitempty"` - - // Indicates that the transaction was kicked out of this node's transaction pool (and specifies why that happened). An empty string indicates the transaction wasn't kicked out of this node's txpool due to an error. - PoolError string `json:"pool-error"` - - // Rewards in microalgos applied to the receiver account. - ReceiverRewards *uint64 `json:"receiver-rewards,omitempty"` - - // Rewards in microalgos applied to the sender account. - SenderRewards *uint64 `json:"sender-rewards,omitempty"` - - // The raw signed transaction. - Txn map[string]interface{} `json:"txn"` -} - // PendingTransactionsResponse defines model for PendingTransactionsResponse. type PendingTransactionsResponse struct { diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 7a60425444..5feb0290c6 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -471,6 +471,23 @@ func (v2 *Handlers) TransactionParams(ctx echo.Context) error { return ctx.JSON(http.StatusOK, response) } +type preEncodedTxInfo struct { + AssetIndex *uint64 `codec:"asset-index,omitempty"` + AssetClosingAmount *uint64 `codec:"asset-closing-amount,omitempty"` + ApplicationIndex *uint64 `codec:"application-index,omitempty"` + CloseRewards *uint64 `codec:"close-rewards,omitempty"` + ClosingAmount *uint64 `codec:"closing-amount,omitempty"` + ConfirmedRound *uint64 `codec:"confirmed-round,omitempty"` + GlobalStateDelta *generated.StateDelta `codec:"global-state-delta,omitempty"` + LocalStateDelta *[]generated.AccountStateDelta `codec:"local-state-delta,omitempty"` + PoolError string `codec:"pool-error"` + ReceiverRewards *uint64 `codec:"receiver-rewards,omitempty"` + SenderRewards *uint64 `codec:"sender-rewards,omitempty"` + Txn transactions.SignedTxn `codec:"txn"` + Logs *[]string `codec:"logs,omitempty"` + Inners *[]preEncodedTxInfo `codec:"inner-txns,omitempty"` +} + // PendingTransactionInformation returns a transaction with the specified txID // from the transaction pool. If not found looks for the transaction in the // last proto.MaxTxnLife rounds @@ -500,29 +517,10 @@ func (v2 *Handlers) PendingTransactionInformation(ctx echo.Context, txid string, } // Encoding wasn't working well without embedding "real" objects. - response := struct { - AssetIndex *uint64 `codec:"asset-index,omitempty"` - AssetClosingAmount *uint64 `codec:"asset-closing-amount,omitempty"` - ApplicationIndex *uint64 `codec:"application-index,omitempty"` - CloseRewards *uint64 `codec:"close-rewards,omitempty"` - ClosingAmount *uint64 `codec:"closing-amount,omitempty"` - ConfirmedRound *uint64 `codec:"confirmed-round,omitempty"` - GlobalStateDelta *generated.StateDelta `codec:"global-state-delta,omitempty"` - LocalStateDelta *[]generated.AccountStateDelta `codec:"local-state-delta,omitempty"` - PoolError string `codec:"pool-error"` - ReceiverRewards *uint64 `codec:"receiver-rewards,omitempty"` - SenderRewards *uint64 `codec:"sender-rewards,omitempty"` - Txn transactions.SignedTxn `codec:"txn"` - Logs *[]generated.LogItem `codec:"logs,omitempty"` - }{ + response := preEncodedTxInfo{ Txn: txn.Txn, } - handle, contentType, err := getCodecHandle(params.Format) - if err != nil { - return badRequest(ctx, err, errFailedParsingFormatOption, v2.Log) - } - if txn.ConfirmedRound != 0 { r := uint64(txn.ConfirmedRound) response.ConfirmedRound = &r @@ -535,11 +533,13 @@ func (v2 *Handlers) PendingTransactionInformation(ctx echo.Context, txid string, response.AssetIndex = computeAssetIndexFromTxn(txn, v2.Node.Ledger()) response.ApplicationIndex = computeAppIndexFromTxn(txn, v2.Node.Ledger()) response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn) - response.Logs, err = convertToLogItems(txn, response.ApplicationIndex) - if err != nil { - return internalError(ctx, err, err.Error(), v2.Log) - } + response.Logs = convertLogs(txn) + response.Inners = convertInners(&txn) + } + handle, contentType, err := getCodecHandle(params.Format) + if err != nil { + return badRequest(ctx, err, errFailedParsingFormatOption, v2.Log) } data, err := encode(handle, response) if err != nil { diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index b5930e28a0..4f943c900f 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -146,7 +146,7 @@ func TestGetBlockJsonEncoding(t *testing.T) { Lsig: lsig, } ad := transactions.ApplyData{ - EvalDelta: basics.EvalDelta{ + EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{ 1: {"key": basics.ValueDelta{Action: 1}}, }, diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index a3bfa12517..8698f9a408 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -266,33 +266,50 @@ func convertToDeltas(txn node.TxnWithStatus) (*[]generated.AccountStateDelta, *g return localStateDelta, stateDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta) } -func convertToLogItems(txn node.TxnWithStatus, aidx *uint64) (*[]generated.LogItem, error) { - var logItems *[]generated.LogItem +func convertLogs(txn node.TxnWithStatus) *[]string { + var logItems *[]string if len(txn.ApplyData.EvalDelta.Logs) > 0 { - l := make([]generated.LogItem, 0, len(txn.ApplyData.EvalDelta.Logs)) - - for _, v := range txn.ApplyData.EvalDelta.Logs { - // Resolve appid from index - var appid uint64 - if v.ID != 0 { - return nil, fmt.Errorf("logging for a foreign app is not supported") - } else if txn.Txn.Txn.ApplicationID == 0 { - if aidx == nil { - return nil, fmt.Errorf("app index cannot be nil") - } - appid = *aidx - } else { - appid = uint64(txn.Txn.Txn.ApplicationID) - } - l = append(l, generated.LogItem{ - Id: appid, - Value: base64.StdEncoding.EncodeToString([]byte(v.Message)), - }) + l := make([]string, len(txn.ApplyData.EvalDelta.Logs)) + + for i, log := range txn.ApplyData.EvalDelta.Logs { + l[i] = base64.StdEncoding.EncodeToString([]byte(log)) } logItems = &l } - return logItems, nil + return logItems +} + +func convertInners(txn *node.TxnWithStatus) *[]preEncodedTxInfo { + inner := make([]preEncodedTxInfo, len(txn.ApplyData.EvalDelta.InnerTxns)) + for i, itxn := range txn.ApplyData.EvalDelta.InnerTxns { + inner[i] = convertTxn(&itxn) + } + return &inner +} + +func convertTxn(txn *transactions.SignedTxnWithAD) preEncodedTxInfo { + // This copies from handlers.PendingTransactionInformation, with + // simplifications because we have a SignedTxnWithAD rather than + // TxnWithStatus, and we know this txn has committed. + + response := preEncodedTxInfo{Txn: txn.SignedTxn} + + response.ClosingAmount = &txn.ApplyData.ClosingAmount.Raw + response.AssetClosingAmount = &txn.ApplyData.AssetClosingAmount + response.SenderRewards = &txn.ApplyData.SenderRewards.Raw + response.ReceiverRewards = &txn.ApplyData.ReceiverRewards.Raw + response.CloseRewards = &txn.ApplyData.CloseRewards.Raw + + // Indexes can't be set until we allow acfg or appl + // response.AssetIndex = computeAssetIndexFromTxn(txn, v2.Node.Ledger()) + // response.ApplicationIndex = computeAppIndexFromTxn(txn, v2.Node.Ledger()) + + // Deltas, Logs, and Inners can not be set until we allow appl + // response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn) + // response.Logs = convertLogs(txn) + // response.Inners = convertInners(&txn) + return response } // printableUTF8OrEmpty checks to see if the entire string is a UTF8 printable string. diff --git a/data/basics/msgp_gen.go b/data/basics/msgp_gen.go index 6127920e8b..8e61362d6d 100644 --- a/data/basics/msgp_gen.go +++ b/data/basics/msgp_gen.go @@ -107,22 +107,6 @@ import ( // |-----> Msgsize // |-----> MsgIsZero // -// EvalDelta -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// -// LogItem -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // Round // |-----> MarshalMsg // |-----> CanMarshalMsg @@ -4133,725 +4117,6 @@ func (z DeltaAction) MsgIsZero() bool { return z == 0 } -// MarshalMsg implements msgp.Marshaler -func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0008Len := uint32(3) - var zb0008Mask uint8 /* 4 bits */ - if len((*z).GlobalDelta) == 0 { - zb0008Len-- - zb0008Mask |= 0x2 - } - if len((*z).LocalDeltas) == 0 { - zb0008Len-- - zb0008Mask |= 0x4 - } - if len((*z).Logs) == 0 { - zb0008Len-- - zb0008Mask |= 0x8 - } - // variable map header, size zb0008Len - o = append(o, 0x80|uint8(zb0008Len)) - if zb0008Len != 0 { - if (zb0008Mask & 0x2) == 0 { // if not empty - // string "gd" - o = append(o, 0xa2, 0x67, 0x64) - if (*z).GlobalDelta == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).GlobalDelta))) - } - zb0001_keys := make([]string, 0, len((*z).GlobalDelta)) - for zb0001 := range (*z).GlobalDelta { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(SortString(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).GlobalDelta[zb0001] - _ = zb0002 - o = msgp.AppendString(o, zb0001) - o = zb0002.MarshalMsg(o) - } - } - if (zb0008Mask & 0x4) == 0 { // if not empty - // string "ld" - o = append(o, 0xa2, 0x6c, 0x64) - if (*z).LocalDeltas == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).LocalDeltas))) - } - zb0003_keys := make([]uint64, 0, len((*z).LocalDeltas)) - for zb0003 := range (*z).LocalDeltas { - zb0003_keys = append(zb0003_keys, zb0003) - } - sort.Sort(SortUint64(zb0003_keys)) - for _, zb0003 := range zb0003_keys { - zb0004 := (*z).LocalDeltas[zb0003] - _ = zb0004 - o = msgp.AppendUint64(o, zb0003) - if zb0004 == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len(zb0004))) - } - zb0005_keys := make([]string, 0, len(zb0004)) - for zb0005 := range zb0004 { - zb0005_keys = append(zb0005_keys, zb0005) - } - sort.Sort(SortString(zb0005_keys)) - for _, zb0005 := range zb0005_keys { - zb0006 := zb0004[zb0005] - _ = zb0006 - o = msgp.AppendString(o, zb0005) - o = zb0006.MarshalMsg(o) - } - } - } - if (zb0008Mask & 0x8) == 0 { // if not empty - // string "lg" - o = append(o, 0xa2, 0x6c, 0x67) - if (*z).Logs == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendArrayHeader(o, uint32(len((*z).Logs))) - } - for zb0007 := range (*z).Logs { - // omitempty: check for empty values - zb0009Len := uint32(2) - var zb0009Mask uint8 /* 3 bits */ - if (*z).Logs[zb0007].ID == 0 { - zb0009Len-- - zb0009Mask |= 0x2 - } - if (*z).Logs[zb0007].Message == "" { - zb0009Len-- - zb0009Mask |= 0x4 - } - // variable map header, size zb0009Len - o = append(o, 0x80|uint8(zb0009Len)) - if (zb0009Mask & 0x2) == 0 { // if not empty - // string "i" - o = append(o, 0xa1, 0x69) - o = msgp.AppendUint64(o, (*z).Logs[zb0007].ID) - } - if (zb0009Mask & 0x4) == 0 { // if not empty - // string "m" - o = append(o, 0xa1, 0x6d) - o = msgp.AppendString(o, (*z).Logs[zb0007].Message) - } - } - } - } - return -} - -func (_ *EvalDelta) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*EvalDelta) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0008 int - var zb0009 bool - zb0008, zb0009, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0008 > 0 { - zb0008-- - var zb0010 int - var zb0011 bool - zb0010, zb0011, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "GlobalDelta") - return - } - if zb0010 > config.MaxStateDeltaKeys { - err = msgp.ErrOverflow(uint64(zb0010), uint64(config.MaxStateDeltaKeys)) - err = msgp.WrapError(err, "struct-from-array", "GlobalDelta") - return - } - if zb0011 { - (*z).GlobalDelta = nil - } else if (*z).GlobalDelta == nil { - (*z).GlobalDelta = make(StateDelta, zb0010) - } - for zb0010 > 0 { - var zb0001 string - var zb0002 ValueDelta - zb0010-- - zb0001, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "GlobalDelta") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "GlobalDelta", zb0001) - return - } - (*z).GlobalDelta[zb0001] = zb0002 - } - } - if zb0008 > 0 { - zb0008-- - var zb0012 int - var zb0013 bool - zb0012, zb0013, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") - return - } - if zb0012 > config.MaxEvalDeltaAccounts { - err = msgp.ErrOverflow(uint64(zb0012), uint64(config.MaxEvalDeltaAccounts)) - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") - return - } - if zb0013 { - (*z).LocalDeltas = nil - } else if (*z).LocalDeltas == nil { - (*z).LocalDeltas = make(map[uint64]StateDelta, zb0012) - } - for zb0012 > 0 { - var zb0003 uint64 - var zb0004 StateDelta - zb0012-- - zb0003, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") - return - } - var zb0014 int - var zb0015 bool - zb0014, zb0015, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas", zb0003) - return - } - if zb0014 > config.MaxStateDeltaKeys { - err = msgp.ErrOverflow(uint64(zb0014), uint64(config.MaxStateDeltaKeys)) - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas", zb0003) - return - } - if zb0015 { - zb0004 = nil - } else if zb0004 == nil { - zb0004 = make(StateDelta, zb0014) - } - for zb0014 > 0 { - var zb0005 string - var zb0006 ValueDelta - zb0014-- - zb0005, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas", zb0003) - return - } - bts, err = zb0006.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "LocalDeltas", zb0003, zb0005) - return - } - zb0004[zb0005] = zb0006 - } - (*z).LocalDeltas[zb0003] = zb0004 - } - } - if zb0008 > 0 { - zb0008-- - var zb0016 int - var zb0017 bool - zb0016, zb0017, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs") - return - } - if zb0016 > config.MaxLogCalls { - err = msgp.ErrOverflow(uint64(zb0016), uint64(config.MaxLogCalls)) - err = msgp.WrapError(err, "struct-from-array", "Logs") - return - } - if zb0017 { - (*z).Logs = nil - } else if (*z).Logs != nil && cap((*z).Logs) >= zb0016 { - (*z).Logs = ((*z).Logs)[:zb0016] - } else { - (*z).Logs = make([]LogItem, zb0016) - } - for zb0007 := range (*z).Logs { - var zb0018 int - var zb0019 bool - zb0018, zb0019, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0018, zb0019, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007) - return - } - if zb0018 > 0 { - zb0018-- - (*z).Logs[zb0007].ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007, "struct-from-array", "ID") - return - } - } - if zb0018 > 0 { - zb0018-- - (*z).Logs[zb0007].Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007, "struct-from-array", "Message") - return - } - } - if zb0018 > 0 { - err = msgp.ErrTooManyArrayFields(zb0018) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007) - return - } - if zb0019 { - (*z).Logs[zb0007] = LogItem{} - } - for zb0018 > 0 { - zb0018-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007) - return - } - switch string(field) { - case "i": - (*z).Logs[zb0007].ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007, "ID") - return - } - case "m": - (*z).Logs[zb0007].Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007, "Message") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Logs", zb0007) - return - } - } - } - } - } - } - if zb0008 > 0 { - err = msgp.ErrTooManyArrayFields(zb0008) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0009 { - (*z) = EvalDelta{} - } - for zb0008 > 0 { - zb0008-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "gd": - var zb0020 int - var zb0021 bool - zb0020, zb0021, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "GlobalDelta") - return - } - if zb0020 > config.MaxStateDeltaKeys { - err = msgp.ErrOverflow(uint64(zb0020), uint64(config.MaxStateDeltaKeys)) - err = msgp.WrapError(err, "GlobalDelta") - return - } - if zb0021 { - (*z).GlobalDelta = nil - } else if (*z).GlobalDelta == nil { - (*z).GlobalDelta = make(StateDelta, zb0020) - } - for zb0020 > 0 { - var zb0001 string - var zb0002 ValueDelta - zb0020-- - zb0001, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "GlobalDelta") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "GlobalDelta", zb0001) - return - } - (*z).GlobalDelta[zb0001] = zb0002 - } - case "ld": - var zb0022 int - var zb0023 bool - zb0022, zb0023, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "LocalDeltas") - return - } - if zb0022 > config.MaxEvalDeltaAccounts { - err = msgp.ErrOverflow(uint64(zb0022), uint64(config.MaxEvalDeltaAccounts)) - err = msgp.WrapError(err, "LocalDeltas") - return - } - if zb0023 { - (*z).LocalDeltas = nil - } else if (*z).LocalDeltas == nil { - (*z).LocalDeltas = make(map[uint64]StateDelta, zb0022) - } - for zb0022 > 0 { - var zb0003 uint64 - var zb0004 StateDelta - zb0022-- - zb0003, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "LocalDeltas") - return - } - var zb0024 int - var zb0025 bool - zb0024, zb0025, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "LocalDeltas", zb0003) - return - } - if zb0024 > config.MaxStateDeltaKeys { - err = msgp.ErrOverflow(uint64(zb0024), uint64(config.MaxStateDeltaKeys)) - err = msgp.WrapError(err, "LocalDeltas", zb0003) - return - } - if zb0025 { - zb0004 = nil - } else if zb0004 == nil { - zb0004 = make(StateDelta, zb0024) - } - for zb0024 > 0 { - var zb0005 string - var zb0006 ValueDelta - zb0024-- - zb0005, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "LocalDeltas", zb0003) - return - } - bts, err = zb0006.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "LocalDeltas", zb0003, zb0005) - return - } - zb0004[zb0005] = zb0006 - } - (*z).LocalDeltas[zb0003] = zb0004 - } - case "lg": - var zb0026 int - var zb0027 bool - zb0026, zb0027, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs") - return - } - if zb0026 > config.MaxLogCalls { - err = msgp.ErrOverflow(uint64(zb0026), uint64(config.MaxLogCalls)) - err = msgp.WrapError(err, "Logs") - return - } - if zb0027 { - (*z).Logs = nil - } else if (*z).Logs != nil && cap((*z).Logs) >= zb0026 { - (*z).Logs = ((*z).Logs)[:zb0026] - } else { - (*z).Logs = make([]LogItem, zb0026) - } - for zb0007 := range (*z).Logs { - var zb0028 int - var zb0029 bool - zb0028, zb0029, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0028, zb0029, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007) - return - } - if zb0028 > 0 { - zb0028-- - (*z).Logs[zb0007].ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007, "struct-from-array", "ID") - return - } - } - if zb0028 > 0 { - zb0028-- - (*z).Logs[zb0007].Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007, "struct-from-array", "Message") - return - } - } - if zb0028 > 0 { - err = msgp.ErrTooManyArrayFields(zb0028) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007) - return - } - if zb0029 { - (*z).Logs[zb0007] = LogItem{} - } - for zb0028 > 0 { - zb0028-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007) - return - } - switch string(field) { - case "i": - (*z).Logs[zb0007].ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007, "ID") - return - } - case "m": - (*z).Logs[zb0007].Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007, "Message") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err, "Logs", zb0007) - return - } - } - } - } - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *EvalDelta) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*EvalDelta) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *EvalDelta) Msgsize() (s int) { - s = 1 + 3 + msgp.MapHeaderSize - if (*z).GlobalDelta != nil { - for zb0001, zb0002 := range (*z).GlobalDelta { - _ = zb0001 - _ = zb0002 - s += 0 + msgp.StringPrefixSize + len(zb0001) + zb0002.Msgsize() - } - } - s += 3 + msgp.MapHeaderSize - if (*z).LocalDeltas != nil { - for zb0003, zb0004 := range (*z).LocalDeltas { - _ = zb0003 - _ = zb0004 - s += 0 + msgp.Uint64Size + msgp.MapHeaderSize - if zb0004 != nil { - for zb0005, zb0006 := range zb0004 { - _ = zb0005 - _ = zb0006 - s += 0 + msgp.StringPrefixSize + len(zb0005) + zb0006.Msgsize() - } - } - } - } - s += 3 + msgp.ArrayHeaderSize - for zb0007 := range (*z).Logs { - s += 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Logs[zb0007].Message) - } - return -} - -// MsgIsZero returns whether this is a zero value -func (z *EvalDelta) MsgIsZero() bool { - return (len((*z).GlobalDelta) == 0) && (len((*z).LocalDeltas) == 0) && (len((*z).Logs) == 0) -} - -// MarshalMsg implements msgp.Marshaler -func (z *LogItem) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(2) - var zb0001Mask uint8 /* 3 bits */ - if (*z).ID == 0 { - zb0001Len-- - zb0001Mask |= 0x2 - } - if (*z).Message == "" { - zb0001Len-- - zb0001Mask |= 0x4 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "i" - o = append(o, 0xa1, 0x69) - o = msgp.AppendUint64(o, (*z).ID) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "m" - o = append(o, 0xa1, 0x6d) - o = msgp.AppendString(o, (*z).Message) - } - } - return -} - -func (_ *LogItem) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*LogItem) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *LogItem) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - (*z).ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ID") - return - } - } - if zb0001 > 0 { - zb0001-- - (*z).Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Message") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = LogItem{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "i": - (*z).ID, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "ID") - return - } - case "m": - (*z).Message, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Message") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *LogItem) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*LogItem) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *LogItem) Msgsize() (s int) { - s = 1 + 2 + msgp.Uint64Size + 2 + msgp.StringPrefixSize + len((*z).Message) - return -} - -// MsgIsZero returns whether this is a zero value -func (z *LogItem) MsgIsZero() bool { - return ((*z).ID == 0) && ((*z).Message == "") -} - // MarshalMsg implements msgp.Marshaler func (z Round) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/data/basics/msgp_gen_test.go b/data/basics/msgp_gen_test.go index c0e43a4fd4..bae0e2e5cf 100644 --- a/data/basics/msgp_gen_test.go +++ b/data/basics/msgp_gen_test.go @@ -372,128 +372,7 @@ func BenchmarkUnmarshalBalanceRecord(b *testing.B) { } } -func TestMarshalUnmarshalEvalDelta(t *testing.T) { - partitiontest.PartitionTest(t) - v := EvalDelta{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingEvalDelta(t *testing.T) { - protocol.RunEncodingTest(t, &EvalDelta{}) -} - -func BenchmarkMarshalMsgEvalDelta(b *testing.B) { - v := EvalDelta{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgEvalDelta(b *testing.B) { - v := EvalDelta{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalEvalDelta(b *testing.B) { - v := EvalDelta{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalLogItem(t *testing.T) { - partitiontest.PartitionTest(t) - v := LogItem{} - bts := v.MarshalMsg(nil) - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func TestRandomizedEncodingLogItem(t *testing.T) { - protocol.RunEncodingTest(t, &LogItem{}) -} - -func BenchmarkMarshalMsgLogItem(b *testing.B) { - v := LogItem{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgLogItem(b *testing.B) { - v := LogItem{} - bts := make([]byte, 0, v.Msgsize()) - bts = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalLogItem(b *testing.B) { - v := LogItem{} - bts := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - func TestMarshalUnmarshalStateDelta(t *testing.T) { - partitiontest.PartitionTest(t) v := StateDelta{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) diff --git a/data/basics/teal.go b/data/basics/teal.go index 7dab26b83b..6fe1ce7c47 100644 --- a/data/basics/teal.go +++ b/data/basics/teal.go @@ -123,80 +123,6 @@ func (sd StateDelta) Valid(proto *config.ConsensusParams) error { return nil } -// LogItem is contains logs for an application -// ID is the offset into Txn.ForeignApps -type LogItem struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - ID uint64 `codec:"i"` - Message string `codec:"m"` -} - -// Equal checks whether two LogItems are equal. -func (l LogItem) Equal(o LogItem) bool { - - return l.ID == o.ID && l.Message == o.Message - -} - -// EvalDelta stores StateDeltas for an application's global key/value store, as -// well as StateDeltas for some number of accounts holding local state for that -// application -type EvalDelta struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - GlobalDelta StateDelta `codec:"gd"` - - // When decoding EvalDeltas, the integer key represents an offset into - // [txn.Sender, txn.Accounts[0], txn.Accounts[1], ...] - LocalDeltas map[uint64]StateDelta `codec:"ld,allocbound=config.MaxEvalDeltaAccounts"` - - Logs []LogItem `codec:"lg,allocbound=config.MaxLogCalls"` -} - -// Equal compares two EvalDeltas and returns whether or not they are -// equivalent. It does not care about nilness equality of LocalDeltas, -// because the msgpack codec will encode/decode an empty map as nil, and we want -// an empty generated EvalDelta to equal an empty one we decode off the wire. -func (ed EvalDelta) Equal(o EvalDelta) bool { - // LocalDeltas length should be the same - if len(ed.LocalDeltas) != len(o.LocalDeltas) { - return false - } - - // All keys and local StateDeltas should be the same - for k, v := range ed.LocalDeltas { - // Other LocalDelta must have value for key - ov, ok := o.LocalDeltas[k] - if !ok { - return false - } - - // Other LocalDelta must have same value for key - if !ov.Equal(v) { - return false - } - } - - // GlobalDeltas must be equal - if !ed.GlobalDelta.Equal(o.GlobalDelta) { - return false - } - - // Logs must be equal - if len(ed.Logs) != len(o.Logs) { - return false - } - for i, l := range ed.Logs { - ok := l.Equal(o.Logs[i]) - if !ok { - return false - } - } - - return true -} - // StateSchema sets maximums on the number of each type that may be stored type StateSchema struct { _struct struct{} `codec:",omitempty,omitemptyarray"` diff --git a/data/basics/teal_test.go b/data/basics/teal_test.go index 526573aaf5..35aa9a193f 100644 --- a/data/basics/teal_test.go +++ b/data/basics/teal_test.go @@ -143,114 +143,3 @@ func TestStateDeltaEqual(t *testing.T) { d2 = StateDelta{"test": {Action: SetBytesAction, Bytes: "val1"}} a.False(d1.Equal(d2)) } - -func TestEvalDeltaEqual(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - d1 := EvalDelta{} - d2 := EvalDelta{} - a.True(d1.Equal(d2)) - - d2 = EvalDelta{ - GlobalDelta: nil, - LocalDeltas: nil, - Logs: nil, - } - a.True(d1.Equal(d2)) - - d2 = EvalDelta{ - GlobalDelta: StateDelta{}, - LocalDeltas: map[uint64]StateDelta{}, - Logs: []LogItem{}, - } - a.True(d1.Equal(d2)) - - d2 = EvalDelta{ - GlobalDelta: StateDelta{"test": {Action: SetUintAction, Uint: 0}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - GlobalDelta: StateDelta{"test": {Action: SetUintAction, Uint: 0}}, - } - a.True(d1.Equal(d2)) - - d2 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetUintAction, Uint: 0}}, - }, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetUintAction, Uint: 1}}, - }, - } - a.False(d1.Equal(d2)) - - d2 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetUintAction, Uint: 1}}, - }, - } - a.True(d1.Equal(d2)) - - d1 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetBytesAction, Bytes: "val"}}, - }, - } - d2 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetBytesAction, Bytes: "val"}}, - }, - } - a.True(d1.Equal(d2)) - - d2 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 0: {"test": {Action: SetBytesAction, Bytes: "val1"}}, - }, - } - a.False(d1.Equal(d2)) - - d2 = EvalDelta{ - LocalDeltas: map[uint64]StateDelta{ - 1: {"test": {Action: SetBytesAction, Bytes: "val"}}, - }, - } - a.False(d1.Equal(d2)) - - d2 = EvalDelta{ - Logs: []LogItem{{ID: 0, Message: "val"}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - Logs: []LogItem{{ID: 0, Message: "val2"}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - Logs: []LogItem{{ID: 1, Message: "val"}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - Logs: []LogItem{{ID: 1, Message: "val2"}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - Logs: []LogItem{{ID: 0, Message: "val"}, {ID: 0, Message: "val2"}}, - } - a.False(d1.Equal(d2)) - - d1 = EvalDelta{ - Logs: []LogItem{{ID: 0, Message: "val"}}, - } - a.True(d1.Equal(d2)) -} diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 629abaf089..4a5253e420 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -17,6 +17,7 @@ package basics import ( + "encoding/binary" "fmt" "reflect" @@ -379,6 +380,18 @@ type AssetParams struct { Clawback Address `codec:"c"` } +// ToBeHashed implements crypto.Hashable +func (app AppIndex) ToBeHashed() (protocol.HashID, []byte) { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, uint64(app)) + return protocol.AppIndex, buf +} + +// Address yields the "app address" of the app +func (app AppIndex) Address() Address { + return Address(crypto.HashObj(app)) +} + // MakeAccountData returns a UserToken func MakeAccountData(status Status, algos MicroAlgos) AccountData { return AccountData{Status: status, MicroAlgos: algos} diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index 89b4901075..1670fe58d6 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -234,3 +234,22 @@ func TestEncodedAccountAllocationBounds(t *testing.T) { } } } + +func TestAppIndexHashing(t *testing.T) { + partitiontest.PartitionTest(t) + + i := AppIndex(12) + prefix, buf := i.ToBeHashed() + require.Equal(t, protocol.HashID("appID"), prefix) + require.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c}, buf) + + i = AppIndex(12 << 16) + prefix, buf = i.ToBeHashed() + require.Equal(t, protocol.HashID("appID"), prefix) + require.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00}, buf) + + // test value created with: + // python -c "import algosdk.encoding as e; print(e.encode_address(e.checksum(b'appID'+($APPID).to_bytes(8, 'big'))))" + i = AppIndex(77) + require.Equal(t, "PCYUFPA2ZTOYWTP43MX2MOX2OWAIAXUDNC2WFCXAGMRUZ3DYD6BWFDL5YM", i.Address().String()) +} diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index 3998ad854d..a60a9362c4 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -478,6 +478,9 @@ func MakeBlock(prev BlockHeader) Block { if err != nil { logging.Base().Warnf("MakeBlock: computing empty TxnRoot: %v", err) } + // We can't know the entire RewardsState yet, but we can carry over the special addresses. + blk.BlockHeader.RewardsState.FeeSink = prev.RewardsState.FeeSink + blk.BlockHeader.RewardsState.RewardsPool = prev.RewardsState.RewardsPool return blk } diff --git a/data/bookkeeping/genesis.go b/data/bookkeeping/genesis.go index 026eceb01f..6593bcd459 100644 --- a/data/bookkeeping/genesis.go +++ b/data/bookkeeping/genesis.go @@ -147,9 +147,6 @@ func MakeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalance return Block{}, fmt.Errorf("unsupported protocol %s", proto) } - poolAddr := basics.Address(genesisBal.RewardsPool) - incentivePoolBalanceAtGenesis := genesisBal.Balances[poolAddr].MicroAlgos - genesisRewardsState := RewardsState{ FeeSink: genesisBal.FeeSink, RewardsPool: genesisBal.RewardsPool, @@ -158,14 +155,11 @@ func MakeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalance RewardsRecalculationRound: basics.Round(params.RewardsRateRefreshInterval), } + initialRewards := genesisBal.Balances[genesisBal.RewardsPool].MicroAlgos.Raw if params.InitialRewardsRateCalculation { - genesisRewardsState.RewardsRate = basics.SubSaturate(incentivePoolBalanceAtGenesis.Raw, params.MinBalance) / uint64(params.RewardsRateRefreshInterval) + genesisRewardsState.RewardsRate = basics.SubSaturate(initialRewards, params.MinBalance) / uint64(params.RewardsRateRefreshInterval) } else { - genesisRewardsState.RewardsRate = incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval) - } - - genesisProtoState := UpgradeState{ - CurrentProtocol: proto, + genesisRewardsState.RewardsRate = initialRewards / uint64(params.RewardsRateRefreshInterval) } blk := Block{ @@ -177,8 +171,10 @@ func MakeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalance TimeStamp: genesisBal.Timestamp, GenesisID: genesisID, RewardsState: genesisRewardsState, - UpgradeState: genesisProtoState, - UpgradeVote: UpgradeVote{}, + UpgradeState: UpgradeState{ + CurrentProtocol: proto, + }, + UpgradeVote: UpgradeVote{}, }, } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 75765f00cd..fec6943445 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -140,6 +140,19 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti | `setbyte` | pop a byte-array A, integer B, and small integer C (between 0..255). Set the Bth byte of A to C, and push the result | | `concat` | pop two byte-arrays A and B and join them, push the result | +These opcodes return portions of byte arrays, accessed by position, in +various sizes. + +| Op | Description | +| --- | --- | +| `substring s e` | pop a byte-array A. For immediate values in 0..255 S and E: extract a range of bytes from A starting at S up to but not including E, push the substring result. If E < S, or either is larger than the array length, the program fails | +| `substring3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including C, push the substring result. If C < B, or either is larger than the array length, the program fails | +| `extract s l` | pop a byte-array A. For immediate values in 0..255 S and L: extract a range of bytes from A starting at S up to but not including S+L, push the substring result. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails | +| `extract3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B or B+C is larger than the array length, the program fails | +| `extract16bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B or B+2 is larger than the array length, the program fails | +| `extract32bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B or B+4 is larger than the array length, the program fails | +| `extract64bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B or B+8 is larger than the array length, the program fails | + These opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the returned values are the shortest byte-array that can represent the @@ -179,6 +192,15 @@ these results may contain leading zero bytes. | `b^` | A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths | | `b~` | X with all bits inverted | +The following opcodes allow for the construction and submission of +"inner transaction" + +| Op | Description | +| --- | --- | +| `tx_begin` | Prepare a new application action | +| `tx_field f` | Set field F of the current application action | +| `tx_submit` | Execute the current application action. Panic on any failure. | + ### Loading Values @@ -304,6 +326,7 @@ Global fields are fields that are common to all the transactions in the group. I | 7 | LatestTimestamp | uint64 | Last confirmed block UNIX timestamp. Fails if negative. LogicSigVersion >= 2. | | 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails if no such application is executing. LogicSigVersion >= 2. | | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | +| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails if no such application is executing. LogicSigVersion >= 5. | **Asset Fields** @@ -346,6 +369,7 @@ App fields used in the `app_params_get` opcode. | 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | | 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | | 7 | AppCreator | []byte | Creator address | +| 8 | AppAddress | []byte | Address for which this application has authority | ### Flow Control diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 8f62938f45..86b3d2ea88 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -77,6 +77,11 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti @@ Arithmetic.md @@ +These opcodes return portions of byte arrays, accessed by position, in +various sizes. + +@@ Byte_Array_Slicing.md @@ + These opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the returned values are the shortest byte-array that can represent the @@ -89,7 +94,7 @@ explicitly restricted, though only `b*` and `b+` can produce a larger output than their inputs, so there is an implicit length limit of 128 bytes on outputs. -@@ Byteslice_Arithmetic.md @@ +@@ Byte_Array_Arithmetic.md @@ These opcodes operate on the bits of byte-array values. The shorter array is interpeted as though left padded with zeros until it is the @@ -97,7 +102,12 @@ same length as the other input. The returned values are the same length as the longest input. Therefore, unlike array arithmetic, these results may contain leading zero bytes. -@@ Byteslice_Logic.md @@ +@@ Byte_Array_Logic.md @@ + +The following opcodes allow for the construction and submission of +"inner transaction" + +@@ Inner_Transactions.md @@ ### Loading Values diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 70437d2c26..9e4d358dcc 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -464,6 +464,7 @@ FirstValidTime causes the program to fail. The field is reserved for future use. | 7 | LatestTimestamp | uint64 | Last confirmed block UNIX timestamp. Fails if negative. LogicSigVersion >= 2. | | 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails if no such application is executing. LogicSigVersion >= 2. | | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | +| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails if no such application is executing. LogicSigVersion >= 5. | ## gtxn t f @@ -959,6 +960,7 @@ params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset | 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | | 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | | 7 | AppCreator | []byte | Creator address | +| 8 | AppAddress | []byte | Address for which this application has authority | params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value. @@ -1213,3 +1215,30 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Mode: Application `log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes. + +## tx_begin + +- Opcode: 0xb1 +- Pops: _None_ +- Pushes: _None_ +- Prepare a new application action +- LogicSigVersion >= 5 +- Mode: Application + +## tx_field f + +- Opcode: 0xb2 {uint8 transaction field index} +- Pops: *... stack*, any +- Pushes: _None_ +- Set field F of the current application action +- LogicSigVersion >= 5 +- Mode: Application + +## tx_submit + +- Opcode: 0xb3 +- Pops: _None_ +- Pushes: _None_ +- Execute the current application action. Panic on any failure. +- LogicSigVersion >= 5 +- Mode: Application diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 129cfe7b2c..28f6ae29c1 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -386,7 +386,7 @@ func (ops *OpStream) ByteLiteral(val []byte) { found := false var constIndex uint for i, cv := range ops.bytec { - if bytes.Compare(cv, val) == 0 { + if bytes.Equal(cv, val) { found = true constIndex = uint(i) break @@ -1064,6 +1064,23 @@ func assembleAppParams(ops *OpStream, spec *OpSpec, args []string) error { return nil } +func asmTxField(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 1 { + return ops.errorf("%s expects one argument", spec.Name) + } + fs, ok := txnFieldSpecByName[args[0]] + if !ok { + return ops.errorf("txn unknown field: %#v", args[0]) + } + _, ok = txnaFieldSpecByField[fs.field] + if ok { + return ops.errorf("found array field %#v in %s op", args[0], spec.Name) + } + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(fs.field)) + return nil +} + type assembleFunc func(*OpStream, *OpSpec, []string) error // Basic assembly. Any extra bytes of opcode are encoded as byte immediates. @@ -1228,6 +1245,17 @@ func typeUncover(ops *OpStream, args []string) (StackTypes, StackTypes) { return anys, returns } +func typeTxField(ops *OpStream, args []string) (StackTypes, StackTypes) { + if len(args) != 1 { + return oneAny, nil + } + fs, ok := txnFieldSpecByName[args[0]] + if !ok { + return oneAny, nil + } + return StackTypes{fs.ftype}, nil +} + // keywords handle parsing and assembling special asm language constructs like 'addr' // We use OpSpec here, but somewhat degenerate, since they don't have opcodes or eval functions var keywords = map[string]OpSpec{ @@ -1970,7 +1998,6 @@ type disassembleState struct { rerun bool nextpc int - err error intc []uint64 bytec [][]byte @@ -2043,7 +2070,7 @@ func parseIntcblock(program []byte, pc int) (intc []uint64, nextpc int, err erro return } -func checkIntConstBlock(cx *evalContext) error { +func checkIntConstBlock(cx *EvalContext) error { pos := cx.pc + 1 numInts, bytesUsed := binary.Uvarint(cx.program[pos:]) if bytesUsed <= 0 { @@ -2111,7 +2138,7 @@ func parseBytecBlock(program []byte, pc int) (bytec [][]byte, nextpc int, err er return } -func checkByteConstBlock(cx *evalContext) error { +func checkByteConstBlock(cx *EvalContext) error { pos := cx.pc + 1 numItems, bytesUsed := binary.Uvarint(cx.program[pos:]) if bytesUsed <= 0 { @@ -2269,7 +2296,7 @@ func disPushInt(dis *disassembleState, spec *OpSpec) (string, error) { dis.nextpc = pos + bytesUsed return fmt.Sprintf("%s %d", spec.Name, val), nil } -func checkPushInt(cx *evalContext) error { +func checkPushInt(cx *EvalContext) error { opPushInt(cx) return cx.err } @@ -2289,7 +2316,7 @@ func disPushBytes(dis *disassembleState, spec *OpSpec) (string, error) { dis.nextpc = int(end) return fmt.Sprintf("%s 0x%s // %s", spec.Name, hex.EncodeToString(bytes), guessByteFormat(bytes)), nil } -func checkPushBytes(cx *evalContext) error { +func checkPushBytes(cx *EvalContext) error { opPushBytes(cx) return cx.err } @@ -2440,6 +2467,20 @@ func disAppParams(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("%s %s", spec.Name, AppParamsFieldNames[arg]), nil } +func disTxField(dis *disassembleState, spec *OpSpec) (string, error) { + lastIdx := dis.pc + 1 + if len(dis.program) <= lastIdx { + missing := lastIdx - len(dis.program) + 1 + return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) + } + dis.nextpc = dis.pc + 2 + arg := dis.program[dis.pc+1] + if int(arg) >= len(TxnFieldNames) { + return "", fmt.Errorf("invalid txfield arg index %d at pc=%d", arg, dis.pc) + } + return fmt.Sprintf("%s %s", spec.Name, TxnFieldNames[arg]), nil +} + type disInfo struct { pcOffset []PCOffset hasStatefulOps bool diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 82d2e6af9a..134c4cb7e0 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -303,15 +303,18 @@ extract 0 8 int 0 int 8 extract3 -int 0 +int 0 extract64bits int 0 extract32bits -int 0 +int 0 extract16bits log txn Nonparticipation gtxn 0 Nonparticipation +tx_begin +tx_field Sender +tx_submit ` var nonsense = map[uint64]string{ @@ -327,7 +330,7 @@ var compiled = map[uint64]string{ 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", - 5: "052004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d816472064e014f0180070123456789abcd57000824810858245b245a2459b03139330039", + 5: "052004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d816472064e014f0180070123456789abcd57000824810858245b245a2459b03139330039b1b200b3", } func pseudoOp(opcode string) bool { @@ -896,8 +899,7 @@ int ClearState for v := uint64(optimizeConstantsEnabledVersion); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - ops, err := AssembleStringWithVersion(program, v) - require.NoError(t, err) + ops := testProg(t, program, v) s := hex.EncodeToString(ops.Program) require.Equal(t, mutateProgVersion(v, expected), s) }) @@ -1315,6 +1317,7 @@ txn LocalNumByteSlice gtxn 12 Fee txn ExtraProgramPages txn Nonparticipation +global CurrentApplicationAddress `, AssemblerMaxVersion) for _, globalField := range GlobalFieldNames { if !strings.Contains(text, globalField) { @@ -1973,8 +1976,7 @@ func TestPragmas(t *testing.T) { testProg(t, `#pragma version 100`, assemblerNoVersion, expect{1, "unsupported version: 100"}) - ops, err := AssembleStringWithVersion(`int 1`, 99) - require.Error(t, err) + testProg(t, `int 1`, 99, expect{0, "Can not assemble version 99"}) testProg(t, `#pragma version 0`, assemblerNoVersion, expect{1, "unsupported version: 0"}) @@ -1983,7 +1985,7 @@ func TestPragmas(t *testing.T) { expect{1, `bad #pragma version: "a"`}) // will default to 1 - ops = testProg(t, "int 3", assemblerNoVersion) + ops := testProg(t, "int 3", assemblerNoVersion) require.Equal(t, uint64(1), ops.Version) require.Equal(t, uint8(1), ops.Program[0]) @@ -1997,7 +1999,7 @@ func TestPragmas(t *testing.T) { testProg(t, "#pragma version 1", 2, expect{1, "version mismatch..."}) testProg(t, "#pragma version 2", 1, expect{1, "version mismatch..."}) - ops = testProg(t, "#pragma version 2\n#pragma version 1", assemblerNoVersion, + testProg(t, "#pragma version 2\n#pragma version 1", assemblerNoVersion, expect{2, "version mismatch..."}) // repetitive, but fine @@ -2103,7 +2105,7 @@ func TestErrShortBytecblock(t *testing.T) { _, _, err = parseIntcblock(ops.Program, 0) require.Equal(t, err, errShortIntcblock) - var cx evalContext + var cx EvalContext cx.program = ops.Program err = checkIntConstBlock(&cx) require.Equal(t, err, errShortIntcblock) @@ -2221,3 +2223,13 @@ func TestUncoverAsm(t *testing.T) { testProg(t, `int 1; byte "jj"; byte "ayush"; byte "john"; int 5; uncover 4; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, expect{5, "+ arg 1..."}) } + +func TestTxTypes(t *testing.T) { + testProg(t, "tx_begin; tx_field Sender", 5, expect{2, "tx_field Sender expects 1 stack argument..."}) + testProg(t, "tx_begin; int 1; tx_field Sender", 5, expect{3, "...wanted type []byte got uint64"}) + testProg(t, "tx_begin; byte 0x56127823; tx_field Sender", 5) + + testProg(t, "tx_begin; tx_field Amount", 5, expect{2, "tx_field Amount expects 1 stack argument..."}) + testProg(t, "tx_begin; byte 0x87123376; tx_field Amount", 5, expect{3, "...wanted type uint64 got []byte"}) + testProg(t, "tx_begin; int 1; tx_field Amount", 5) +} diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index e07ce5dbd9..226c948405 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -22,8 +22,11 @@ import ( "strings" "testing" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions/logictest" + "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" ) @@ -371,6 +374,151 @@ func TestBackwardCompatTEALv1(t *testing.T) { } +// ensure v2 fields error on pre TEAL v2 logicsig version +// ensure v2 fields error in v1 program +func TestBackwardCompatGlobalFields(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + var fields []globalFieldSpec + for _, fs := range globalFieldSpecs { + if fs.version > 1 { + fields = append(fields, fs) + } + } + require.Greater(t, len(fields), 1) + + ledger := logictest.MakeLedger(nil) + for _, field := range fields { + text := fmt.Sprintf("global %s", field.field.String()) + // check assembler fails if version before introduction + testLine(t, text, assemblerNoVersion, "...available in version...") + for v := uint64(0); v < field.version; v++ { + testLine(t, text, v, "...available in version...") + } + + ops := testProg(t, text, AssemblerMaxVersion) + + proto := config.Consensus[protocol.ConsensusV23] + require.False(t, proto.Application) + ep := defaultEvalParams(nil, nil) + ep.Proto = &proto + ep.Ledger = ledger + + // check failure with version check + _, err := Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "greater than protocol supported version") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "greater than protocol supported version") + + // check opcodes failures + ops.Program[0] = 1 // set version to 1 + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid global field") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid global field") + + // check opcodes failures + ops.Program[0] = 0 // set version to 0 + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid global field") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid global field") + } +} + +// ensure v2 fields error in v1 program +func TestBackwardCompatTxnFields(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + var fields []txnFieldSpec + for _, fs := range txnFieldSpecs { + if fs.version > 1 { + fields = append(fields, fs) + } + } + require.Greater(t, len(fields), 1) + + tests := []string{ + "txn %s", + "gtxn 0 %s", + } + + ledger := logictest.MakeLedger(nil) + txn := makeSampleTxn() + // We'll reject too early if we have a nonzero RekeyTo, because that + // field must be zero for every txn in the group if this is an old + // TEAL version + txn.Txn.RekeyTo = basics.Address{} + txgroup := makeSampleTxnGroup(txn) + for _, fs := range fields { + field := fs.field.String() + for _, command := range tests { + text := fmt.Sprintf(command, field) + asmError := "...available in version ..." + if _, ok := txnaFieldSpecByField[fs.field]; ok { + parts := strings.Split(text, " ") + op := parts[0] + asmError = fmt.Sprintf("found array field %#v in %s op", field, op) + } + // check assembler fails if version before introduction + testLine(t, text, assemblerNoVersion, asmError) + for v := uint64(0); v < fs.version; v++ { + testLine(t, text, v, asmError) + } + + ops, err := AssembleStringWithVersion(text, AssemblerMaxVersion) + if _, ok := txnaFieldSpecByField[fs.field]; ok { + // "txn Accounts" is invalid, so skip evaluation + require.Error(t, err, asmError) + continue + } else { + require.NoError(t, err) + } + + proto := config.Consensus[protocol.ConsensusV23] + require.False(t, proto.Application) + ep := defaultEvalParams(nil, nil) + ep.Proto = &proto + ep.Ledger = ledger + ep.TxnGroup = txgroup + + // check failure with version check + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "greater than protocol supported version") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "greater than protocol supported version") + + // check opcodes failures + ops.Program[0] = 1 // set version to 1 + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid txn field") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid txn field") + + // check opcodes failures + ops.Program[0] = 0 // set version to 0 + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid txn field") + _, err = Eval(ops.Program, ep) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid txn field") + } + } +} + func TestBackwardCompatAssemble(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index ae6e57bd33..c4477473ca 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -76,7 +76,7 @@ type DebugState struct { Error string `codec:"error"` // global/local state changes are updated every step. Stateful TEAL only. - basics.EvalDelta + transactions.EvalDelta } // GetProgramID returns program or execution ID that is string representation of sha256 checksum. @@ -86,7 +86,7 @@ func GetProgramID(program []byte) string { return hex.EncodeToString(hash[:]) } -func makeDebugState(cx *evalContext) DebugState { +func makeDebugState(cx *EvalContext) DebugState { disasm, dsInfo, err := disassembleInstrumented(cx.program, nil) if err != nil { // Report disassembly error as program text @@ -194,7 +194,7 @@ func valueDeltaToValueDelta(vd *basics.ValueDelta) basics.ValueDelta { } } -func (cx *evalContext) refreshDebugState() *DebugState { +func (cx *EvalContext) refreshDebugState() *DebugState { ds := &cx.debugState // Update pc, line, error, stack, and scratch space @@ -204,12 +204,12 @@ func (cx *evalContext) refreshDebugState() *DebugState { ds.Error = cx.err.Error() } - stack := make([]basics.TealValue, len(cx.stack), len(cx.stack)) + stack := make([]basics.TealValue, len(cx.stack)) for i, sv := range cx.stack { stack[i] = stackValueToTealValue(&sv) } - scratch := make([]basics.TealValue, len(cx.scratch), len(cx.scratch)) + scratch := make([]basics.TealValue, len(cx.scratch)) for i, sv := range cx.scratch { scratch[i] = stackValueToTealValue(&sv) } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 27d2963a4a..7a2c918bfe 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -151,7 +151,10 @@ var opDocByName = map[string]string{ "b^": "A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths", "b~": "X with all bits inverted", - "log": "write bytes to log state of the current application", + "log": "write bytes to log state of the current application", + "tx_begin": "Prepare a new application action", + "tx_field": "Set field F of the current application action", + "tx_submit": "Execute the current application action. Panic on any failure.", } // OpDoc returns a description of the op @@ -191,6 +194,7 @@ var opcodeImmediateNotes = map[string]string{ "asset_holding_get": "{uint8 asset holding field index}", "asset_params_get": "{uint8 asset params field index}", "app_params_get": "{uint8 app params field index}", + "tx_field": "{uint8 transaction field index}", } // OpImmediateNote returns a short string about immediate data which follows the op byte @@ -248,13 +252,14 @@ func OpDocExtra(opName string) string { // OpGroups is groupings of ops for documentation purposes. var OpGroups = map[string][]string{ - "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, - "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract16bits", "extract32bits", "extract64bits"}, - "Byteslice Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, - "Byteslice Logic": {"b|", "b&", "b^", "b~"}, - "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids"}, - "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, - "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "log"}, + "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, + "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract16bits", "extract32bits", "extract64bits"}, + "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, + "Byte Array Logic": {"b|", "b&", "b^", "b~"}, + "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids"}, + "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, + "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "log"}, + "Inner Transactions": {"tx_begin", "tx_field", "tx_submit"}, } // OpCost indicates the cost of an operation over the range of @@ -388,16 +393,17 @@ func TxnFieldDocs() map[string]string { } var globalFieldDocs = map[string]string{ - "MinTxnFee": "micro Algos", - "MinBalance": "micro Algos", - "MaxTxnLife": "rounds", - "ZeroAddress": "32 byte address of all zero bytes", - "GroupSize": "Number of transactions in this atomic transaction group. At least 1", - "LogicSigVersion": "Maximum supported TEAL version", - "Round": "Current round number", - "LatestTimestamp": "Last confirmed block UNIX timestamp. Fails if negative", - "CurrentApplicationID": "ID of current application executing. Fails if no such application is executing", - "CreatorAddress": "Address of the creator of the current application. Fails if no such application is executing", + "MinTxnFee": "micro Algos", + "MinBalance": "micro Algos", + "MaxTxnLife": "rounds", + "ZeroAddress": "32 byte address of all zero bytes", + "GroupSize": "Number of transactions in this atomic transaction group. At least 1", + "LogicSigVersion": "Maximum supported TEAL version", + "Round": "Current round number", + "LatestTimestamp": "Last confirmed block UNIX timestamp. Fails if negative", + "CurrentApplicationID": "ID of current application executing. Fails if no such application is executing", + "CreatorAddress": "Address of the creator of the current application. Fails if no such application is executing", + "CurrentApplicationAddress": "Address that the current application controls. Fails if no such application is executing", } // GlobalFieldDocs are notes on fields available in `global` with extra versioning info if any @@ -460,4 +466,5 @@ var AppParamsFieldDocs = map[string]string{ "AppLocalNumByteSlice": "Number of byte array values allowed in Local State", "AppExtraProgramPages": "Number of Extra Program Pages of code space", "AppCreator": "Creator address", + "AppAddress": "Address for which this application has authority", } diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index ad87f4a010..cf147fa5a8 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -72,7 +72,7 @@ func TestOpGroupCoverage(t *testing.T) { } for name, seen := range opsSeen { if !seen { - t.Errorf("warning: op %#v not in any group of OpGroupList\n", name) + t.Errorf("warning: op %#v not in any group of OpGroups\n", name) } } } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index de56fa2850..084474a433 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -99,6 +99,21 @@ func (sv *stackValue) String() string { return fmt.Sprintf("%d 0x%x", sv.Uint, sv.Uint) } +func (sv *stackValue) address() (addr basics.Address, err error) { + if len(sv.Bytes) != len(addr) { + return basics.Address{}, errors.New("not an address") + } + copy(addr[:], sv.Bytes) + return +} + +func (sv *stackValue) uint() (uint64, error) { + if sv.Bytes != nil { + return 0, errors.New("not a uint64") + } + return sv.Uint, nil +} + func stackValueFromTealValue(tv *basics.TealValue) (sv stackValue, err error) { switch tv.Type { case basics.TealBytesType: @@ -145,6 +160,7 @@ func (sv *stackValue) toTealValue() (tv basics.TealValue) { type LedgerForLogic interface { Balance(addr basics.Address) (basics.MicroAlgos, error) MinBalance(addr basics.Address, proto *config.ConsensusParams) (basics.MicroAlgos, error) + Authorizer(addr basics.Address) (basics.Address, error) Round() basics.Round LatestTimestamp() int64 @@ -152,7 +168,6 @@ type LedgerForLogic interface { AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error) AppParams(aidx basics.AppIndex) (basics.AppParams, basics.Address, error) ApplicationID() basics.AppIndex - CreatorAddress() basics.Address OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) GetCreatableID(groupIdx int) basics.CreatableIndex @@ -164,9 +179,9 @@ type LedgerForLogic interface { SetGlobal(key string, value basics.TealValue) error DelGlobal(key string) error - GetDelta(txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) + GetDelta(txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) - AppendLog(txn *transactions.Transaction, value string) error + Perform(txn *transactions.Transaction, spec transactions.SpecialAddresses) (transactions.ApplyData, error) } // EvalSideEffects contains data returned from evaluation @@ -222,6 +237,14 @@ type EvalParams struct { // MinTealVersion is nil, we will compute it ourselves MinTealVersion *uint64 + // Amount "overpaid" by the top-level transactions of the + // group. Often 0. When positive, it is spent by application + // actions. Shared value across a group's txns, so that it + // can be updated. nil is interpretted as 0. + FeeCredit *uint64 + + Specials *transactions.SpecialAddresses + // determines eval mode: runModeSignature or runModeApplication runModeFlags runMode @@ -229,8 +252,8 @@ type EvalParams struct { PooledApplicationBudget *uint64 } -type opEvalFunc func(cx *evalContext) -type opCheckFunc func(cx *evalContext) error +type opEvalFunc func(cx *EvalContext) +type opCheckFunc func(cx *EvalContext) error type runMode uint64 @@ -281,23 +304,32 @@ func (ep EvalParams) log() logging.Logger { type scratchSpace = [256]stackValue -type evalContext struct { +// EvalContext is the execution context of AVM bytecode. It contains +// the full state of the running program, and tracks some of the +// things that the program has been done, like log message and inner +// transactions. +type EvalContext struct { EvalParams stack []stackValue callstack []int - program []byte // txn.Lsig.Logic ? - pc int - nextpc int - err error - intc []uint64 - bytec [][]byte - version uint64 - scratch scratchSpace - - cost int // cost incurred so far - logCalls int // number of log calls so far - logSize int // log size of the program so far + + program []byte + pc int + nextpc int + err error + intc []uint64 + bytec [][]byte + version uint64 + scratch scratchSpace + + subtxn *transactions.SignedTxn // place to build for tx_submit + // The transactions Performed() and their effects + InnerTxns []transactions.SignedTxnWithAD + + cost int // cost incurred so far + Logs []string + logSize int // total log size so far // Set of PC values that branches we've seen so far might // go. So, if checkStep() skips one, that branch is trying to @@ -311,6 +343,7 @@ type evalContext struct { programHashCached crypto.Digest txidCache map[int]transactions.Txid + appAddrCache map[basics.AppIndex]basics.Address // Stores state & disassembly for the optional debugger debugState DebugState @@ -365,26 +398,38 @@ func (pe PanicError) Error() string { var errLogicSigNotSupported = errors.New("LogicSig not supported") var errTooManyArgs = errors.New("LogicSig has too many arguments") -// EvalStateful executes stateful TEAL program -func EvalStateful(program []byte, params EvalParams) (pass bool, err error) { - var cx evalContext +// EvalStatefulCx executes stateful TEAL program +func EvalStatefulCx(program []byte, params EvalParams) (bool, *EvalContext, error) { + var cx EvalContext cx.EvalParams = params cx.runModeFlags = runModeApplication - pass, err = eval(program, &cx) - if cx.EvalParams.Proto.EnableAppCostPooling && cx.EvalParams.PooledApplicationBudget != nil { + pass, err := eval(program, &cx) + + // The following two updates show a need for something like a + // GroupEvalContext, as we are currently tucking things into the + // EvalParams so that they are available to later calls. + + // update pooled budget + if cx.Proto.EnableAppCostPooling && cx.PooledApplicationBudget != nil { // if eval passes, then budget is always greater than cost, so should not have underflow - *cx.EvalParams.PooledApplicationBudget = basics.SubSaturate(*cx.EvalParams.PooledApplicationBudget, uint64(cx.cost)) + *cx.PooledApplicationBudget = basics.SubSaturate(*cx.PooledApplicationBudget, uint64(cx.cost)) } - - // set side effects + // update side effects cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) - return + + return pass, &cx, err +} + +// EvalStateful is a lighter weight interface that doesn't return the EvalContext +func EvalStateful(program []byte, params EvalParams) (bool, error) { + pass, _, err := EvalStatefulCx(program, params) + return pass, err } // Eval checks to see if a transaction passes logic // A program passes successfully if it finishes with one int element on the stack that is non-zero. func Eval(program []byte, params EvalParams) (pass bool, err error) { - var cx evalContext + var cx EvalContext cx.EvalParams = params cx.runModeFlags = runModeSignature return eval(program, &cx) @@ -392,7 +437,7 @@ func Eval(program []byte, params EvalParams) (pass bool, err error) { // eval implementation // A program passes successfully if it finishes with one int element on the stack that is non-zero. -func eval(program []byte, cx *evalContext) (pass bool, err error) { +func eval(program []byte, cx *EvalContext) (pass bool, err error) { defer func() { if x := recover(); x != nil { buf := make([]byte, 16*1024) @@ -558,7 +603,7 @@ func check(program []byte, params EvalParams) (err error) { return fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", minVersion, version) } - var cx evalContext + var cx EvalContext cx.version = version cx.pc = vlen cx.EvalParams = params @@ -616,7 +661,7 @@ func boolToUint(x bool) uint64 { // MaxStackDepth should move to consensus params const MaxStackDepth = 1000 -func (cx *evalContext) step() { +func (cx *EvalContext) step() { opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -691,7 +736,7 @@ func (cx *evalContext) step() { // perhaps we could have an interface that allows // disassembly to use the cx directly. But for now, // we don't want to worry about the dissassembly - // routines mucking about in the excution context + // routines mucking about in the execution context // (changing the pc, for example) and this gives a big // improvement of dryrun readability dstate := &disassembleState{program: cx.program, pc: cx.pc, numericTargets: true, intc: cx.intc, bytec: cx.bytec} @@ -741,7 +786,7 @@ func (cx *evalContext) step() { } } -func (cx *evalContext) checkStep() (int, error) { +func (cx *EvalContext) checkStep() (int, error) { cx.instructionStarts[cx.pc] = true opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -783,11 +828,11 @@ func (cx *evalContext) checkStep() (int, error) { return deets.Cost, nil } -func opErr(cx *evalContext) { +func opErr(cx *EvalContext) { cx.err = errors.New("TEAL runtime encountered err opcode") } -func opReturn(cx *evalContext) { +func opReturn(cx *EvalContext) { // Achieve the end condition: // Take the last element on the stack and make it the return value (only element on the stack) // Move the pc to the end of the program @@ -797,7 +842,7 @@ func opReturn(cx *evalContext) { cx.nextpc = len(cx.program) } -func opAssert(cx *evalContext) { +func opAssert(cx *EvalContext) { last := len(cx.stack) - 1 if cx.stack[last].Uint != 0 { cx.stack = cx.stack[:last] @@ -806,13 +851,13 @@ func opAssert(cx *evalContext) { cx.err = fmt.Errorf("assert failed pc=%d", cx.pc) } -func opSwap(cx *evalContext) { +func opSwap(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[last], cx.stack[prev] = cx.stack[prev], cx.stack[last] } -func opSelect(cx *evalContext) { +func opSelect(cx *EvalContext) { last := len(cx.stack) - 1 // condition on top prev := last - 1 // true is one down pprev := prev - 1 // false below that @@ -823,14 +868,14 @@ func opSelect(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opSHA256(cx *evalContext) { +func opSHA256(cx *EvalContext) { last := len(cx.stack) - 1 hash := sha256.Sum256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } // The Keccak256 variant of SHA-3 is implemented for compatibility with Ethereum -func opKeccak256(cx *evalContext) { +func opKeccak256(cx *EvalContext) { last := len(cx.stack) - 1 hasher := sha3.NewLegacyKeccak256() hasher.Write(cx.stack[last].Bytes) @@ -845,13 +890,13 @@ func opKeccak256(cx *evalContext) { // stability and portability in case the rest of Algorand ever moves // to a different default hash. For stability of this language, at // that time a new opcode should be made with the new hash. -func opSHA512_256(cx *evalContext) { +func opSHA512_256(cx *EvalContext) { last := len(cx.stack) - 1 hash := sha512.Sum512_256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } -func opPlus(cx *evalContext) { +func opPlus(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint += cx.stack[last].Uint @@ -870,7 +915,7 @@ func opAddwImpl(x, y uint64) (carry uint64, sum uint64) { return } -func opAddw(cx *evalContext) { +func opAddw(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 carry, sum := opAddwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -896,7 +941,7 @@ func opDivModwImpl(hiNum, loNum, hiDen, loDen uint64) (hiQuo uint64, loQuo uint6 rem.Uint64() } -func opDivModw(cx *evalContext) { +func opDivModw(cx *EvalContext) { loDen := len(cx.stack) - 1 hiDen := loDen - 1 if cx.stack[loDen].Uint == 0 && cx.stack[hiDen].Uint == 0 { @@ -913,7 +958,7 @@ func opDivModw(cx *evalContext) { cx.stack[loDen].Uint = loRem } -func opMinus(cx *evalContext) { +func opMinus(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint > cx.stack[prev].Uint { @@ -924,7 +969,7 @@ func opMinus(cx *evalContext) { cx.stack = cx.stack[:last] } -func opDiv(cx *evalContext) { +func opDiv(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -935,7 +980,7 @@ func opDiv(cx *evalContext) { cx.stack = cx.stack[:last] } -func opModulo(cx *evalContext) { +func opModulo(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -946,7 +991,7 @@ func opModulo(cx *evalContext) { cx.stack = cx.stack[:last] } -func opMul(cx *evalContext) { +func opMul(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Uint @@ -980,7 +1025,7 @@ func opMulwImpl(x, y uint64) (high64 uint64, low64 uint64, err error) { return } -func opMulw(cx *evalContext) { +func opMulw(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 high, low, err := opMulwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -992,7 +1037,7 @@ func opMulw(cx *evalContext) { cx.stack[last].Uint = low } -func opLt(cx *evalContext) { +func opLt(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint < cx.stack[last].Uint @@ -1004,22 +1049,22 @@ func opLt(cx *evalContext) { cx.stack = cx.stack[:last] } -func opGt(cx *evalContext) { +func opGt(cx *EvalContext) { opSwap(cx) opLt(cx) } -func opLe(cx *evalContext) { +func opLe(cx *EvalContext) { opGt(cx) opNot(cx) } -func opGe(cx *evalContext) { +func opGe(cx *EvalContext) { opLt(cx) opNot(cx) } -func opAnd(cx *evalContext) { +func opAnd(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) && (cx.stack[last].Uint != 0) @@ -1031,7 +1076,7 @@ func opAnd(cx *evalContext) { cx.stack = cx.stack[:last] } -func opOr(cx *evalContext) { +func opOr(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) || (cx.stack[last].Uint != 0) @@ -1043,7 +1088,7 @@ func opOr(cx *evalContext) { cx.stack = cx.stack[:last] } -func opEq(cx *evalContext) { +func opEq(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 ta := cx.stack[prev].argType() @@ -1067,12 +1112,12 @@ func opEq(cx *evalContext) { cx.stack = cx.stack[:last] } -func opNeq(cx *evalContext) { +func opNeq(cx *EvalContext) { opEq(cx) opNot(cx) } -func opNot(cx *evalContext) { +func opNot(cx *EvalContext) { last := len(cx.stack) - 1 cond := cx.stack[last].Uint == 0 if cond { @@ -1082,13 +1127,13 @@ func opNot(cx *evalContext) { } } -func opLen(cx *evalContext) { +func opLen(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = uint64(len(cx.stack[last].Bytes)) cx.stack[last].Bytes = nil } -func opItob(cx *evalContext) { +func opItob(cx *EvalContext) { last := len(cx.stack) - 1 ibytes := make([]byte, 8) binary.BigEndian.PutUint64(ibytes, cx.stack[last].Uint) @@ -1097,7 +1142,7 @@ func opItob(cx *evalContext) { cx.stack[last].Bytes = ibytes } -func opBtoi(cx *evalContext) { +func opBtoi(cx *EvalContext) { last := len(cx.stack) - 1 ibytes := cx.stack[last].Bytes if len(ibytes) > 8 { @@ -1113,33 +1158,33 @@ func opBtoi(cx *evalContext) { cx.stack[last].Bytes = nil } -func opBitOr(cx *evalContext) { +func opBitOr(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint | cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitAnd(cx *evalContext) { +func opBitAnd(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint & cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitXor(cx *evalContext) { +func opBitXor(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint ^ cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitNot(cx *evalContext) { +func opBitNot(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = cx.stack[last].Uint ^ 0xffffffffffffffff } -func opShiftLeft(cx *evalContext) { +func opShiftLeft(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint > 63 { @@ -1150,7 +1195,7 @@ func opShiftLeft(cx *evalContext) { cx.stack = cx.stack[:last] } -func opShiftRight(cx *evalContext) { +func opShiftRight(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint > 63 { @@ -1161,7 +1206,7 @@ func opShiftRight(cx *evalContext) { cx.stack = cx.stack[:last] } -func opSqrt(cx *evalContext) { +func opSqrt(cx *EvalContext) { /* It would not be safe to use math.Sqrt, because we would have to convert our u64 to an f64, but f64 cannot represent all u64s exactly. @@ -1188,7 +1233,7 @@ func opSqrt(cx *evalContext) { cx.stack[last].Uint = root >> 1 } -func opBitLen(cx *evalContext) { +func opBitLen(cx *EvalContext) { last := len(cx.stack) - 1 if cx.stack[last].argType() == StackUint64 { cx.stack[last].Uint = uint64(bits.Len64(cx.stack[last].Uint)) @@ -1235,7 +1280,7 @@ func opExpImpl(base uint64, exp uint64) (uint64, error) { return answer, nil } -func opExp(cx *evalContext) { +func opExp(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -1281,7 +1326,7 @@ func opExpwImpl(base uint64, exp uint64) (*big.Int, error) { } -func opExpw(cx *evalContext) { +func opExpw(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -1299,7 +1344,7 @@ func opExpw(cx *evalContext) { cx.stack[last].Uint = lo } -func opBytesBinOp(cx *evalContext, result *big.Int, op func(x, y *big.Int) *big.Int) { +func opBytesBinOp(cx *EvalContext, result *big.Int, op func(x, y *big.Int) *big.Int) { last := len(cx.stack) - 1 prev := last - 1 @@ -1319,17 +1364,17 @@ func opBytesBinOp(cx *evalContext, result *big.Int, op func(x, y *big.Int) *big. cx.stack = cx.stack[:last] } -func opBytesPlus(cx *evalContext) { +func opBytesPlus(cx *EvalContext) { result := new(big.Int) opBytesBinOp(cx, result, result.Add) } -func opBytesMinus(cx *evalContext) { +func opBytesMinus(cx *EvalContext) { result := new(big.Int) opBytesBinOp(cx, result, result.Sub) } -func opBytesDiv(cx *evalContext) { +func opBytesDiv(cx *EvalContext) { result := new(big.Int) checkDiv := func(x, y *big.Int) *big.Int { if y.BitLen() == 0 { @@ -1341,12 +1386,12 @@ func opBytesDiv(cx *evalContext) { opBytesBinOp(cx, result, checkDiv) } -func opBytesMul(cx *evalContext) { +func opBytesMul(cx *EvalContext) { result := new(big.Int) opBytesBinOp(cx, result, result.Mul) } -func opBytesLt(cx *evalContext) { +func opBytesLt(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -1366,22 +1411,22 @@ func opBytesLt(cx *evalContext) { cx.stack = cx.stack[:last] } -func opBytesGt(cx *evalContext) { +func opBytesGt(cx *EvalContext) { opSwap(cx) opBytesLt(cx) } -func opBytesLe(cx *evalContext) { +func opBytesLe(cx *EvalContext) { opBytesGt(cx) opNot(cx) } -func opBytesGe(cx *evalContext) { +func opBytesGe(cx *EvalContext) { opBytesLt(cx) opNot(cx) } -func opBytesEq(cx *evalContext) { +func opBytesEq(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -1401,12 +1446,12 @@ func opBytesEq(cx *evalContext) { cx.stack = cx.stack[:last] } -func opBytesNeq(cx *evalContext) { +func opBytesNeq(cx *EvalContext) { opBytesEq(cx) opNot(cx) } -func opBytesModulo(cx *evalContext) { +func opBytesModulo(cx *EvalContext) { result := new(big.Int) checkMod := func(x, y *big.Int) *big.Int { if y.BitLen() == 0 { @@ -1429,7 +1474,7 @@ func zpad(smaller []byte, size int) []byte { // They can be returned in either order, but the first slice returned // must be newly allocated, and already in place at the top of stack // (the original top having been popped). -func opBytesBinaryLogicPrep(cx *evalContext) ([]byte, []byte) { +func opBytesBinaryLogicPrep(cx *EvalContext) ([]byte, []byte) { last := len(cx.stack) - 1 prev := last - 1 @@ -1447,28 +1492,28 @@ func opBytesBinaryLogicPrep(cx *evalContext) ([]byte, []byte) { return fresh, other } -func opBytesBitOr(cx *evalContext) { +func opBytesBitOr(cx *EvalContext) { a, b := opBytesBinaryLogicPrep(cx) for i := range a { a[i] = a[i] | b[i] } } -func opBytesBitAnd(cx *evalContext) { +func opBytesBitAnd(cx *EvalContext) { a, b := opBytesBinaryLogicPrep(cx) for i := range a { a[i] = a[i] & b[i] } } -func opBytesBitXor(cx *evalContext) { +func opBytesBitXor(cx *EvalContext) { a, b := opBytesBinaryLogicPrep(cx) for i := range a { a[i] = a[i] ^ b[i] } } -func opBytesBitNot(cx *evalContext) { +func opBytesBitNot(cx *EvalContext) { last := len(cx.stack) - 1 fresh := make([]byte, len(cx.stack[last].Bytes)) @@ -1478,7 +1523,7 @@ func opBytesBitNot(cx *evalContext) { cx.stack[last].Bytes = fresh } -func opBytesZero(cx *evalContext) { +func opBytesZero(cx *EvalContext) { last := len(cx.stack) - 1 length := cx.stack[last].Uint if length > MaxStringSize { @@ -1488,35 +1533,35 @@ func opBytesZero(cx *evalContext) { cx.stack[last].Bytes = make([]byte, length) } -func opIntConstBlock(cx *evalContext) { +func opIntConstBlock(cx *EvalContext) { cx.intc, cx.nextpc, cx.err = parseIntcblock(cx.program, cx.pc) } -func opIntConstN(cx *evalContext, n uint) { +func opIntConstN(cx *EvalContext, n uint) { if n >= uint(len(cx.intc)) { cx.err = fmt.Errorf("intc [%d] beyond %d constants", n, len(cx.intc)) return } cx.stack = append(cx.stack, stackValue{Uint: cx.intc[n]}) } -func opIntConstLoad(cx *evalContext) { +func opIntConstLoad(cx *EvalContext) { n := uint(cx.program[cx.pc+1]) opIntConstN(cx, n) } -func opIntConst0(cx *evalContext) { +func opIntConst0(cx *EvalContext) { opIntConstN(cx, 0) } -func opIntConst1(cx *evalContext) { +func opIntConst1(cx *EvalContext) { opIntConstN(cx, 1) } -func opIntConst2(cx *evalContext) { +func opIntConst2(cx *EvalContext) { opIntConstN(cx, 2) } -func opIntConst3(cx *evalContext) { +func opIntConst3(cx *EvalContext) { opIntConstN(cx, 3) } -func opPushInt(cx *evalContext) { +func opPushInt(cx *EvalContext) { val, bytesUsed := binary.Uvarint(cx.program[cx.pc+1:]) if bytesUsed <= 0 { cx.err = fmt.Errorf("could not decode int at pc=%d", cx.pc+1) @@ -1527,35 +1572,35 @@ func opPushInt(cx *evalContext) { cx.nextpc = cx.pc + 1 + bytesUsed } -func opByteConstBlock(cx *evalContext) { +func opByteConstBlock(cx *EvalContext) { cx.bytec, cx.nextpc, cx.err = parseBytecBlock(cx.program, cx.pc) } -func opByteConstN(cx *evalContext, n uint) { +func opByteConstN(cx *EvalContext, n uint) { if n >= uint(len(cx.bytec)) { cx.err = fmt.Errorf("bytec [%d] beyond %d constants", n, len(cx.bytec)) return } cx.stack = append(cx.stack, stackValue{Bytes: cx.bytec[n]}) } -func opByteConstLoad(cx *evalContext) { +func opByteConstLoad(cx *EvalContext) { n := uint(cx.program[cx.pc+1]) opByteConstN(cx, n) } -func opByteConst0(cx *evalContext) { +func opByteConst0(cx *EvalContext) { opByteConstN(cx, 0) } -func opByteConst1(cx *evalContext) { +func opByteConst1(cx *EvalContext) { opByteConstN(cx, 1) } -func opByteConst2(cx *evalContext) { +func opByteConst2(cx *EvalContext) { opByteConstN(cx, 2) } -func opByteConst3(cx *evalContext) { +func opByteConst3(cx *EvalContext) { opByteConstN(cx, 3) } -func opPushBytes(cx *evalContext) { +func opPushBytes(cx *EvalContext) { pos := cx.pc + 1 length, bytesUsed := binary.Uvarint(cx.program[pos:]) if bytesUsed <= 0 { @@ -1573,7 +1618,7 @@ func opPushBytes(cx *evalContext) { cx.nextpc = int(end) } -func opArgN(cx *evalContext, n uint64) { +func opArgN(cx *EvalContext, n uint64) { if n >= uint64(len(cx.Txn.Lsig.Args)) { cx.err = fmt.Errorf("cannot load arg[%d] of %d", n, len(cx.Txn.Lsig.Args)) return @@ -1582,24 +1627,24 @@ func opArgN(cx *evalContext, n uint64) { cx.stack = append(cx.stack, stackValue{Bytes: val}) } -func opArg(cx *evalContext) { +func opArg(cx *EvalContext) { n := uint64(cx.program[cx.pc+1]) opArgN(cx, n) } -func opArg0(cx *evalContext) { +func opArg0(cx *EvalContext) { opArgN(cx, 0) } -func opArg1(cx *evalContext) { +func opArg1(cx *EvalContext) { opArgN(cx, 1) } -func opArg2(cx *evalContext) { +func opArg2(cx *EvalContext) { opArgN(cx, 2) } -func opArg3(cx *evalContext) { +func opArg3(cx *EvalContext) { opArgN(cx, 3) } -func branchTarget(cx *evalContext) (int, error) { +func branchTarget(cx *EvalContext) (int, error) { offset := int16(uint16(cx.program[cx.pc+1])<<8 | uint16(cx.program[cx.pc+2])) if offset < 0 && cx.version < backBranchEnabledVersion { return 0, fmt.Errorf("negative branch offset %x", offset) @@ -1620,7 +1665,7 @@ func branchTarget(cx *evalContext) (int, error) { } // checks any branch that is {op} {int16 be offset} -func checkBranch(cx *evalContext) error { +func checkBranch(cx *EvalContext) error { cx.nextpc = cx.pc + 3 target, err := branchTarget(cx) if err != nil { @@ -1635,7 +1680,7 @@ func checkBranch(cx *evalContext) error { cx.branchTargets[target] = true return nil } -func opBnz(cx *evalContext) { +func opBnz(cx *EvalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isNonZero := cx.stack[last].Uint != 0 @@ -1650,7 +1695,7 @@ func opBnz(cx *evalContext) { } } -func opBz(cx *evalContext) { +func opBz(cx *EvalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isZero := cx.stack[last].Uint == 0 @@ -1665,7 +1710,7 @@ func opBz(cx *evalContext) { } } -func opB(cx *evalContext) { +func opB(cx *EvalContext) { target, err := branchTarget(cx) if err != nil { cx.err = err @@ -1674,12 +1719,12 @@ func opB(cx *evalContext) { cx.nextpc = target } -func opCallSub(cx *evalContext) { +func opCallSub(cx *EvalContext) { cx.callstack = append(cx.callstack, cx.pc+3) opB(cx) } -func opRetSub(cx *evalContext) { +func opRetSub(cx *EvalContext) { top := len(cx.callstack) - 1 if top < 0 { cx.err = errors.New("retsub with empty callstack") @@ -1690,24 +1735,24 @@ func opRetSub(cx *evalContext) { cx.nextpc = target } -func opPop(cx *evalContext) { +func opPop(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack = cx.stack[:last] } -func opDup(cx *evalContext) { +func opDup(cx *EvalContext) { last := len(cx.stack) - 1 sv := cx.stack[last] cx.stack = append(cx.stack, sv) } -func opDup2(cx *evalContext) { +func opDup2(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack = append(cx.stack, cx.stack[prev:]...) } -func opDig(cx *evalContext) { +func opDig(cx *EvalContext) { depth := int(uint(cx.program[cx.pc+1])) idx := len(cx.stack) - 1 - depth // Need to check stack size explicitly here because checkArgs() doesn't understand dig @@ -1720,7 +1765,7 @@ func opDig(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opCover(cx *evalContext) { +func opCover(cx *EvalContext) { depth := int(cx.program[cx.pc+1]) topIdx := len(cx.stack) - 1 idx := topIdx - depth @@ -1735,7 +1780,7 @@ func opCover(cx *evalContext) { cx.stack[idx] = sv } -func opUncover(cx *evalContext) { +func opUncover(cx *EvalContext) { depth := int(cx.program[cx.pc+1]) topIdx := len(cx.stack) - 1 idx := topIdx - depth @@ -1751,7 +1796,7 @@ func opUncover(cx *evalContext) { cx.stack[topIdx] = sv } -func (cx *evalContext) assetHoldingToValue(holding *basics.AssetHolding, fs assetHoldingFieldSpec) (sv stackValue, err error) { +func (cx *EvalContext) assetHoldingToValue(holding *basics.AssetHolding, fs assetHoldingFieldSpec) (sv stackValue, err error) { switch fs.field { case AssetBalance: sv.Uint = holding.Amount @@ -1768,7 +1813,7 @@ func (cx *evalContext) assetHoldingToValue(holding *basics.AssetHolding, fs asse return } -func (cx *evalContext) assetParamsToValue(params *basics.AssetParams, creator basics.Address, fs assetParamsFieldSpec) (sv stackValue, err error) { +func (cx *EvalContext) assetParamsToValue(params *basics.AssetParams, creator basics.Address, fs assetParamsFieldSpec) (sv stackValue, err error) { switch fs.field { case AssetTotal: sv.Uint = params.Total @@ -1805,7 +1850,7 @@ func (cx *evalContext) assetParamsToValue(params *basics.AssetParams, creator ba return } -func (cx *evalContext) appParamsToValue(params *basics.AppParams, creator basics.Address, fs appParamsFieldSpec) (sv stackValue, err error) { +func (cx *EvalContext) appParamsToValue(params *basics.AppParams, fs appParamsFieldSpec) (sv stackValue, err error) { switch fs.field { case AppApprovalProgram: sv.Bytes = params.ApprovalProgram[:] @@ -1821,9 +1866,8 @@ func (cx *evalContext) appParamsToValue(params *basics.AppParams, creator basics sv.Uint = params.LocalStateSchema.NumByteSlice case AppExtraProgramPages: sv.Uint = uint64(params.ExtraProgramPages) - case AppCreator: - sv.Bytes = creator[:] default: + // The pseudo fields AppCreator and AppAddress are handled before this method err = fmt.Errorf("invalid app_params_get field %d", fs.field) return } @@ -1836,12 +1880,12 @@ func (cx *evalContext) appParamsToValue(params *basics.AppParams, creator basics // TxnFieldToTealValue is a thin wrapper for txnFieldToStack for external use func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field TxnField, arrayFieldIdx uint64) (basics.TealValue, error) { - cx := evalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} + cx := EvalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} sv, err := cx.txnFieldToStack(txn, field, arrayFieldIdx, groupIndex) return sv.toTealValue(), err } -func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { +func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { // Initialize txidCache if necessary if cx.txidCache == nil { cx.txidCache = make(map[int]transactions.Txid, len(cx.TxnGroup)) @@ -1857,7 +1901,7 @@ func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) tr return txid } -func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { +func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { err = nil switch field { case Sender: @@ -2021,7 +2065,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF return } -func opTxn(cx *evalContext) { +func opTxn(cx *EvalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -2041,7 +2085,7 @@ func opTxn(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opTxna(cx *evalContext) { +func opTxna(cx *EvalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -2062,7 +2106,7 @@ func opTxna(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxn(cx *evalContext) { +func opGtxn(cx *EvalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxn lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -2095,7 +2139,7 @@ func opGtxn(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxna(cx *evalContext) { +func opGtxna(cx *EvalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxna lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -2122,7 +2166,7 @@ func opGtxna(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxns(cx *evalContext) { +func opGtxns(cx *EvalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -2156,7 +2200,7 @@ func opGtxns(cx *evalContext) { cx.stack[last] = sv } -func opGtxnsa(cx *evalContext) { +func opGtxnsa(cx *EvalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -2184,7 +2228,7 @@ func opGtxnsa(cx *evalContext) { cx.stack[last] = sv } -func opGaidImpl(cx *evalContext, groupIdx int, opName string) (sv stackValue, err error) { +func opGaidImpl(cx *EvalContext, groupIdx int, opName string) (sv stackValue, err error) { if groupIdx >= len(cx.TxnGroup) { err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, groupIdx, len(cx.TxnGroup)) return @@ -2211,7 +2255,7 @@ func opGaidImpl(cx *evalContext, groupIdx int, opName string) (sv stackValue, er return } -func opGaid(cx *evalContext) { +func opGaid(cx *EvalContext) { groupIdx := int(uint(cx.program[cx.pc+1])) sv, err := opGaidImpl(cx, groupIdx, "gaid") if err != nil { @@ -2222,7 +2266,7 @@ func opGaid(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGaids(cx *evalContext) { +func opGaids(cx *EvalContext) { last := len(cx.stack) - 1 groupIdx := int(cx.stack[last].Uint) sv, err := opGaidImpl(cx, groupIdx, "gaids") @@ -2234,7 +2278,7 @@ func opGaids(cx *evalContext) { cx.stack[last] = sv } -func (cx *evalContext) getRound() (rnd uint64, err error) { +func (cx *EvalContext) getRound() (rnd uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2242,7 +2286,7 @@ func (cx *evalContext) getRound() (rnd uint64, err error) { return uint64(cx.Ledger.Round()), nil } -func (cx *evalContext) getLatestTimestamp() (timestamp uint64, err error) { +func (cx *EvalContext) getLatestTimestamp() (timestamp uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2255,15 +2299,35 @@ func (cx *evalContext) getLatestTimestamp() (timestamp uint64, err error) { return uint64(ts), nil } -func (cx *evalContext) getApplicationID() (rnd uint64, err error) { +func (cx *EvalContext) getApplicationID() (uint64, error) { if cx.Ledger == nil { - err = fmt.Errorf("ledger not available") - return + return 0, fmt.Errorf("ledger not available") } return uint64(cx.Ledger.ApplicationID()), nil } -func (cx *evalContext) getCreatableID(groupIndex int) (cid uint64, err error) { +func (cx *EvalContext) getApplicationAddress() (basics.Address, error) { + if cx.Ledger == nil { + return basics.Address{}, fmt.Errorf("ledger not available") + } + + // Initialize appAddrCache if necessary + if cx.appAddrCache == nil { + cx.appAddrCache = make(map[basics.AppIndex]basics.Address) + } + + appID := cx.Ledger.ApplicationID() + // Hashes are expensive, so we cache computed app addrs + appAddr, ok := cx.appAddrCache[appID] + if !ok { + appAddr = appID.Address() + cx.appAddrCache[appID] = appAddr + } + + return appAddr, nil +} + +func (cx *EvalContext) getCreatableID(groupIndex int) (cid uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2271,17 +2335,20 @@ func (cx *evalContext) getCreatableID(groupIndex int) (cid uint64, err error) { return uint64(cx.Ledger.GetCreatableID(groupIndex)), nil } -func (cx *evalContext) getCreatorAddress() ([]byte, error) { +func (cx *EvalContext) getCreatorAddress() ([]byte, error) { if cx.Ledger == nil { return nil, fmt.Errorf("ledger not available") } - addr := cx.Ledger.CreatorAddress() - return addr[:], nil + _, creator, err := cx.Ledger.AppParams(cx.Ledger.ApplicationID()) + if err != nil { + return nil, fmt.Errorf("No params for current app") + } + return creator[:], nil } var zeroAddress basics.Address -func (cx *evalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, err error) { +func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, err error) { switch fs.field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee @@ -2301,6 +2368,10 @@ func (cx *evalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er sv.Uint, err = cx.getLatestTimestamp() case CurrentApplicationID: sv.Uint, err = cx.getApplicationID() + case CurrentApplicationAddress: + var addr basics.Address + addr, err = cx.getApplicationAddress() + sv.Bytes = addr[:] case CreatorAddress: sv.Bytes, err = cx.getCreatorAddress() default: @@ -2314,7 +2385,7 @@ func (cx *evalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er return sv, err } -func opGlobal(cx *evalContext) { +func opGlobal(cx *EvalContext) { globalField := GlobalField(cx.program[cx.pc+1]) fs, ok := globalFieldSpecByField[globalField] if !ok || fs.version > cx.version { @@ -2349,14 +2420,14 @@ func (msg Msg) ToBeHashed() (protocol.HashID, []byte) { } // programHash lets us lazily compute H(cx.program) -func (cx *evalContext) programHash() crypto.Digest { +func (cx *EvalContext) programHash() crypto.Digest { if cx.programHashCached == (crypto.Digest{}) { cx.programHashCached = crypto.HashObj(Program(cx.program)) } return cx.programHashCached } -func opEd25519verify(cx *evalContext) { +func opEd25519verify(cx *EvalContext) { last := len(cx.stack) - 1 // index of PK prev := last - 1 // index of signature pprev := prev - 1 // index of data @@ -2385,19 +2456,19 @@ func opEd25519verify(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opLoad(cx *evalContext) { +func opLoad(cx *EvalContext) { gindex := int(uint(cx.program[cx.pc+1])) cx.stack = append(cx.stack, cx.scratch[gindex]) } -func opStore(cx *evalContext) { +func opStore(cx *EvalContext) { gindex := int(uint(cx.program[cx.pc+1])) last := len(cx.stack) - 1 cx.scratch[gindex] = cx.stack[last] cx.stack = cx.stack[:last] } -func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int, opName string) (scratchValue stackValue, err error) { +func opGloadImpl(cx *EvalContext, groupIdx int, scratchIdx int, opName string) (scratchValue stackValue, err error) { if groupIdx >= len(cx.TxnGroup) { err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, groupIdx, len(cx.TxnGroup)) return @@ -2419,7 +2490,7 @@ func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int, opName string) ( return } -func opGload(cx *evalContext) { +func opGload(cx *EvalContext) { groupIdx := int(uint(cx.program[cx.pc+1])) scratchIdx := int(uint(cx.program[cx.pc+2])) scratchValue, err := opGloadImpl(cx, groupIdx, scratchIdx, "gload") @@ -2431,7 +2502,7 @@ func opGload(cx *evalContext) { cx.stack = append(cx.stack, scratchValue) } -func opGloads(cx *evalContext) { +func opGloads(cx *EvalContext) { last := len(cx.stack) - 1 groupIdx := int(cx.stack[last].Uint) scratchIdx := int(uint(cx.program[cx.pc+1])) @@ -2444,7 +2515,7 @@ func opGloads(cx *evalContext) { cx.stack[last] = scratchValue } -func opConcat(cx *evalContext) { +func opConcat(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Bytes @@ -2472,14 +2543,14 @@ func substring(x []byte, start, end int) (out []byte, err error) { return } -func opSubstring(cx *evalContext) { +func opSubstring(cx *EvalContext) { last := len(cx.stack) - 1 start := cx.program[cx.pc+1] end := cx.program[cx.pc+2] cx.stack[last].Bytes, cx.err = substring(cx.stack[last].Bytes, int(start), int(end)) } -func opSubstring3(cx *evalContext) { +func opSubstring3(cx *EvalContext) { last := len(cx.stack) - 1 // end prev := last - 1 // start pprev := prev - 1 // bytes @@ -2493,7 +2564,7 @@ func opSubstring3(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opGetBit(cx *evalContext) { +func opGetBit(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 idx := cx.stack[last].Uint @@ -2530,7 +2601,7 @@ func opGetBit(cx *evalContext) { cx.stack = cx.stack[:last] } -func opSetBit(cx *evalContext) { +func opSetBit(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2582,7 +2653,7 @@ func opSetBit(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opGetByte(cx *evalContext) { +func opGetByte(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -2598,7 +2669,7 @@ func opGetByte(cx *evalContext) { cx.stack = cx.stack[:last] } -func opSetByte(cx *evalContext) { +func opSetByte(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2627,7 +2698,7 @@ func opExtractImpl(x []byte, start, length int) (out []byte, err error) { return } -func opExtract(cx *evalContext) { +func opExtract(cx *EvalContext) { last := len(cx.stack) - 1 startIdx := cx.program[cx.pc+1] lengthIdx := cx.program[cx.pc+2] @@ -2639,7 +2710,7 @@ func opExtract(cx *evalContext) { cx.stack[last].Bytes, cx.err = opExtractImpl(cx.stack[last].Bytes, int(startIdx), length) } -func opExtract3(cx *evalContext) { +func opExtract3(cx *EvalContext) { last := len(cx.stack) - 1 // length prev := last - 1 // start byteArrayIdx := prev - 1 // bytes @@ -2664,7 +2735,7 @@ func convertBytesToInt(x []byte) (out uint64) { return } -func opExtractNBytes(cx *evalContext, n int) { +func opExtractNBytes(cx *EvalContext, n int) { last := len(cx.stack) - 1 // start prev := last - 1 // bytes startIdx := cx.stack[last].Uint @@ -2675,35 +2746,54 @@ func opExtractNBytes(cx *evalContext, n int) { cx.stack = cx.stack[:last] } -func opExtract16Bits(cx *evalContext) { +func opExtract16Bits(cx *EvalContext) { opExtractNBytes(cx, 2) // extract 2 bytes } -func opExtract32Bits(cx *evalContext) { +func opExtract32Bits(cx *EvalContext) { opExtractNBytes(cx, 4) // extract 4 bytes } -func opExtract64Bits(cx *evalContext) { +func opExtract64Bits(cx *EvalContext) { opExtractNBytes(cx, 8) // extract 8 bytes } -func accountReference(cx *evalContext, account stackValue) (basics.Address, uint64, error) { +// accountReference yields the address and Accounts offset designated +// by a stackValue. If the stackValue is the app account, it need not +// be in the Accounts array, therefore len(Accounts) + 1 is returned +// as the index. This unusual convention is based on the existing +// convention that 0 is the sender, 1-len(Accounts) are indexes into +// Accounts array, and so len+1 is the next available value. This +// will allow encoding into EvalDelta efficiently when it becomes +// necessary (when apps change local state on their own account). +func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uint64, error) { if account.argType() == StackUint64 { addr, err := cx.Txn.Txn.AddressByIndex(account.Uint, cx.Txn.Txn.Sender) return addr, account.Uint, err } - addr := basics.Address{} - copy(addr[:], account.Bytes) + addr, err := account.address() + if err != nil { + return addr, 0, err + } idx, err := cx.Txn.Txn.IndexByAddress(addr, cx.Txn.Txn.Sender) + + if err != nil { + // Application address is acceptable. index is meaningless though + appAddr, _ := cx.getApplicationAddress() + if appAddr == addr { + return addr, uint64(len(cx.Txn.Txn.Accounts) + 1), nil + } + } + return addr, idx, err } type opQuery func(basics.Address, *config.ConsensusParams) (basics.MicroAlgos, error) -func opBalanceQuery(cx *evalContext, query opQuery, item string) error { +func opBalanceQuery(cx *EvalContext, query opQuery, item string) error { last := len(cx.stack) - 1 // account (index or actual address) - addr, _, err := accountReference(cx, cx.stack[last]) + addr, _, err := cx.accountReference(cx.stack[last]) if err != nil { return err } @@ -2717,7 +2807,7 @@ func opBalanceQuery(cx *evalContext, query opQuery, item string) error { cx.stack[last].Uint = microAlgos.Raw return nil } -func opBalance(cx *evalContext) { +func opBalance(cx *EvalContext) { if cx.Ledger == nil { cx.err = fmt.Errorf("ledger not available") return @@ -2731,7 +2821,7 @@ func opBalance(cx *evalContext) { cx.err = err } } -func opMinBalance(cx *evalContext) { +func opMinBalance(cx *EvalContext) { if cx.Ledger == nil { cx.err = fmt.Errorf("ledger not available") return @@ -2743,7 +2833,7 @@ func opMinBalance(cx *evalContext) { } } -func opAppOptedIn(cx *evalContext) { +func opAppOptedIn(cx *EvalContext) { last := len(cx.stack) - 1 // app prev := last - 1 // account @@ -2752,7 +2842,7 @@ func opAppOptedIn(cx *evalContext) { return } - addr, _, err := accountReference(cx, cx.stack[prev]) + addr, _, err := cx.accountReference(cx.stack[prev]) if err != nil { cx.err = err return @@ -2780,7 +2870,7 @@ func opAppOptedIn(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppLocalGet(cx *evalContext) { +func opAppLocalGet(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // account @@ -2796,7 +2886,7 @@ func opAppLocalGet(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppLocalGetEx(cx *evalContext) { +func opAppLocalGetEx(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // app id pprev := prev - 1 // account @@ -2820,13 +2910,13 @@ func opAppLocalGetEx(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppLocalGetImpl(cx *evalContext, appID uint64, key []byte, acct stackValue) (result stackValue, ok bool, err error) { +func opAppLocalGetImpl(cx *EvalContext, appID uint64, key []byte, acct stackValue) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return } - addr, accountIdx, err := accountReference(cx, acct) + addr, accountIdx, err := cx.accountReference(acct) if err != nil { return } @@ -2848,7 +2938,7 @@ func opAppLocalGetImpl(cx *evalContext, appID uint64, key []byte, acct stackValu return } -func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { +func opAppGetGlobalStateImpl(cx *EvalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2870,7 +2960,7 @@ func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (resu return } -func opAppGlobalGet(cx *evalContext) { +func opAppGlobalGet(cx *EvalContext) { last := len(cx.stack) - 1 // state key key := cx.stack[last].Bytes @@ -2884,7 +2974,7 @@ func opAppGlobalGet(cx *evalContext) { cx.stack[last] = result } -func opAppGlobalGetEx(cx *evalContext) { +func opAppGlobalGetEx(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // app @@ -2905,7 +2995,7 @@ func opAppGlobalGetEx(cx *evalContext) { cx.stack[last] = isOk } -func opAppLocalPut(cx *evalContext) { +func opAppLocalPut(cx *EvalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key pprev := prev - 1 // account @@ -2918,7 +3008,7 @@ func opAppLocalPut(cx *evalContext) { return } - addr, accountIdx, err := accountReference(cx, cx.stack[pprev]) + addr, accountIdx, err := cx.accountReference(cx.stack[pprev]) if err == nil { err = cx.Ledger.SetLocal(addr, key, sv.toTealValue(), accountIdx) } @@ -2931,7 +3021,7 @@ func opAppLocalPut(cx *evalContext) { cx.stack = cx.stack[:pprev] } -func opAppGlobalPut(cx *evalContext) { +func opAppGlobalPut(cx *EvalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key @@ -2952,7 +3042,7 @@ func opAppGlobalPut(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opAppLocalDel(cx *evalContext) { +func opAppLocalDel(cx *EvalContext) { last := len(cx.stack) - 1 // key prev := last - 1 // account @@ -2963,7 +3053,7 @@ func opAppLocalDel(cx *evalContext) { return } - addr, accountIdx, err := accountReference(cx, cx.stack[prev]) + addr, accountIdx, err := cx.accountReference(cx.stack[prev]) if err == nil { err = cx.Ledger.DelLocal(addr, key, accountIdx) } @@ -2975,7 +3065,7 @@ func opAppLocalDel(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opAppGlobalDel(cx *evalContext) { +func opAppGlobalDel(cx *EvalContext) { last := len(cx.stack) - 1 // key key := string(cx.stack[last].Bytes) @@ -3000,7 +3090,7 @@ func opAppGlobalDel(cx *evalContext) { // often called an "index". But it was not a basics.AssetIndex or // basics.ApplicationIndex. -func appReference(cx *evalContext, ref uint64, foreign bool) (basics.AppIndex, error) { +func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, error) { if cx.version >= directRefEnabledVersion { if ref == 0 { return cx.Ledger.ApplicationID(), nil @@ -3040,7 +3130,7 @@ func appReference(cx *evalContext, ref uint64, foreign bool) (basics.AppIndex, e return basics.AppIndex(0), fmt.Errorf("invalid App reference %d", ref) } -func asaReference(cx *evalContext, ref uint64, foreign bool) (basics.AssetIndex, error) { +func asaReference(cx *EvalContext, ref uint64, foreign bool) (basics.AssetIndex, error) { if cx.version >= directRefEnabledVersion { // In recent versions, accept either kind of ASA reference if ref < uint64(len(cx.Txn.Txn.ForeignAssets)) { @@ -3067,7 +3157,7 @@ func asaReference(cx *evalContext, ref uint64, foreign bool) (basics.AssetIndex, } -func opAssetHoldingGet(cx *evalContext) { +func opAssetHoldingGet(cx *EvalContext) { last := len(cx.stack) - 1 // asset prev := last - 1 // account @@ -3083,7 +3173,7 @@ func opAssetHoldingGet(cx *evalContext) { return } - addr, _, err := accountReference(cx, cx.stack[prev]) + addr, _, err := cx.accountReference(cx.stack[prev]) if err != nil { cx.err = err return @@ -3111,7 +3201,7 @@ func opAssetHoldingGet(cx *evalContext) { cx.stack[last].Uint = exist } -func opAssetParamsGet(cx *evalContext) { +func opAssetParamsGet(cx *EvalContext) { last := len(cx.stack) - 1 // asset if cx.Ledger == nil { @@ -3148,7 +3238,7 @@ func opAssetParamsGet(cx *evalContext) { cx.stack = append(cx.stack, stackValue{Uint: exist}) } -func opAppParamsGet(cx *evalContext) { +func opAppParamsGet(cx *EvalContext) { last := len(cx.stack) - 1 // app if cx.Ledger == nil { @@ -3174,7 +3264,16 @@ func opAppParamsGet(cx *evalContext) { if params, creator, err := cx.Ledger.AppParams(app); err == nil { // params exist, read the value exist = 1 - value, err = cx.appParamsToValue(¶ms, creator, fs) + + switch fs.field { + case AppCreator: + value.Bytes = creator[:] + case AppAddress: + address := app.Address() + value.Bytes = address[:] + default: + value, err = cx.appParamsToValue(¶ms, fs) + } if err != nil { cx.err = err return @@ -3185,25 +3284,209 @@ func opAppParamsGet(cx *evalContext) { cx.stack = append(cx.stack, stackValue{Uint: exist}) } -func opLog(cx *evalContext) { +func opLog(cx *EvalContext) { last := len(cx.stack) - 1 - if cx.logCalls == MaxLogCalls { + if len(cx.Logs) == MaxLogCalls { cx.err = fmt.Errorf("too many log calls in program. up to %d is allowed", MaxLogCalls) return } - cx.logCalls++ log := cx.stack[last] cx.logSize += len(log.Bytes) if cx.logSize > MaxLogSize { cx.err = fmt.Errorf("program logs too large. %d bytes > %d bytes limit", cx.logSize, MaxLogSize) return } - // write log to applyData - err := cx.Ledger.AppendLog(&cx.Txn.Txn, string(log.Bytes)) + cx.Logs = append(cx.Logs, string(log.Bytes)) + cx.stack = cx.stack[:last] +} + +func authorizedSender(cx *EvalContext, addr basics.Address) bool { + appAddr, err := cx.getApplicationAddress() + if err != nil { + return false + } + authorizer, err := cx.Ledger.Authorizer(addr) + if err != nil { + return false + } + return appAddr == authorizer +} + +func opTxBegin(cx *EvalContext) { + if cx.subtxn != nil { + cx.err = errors.New("tx_begin without tx_submit") + return + } + // Start fresh + cx.subtxn = &transactions.SignedTxn{} + // Fill in defaults. + addr, err := cx.getApplicationAddress() if err != nil { cx.err = err return } - cx.stack = cx.stack[:last] + + fee := cx.Proto.MinTxnFee + if cx.FeeCredit != nil { + // Use credit to shrink the fee, but don't change FeeCredit + // here, because they might never tx_submit, or they might + // change the fee. Do it in tx_submit. + fee = basics.SubSaturate(fee, *cx.FeeCredit) + } + cx.subtxn.Txn.Header = transactions.Header{ + Sender: addr, // Default, to simplify usage + Fee: basics.MicroAlgos{Raw: fee}, + FirstValid: cx.Txn.Txn.FirstValid, + LastValid: cx.Txn.Txn.LastValid, + } +} + +func (cx *EvalContext) availableAsset(sv stackValue) (basics.AssetIndex, error) { + aid, err := sv.uint() + if err != nil { + return basics.AssetIndex(0), err + } + // Ensure that aid is in Foreign Assets + for _, assetID := range cx.Txn.Txn.ForeignAssets { + if assetID == basics.AssetIndex(aid) { + return basics.AssetIndex(aid), nil + } + } + return basics.AssetIndex(0), fmt.Errorf("invalid Asset reference %d", aid) +} + +func opTxField(cx *EvalContext) { + if cx.subtxn == nil { + cx.err = errors.New("tx_field without tx_begin") + return + } + last := len(cx.stack) - 1 + field := TxnField(uint64(cx.program[cx.pc+1])) + sv := cx.stack[last] + switch field { + case Type: + cx.subtxn.Txn.Type = protocol.TxType(sv.Bytes) + case TypeEnum: + var i uint64 + i, cx.err = sv.uint() + if i < uint64(len(TxnTypeNames)) { + cx.subtxn.Txn.Type = protocol.TxType(TxnTypeNames[i]) + } + + case Sender: + cx.subtxn.Txn.Sender, _, cx.err = cx.accountReference(sv) + case Fee: + cx.subtxn.Txn.Fee.Raw, cx.err = sv.uint() + // FirstValid, LastValid unsettable: no motivation + // Note unsettable: would be strange, as this "Note" would not end up "chain-visible" + // GenesisID, GenesisHash unsettable: surely makes no sense + // Group unsettable: Can't make groups from AVM (yet?) + // Lease unsettable: This seems potentially useful. + // RekeyTo unsettable: Feels dangerous for first release. + + // KeyReg not allowed yet, so no fields settable + + case Receiver: + cx.subtxn.Txn.Receiver, _, cx.err = cx.accountReference(sv) + case Amount: + cx.subtxn.Txn.Amount.Raw, cx.err = sv.uint() + case CloseRemainderTo: + cx.subtxn.Txn.CloseRemainderTo, _, cx.err = cx.accountReference(sv) + + case XferAsset: + cx.subtxn.Txn.XferAsset, cx.err = cx.availableAsset(sv) + case AssetAmount: + cx.subtxn.Txn.AssetAmount, cx.err = sv.uint() + case AssetSender: + cx.subtxn.Txn.AssetSender, _, cx.err = cx.accountReference(sv) + case AssetReceiver: + cx.subtxn.Txn.AssetReceiver, _, cx.err = cx.accountReference(sv) + case AssetCloseTo: + cx.subtxn.Txn.AssetCloseTo, _, cx.err = cx.accountReference(sv) + + // acfg likely next + + // afrz seems easy but not high demand + + // appl needs to wait. Can't call AVM from AVM. + + default: + cx.err = fmt.Errorf("invalid txfield %s", field) + } + + cx.stack = cx.stack[:last] // pop +} + +func opTxSubmit(cx *EvalContext) { + if cx.Ledger == nil { + cx.err = fmt.Errorf("ledger not available") + return + } + + if cx.subtxn == nil { + cx.err = errors.New("tx_submit without tx_begin") + return + } + + if len(cx.InnerTxns) >= cx.Proto.MaxInnerTransactions { + cx.err = errors.New("tx_submit with MaxInnerTransactions") + return + } + + // Error out on anything unusual. Allow pay, axfer. + switch cx.subtxn.Txn.Type { + case protocol.PaymentTx, protocol.AssetTransferTx: + // only pay and axfer for now + default: + cx.err = fmt.Errorf("Invalid inner transaction type %s", cx.subtxn.Txn.Type) + return + } + + // The goal is to follow the same invariants used by the + // transaction pool. Namely that any transaction that makes it + // to Perform (which is equivalent to eval.applyTransaction) + // is authorized, and WellFormed. + if !authorizedSender(cx, cx.subtxn.Txn.Sender) { + cx.err = fmt.Errorf("unauthorized") + return + } + + // Recall that WellFormed does not care about individual + // transaction fees because of fee pooling. So we check below. + cx.err = cx.subtxn.Txn.WellFormed(*cx.Specials, *cx.Proto) + if cx.err != nil { + return + } + + paid := cx.subtxn.Txn.Fee.Raw + if paid >= cx.Proto.MinTxnFee { + // Over paying - accumulate into FeeCredit + overpaid := paid - cx.Proto.MinTxnFee + if cx.FeeCredit == nil { + cx.FeeCredit = new(uint64) + } + *cx.FeeCredit = basics.AddSaturate(*cx.FeeCredit, overpaid) + } else { + underpaid := cx.Proto.MinTxnFee - paid + // Try to pay with FeeCredit, else fail. + if cx.FeeCredit != nil && *cx.FeeCredit >= underpaid { + *cx.FeeCredit -= underpaid + } else { + // This should be impossible until we allow changing the Fee + cx.err = fmt.Errorf("fee too small") + return + } + } + + ad, err := cx.Ledger.Perform(&cx.subtxn.Txn, *cx.Specials) + if err != nil { + cx.err = err + return + } + cx.InnerTxns = append(cx.InnerTxns, transactions.SignedTxnWithAD{ + SignedTxn: *cx.subtxn, + ApplyData: ad, + }) + cx.subtxn = nil } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go new file mode 100644 index 0000000000..24b46a40d2 --- /dev/null +++ b/data/transactions/logic/evalAppTxn_test.go @@ -0,0 +1,308 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/data/basics" +) + +func TestActionTypes(t *testing.T) { + ep, ledger := makeSampleEnv() + testApp(t, "tx_submit; int 1;", ep, "tx_submit without tx_begin") + testApp(t, "int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "tx_field without tx_begin") + testApp(t, "tx_begin; tx_submit; int 1;", ep, "Invalid inner transaction type") + // bad type + testApp(t, "tx_begin; byte \"pya\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + + // good types, not alllowed yet + testApp(t, "tx_begin; byte \"keyreg\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + // same, as enums + testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; int appl; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") + + // "insufficient balance" because app account is charged fee + // (defaults make these 0 pay|axfer to zero address, from app account) + testApp(t, "tx_begin; byte \"pay\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; byte \"axfer\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; int axfer; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + + // Establish 888 as the app id, and fund it. + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + + testApp(t, "tx_begin; byte \"pay\"; tx_field Type; tx_submit; int 1;", ep) + testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep) +} + +func TestAppPay(t *testing.T) { + pay := ` + tx_begin + tx_field Amount + tx_field Receiver + tx_field Sender + int pay + tx_field TypeEnum + tx_submit + int 1 +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + testApp(t, "txn Sender; balance; int 0; ==;", ep) + testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + "insufficient balance") + ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) + + // You might expect this to fail because of min balance issue + // (receiving account only gets 100 microalgos). It does not fail at + // this level, instead, we must be certain that the existing min + // balance check in eval.transaction() properly notices and fails + // the transaction later. This fits with the model that we check + // min balances once at the end of each "top-level" transaction. + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) + + // 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001 + testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + testApp(t, "txn Receiver; balance; int 100; ==", ep) + + close := ` + tx_begin + int pay; tx_field TypeEnum + txn Receiver; tx_field CloseRemainderTo + tx_submit + int 1 +` + testApp(t, close, ep) + testApp(t, "global CurrentApplicationAddress; balance; !", ep) + // Receiver got most of the algos (except 1001 for fee) + testApp(t, "txn Receiver; balance; int 997998; ==", ep) +} + +func TestAppAssetOptIn(t *testing.T) { + ep, ledger := makeSampleEnv() + // Establish 888 as the app id, and fund it. + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(basics.AppIndex(888).Address(), 200000) + + axfer := ` +tx_begin +int axfer; tx_field TypeEnum; +int 25; tx_field XferAsset; +int 2; tx_field AssetAmount; +txn Sender; tx_field AssetReceiver; +tx_submit +int 1 +` + testApp(t, axfer, ep, "invalid Asset reference") + ep.Txn.Txn.ForeignAssets = append(ep.Txn.Txn.ForeignAssets, 25) + testApp(t, axfer, ep, "not opted in") // app account not opted in + optin := ` +tx_begin +int axfer; tx_field TypeEnum; +int 25; tx_field XferAsset; +int 0; tx_field AssetAmount; +global CurrentApplicationAddress; tx_field AssetReceiver; +tx_submit +int 1 +` + testApp(t, optin, ep, "does not exist") + // Asset 25 + ledger.NewAsset(ep.Txn.Txn.Sender, 25, basics.AssetParams{ + Total: 10, + UnitName: "x", + AssetName: "Cross", + }) + testApp(t, optin, ep) + + testApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 + + // Fund the app account with the asset + ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false) + testApp(t, axfer, ep) + testApp(t, axfer, ep) + testApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) + testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) + + close := ` +tx_begin +int axfer; tx_field TypeEnum; +int 25; tx_field XferAsset; +int 0; tx_field AssetAmount; +txn Sender; tx_field AssetReceiver; +txn Sender; tx_field AssetCloseTo; +tx_submit +int 1 +` + testApp(t, close, ep) + testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) +} + +func TestRekeyPay(t *testing.T) { + pay := ` + tx_begin + tx_field Amount + tx_field Receiver + tx_field Sender + int pay + tx_field TypeEnum + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + testApp(t, "txn Sender; balance; int 0; ==;", ep) + testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + ledger.NewAccount(ep.Txn.Txn.Sender, 120+ep.Proto.MinTxnFee) + ledger.Rekey(ep.Txn.Txn.Sender, basics.AppIndex(888).Address()) + testApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) + // Note that the Sender would fail min balance check if we did it here. + // It seems proper to wait until end of txn though. + // See explanation in logicLedger's Perform() +} + +func TestDefaultSender(t *testing.T) { + pay := ` + tx_begin + tx_field Amount + tx_field Receiver + int pay + tx_field TypeEnum + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ep.Txn.Txn.Accounts = append(ep.Txn.Txn.Accounts, ledger.ApplicationID().Address()) + testApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") + ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) + testApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep) + testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) +} + +func TestAppAxfer(t *testing.T) { + axfer := ` + tx_begin + int 77 + tx_field XferAsset + tx_field AssetAmount + tx_field AssetReceiver + tx_field Sender + int axfer + tx_field TypeEnum + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAsset(ep.Txn.Txn.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample + ledger.NewAsset(ep.Txn.Txn.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample + testApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "invalid Asset reference") // 777 not in foreign-assets + testApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "assert failed") // because Sender not opted-in + testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + "assert failed") // app account not opted in + + ledger.NewAccount(ledger.ApplicationID().Address(), 10000) // plenty for fees + ledger.NewHolding(ledger.ApplicationID().Address(), 77, 3000, false) + testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) + + testApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") + testApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, + fmt.Sprintf("Receiver (%s) not opted in", ep.Txn.Txn.Sender)) // txn.Sender (receiver of the axfer) isn't opted in + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + "insufficient balance") + + // Temporarily remove from ForeignAssets to ensure App Account + // doesn't get some sort of free pass to send arbitrary assets. + save := ep.Txn.Txn.ForeignAssets + ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{6, 10} + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + "invalid Asset reference 77") + ep.Txn.Txn.ForeignAssets = save + + noid := ` + tx_begin + tx_field AssetAmount + tx_field AssetReceiver + tx_field Sender + int axfer + tx_field TypeEnum + tx_submit +` + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, + fmt.Sprintf("Sender (%s) not opted in to 0", ledger.ApplicationID().Address())) + + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) + + // 100 of 3000 spent + testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) + testApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) +} + +func TestExtraFields(t *testing.T) { + pay := ` + tx_begin + int 7; tx_field AssetAmount; + tx_field Amount + tx_field Receiver + tx_field Sender + int pay + tx_field TypeEnum + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + testApp(t, "txn Sender; balance; int 0; ==;", ep) + testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + "non-zero fields for type axfer") +} + +func TestNumInner(t *testing.T) { + pay := ` + tx_begin + int 1 + tx_field Amount + txn Accounts 1 + tx_field Receiver + int pay + tx_field TypeEnum + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) + testApp(t, pay+";int 1", ep) + testApp(t, pay+pay+";int 1", ep) + testApp(t, pay+pay+pay+";int 1", ep) + testApp(t, pay+pay+pay+pay+";int 1", ep) + // In the sample proto, MaxInnerTransactions = 4 + testApp(t, pay+pay+pay+pay+pay+";int 1", ep, "tx_submit with MaxInnerTransactions") +} diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index f782fcce9a..2ffa41fff2 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -19,50 +19,18 @@ package logic import ( "encoding/hex" "fmt" - "math/rand" "strings" "testing" "github.com/stretchr/testify/require" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logictest" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) -type balanceRecord struct { - addr basics.Address - balance uint64 - locals map[basics.AppIndex]basics.TealKeyValue - holdings map[basics.AssetIndex]basics.AssetHolding - mods map[basics.AppIndex]map[string]basics.ValueDelta -} - -// In our test ledger, we don't store the creatables with their -// creators, so we need to carry the creator around with them. -type appParams struct { - basics.AppParams - Creator basics.Address -} - -type asaParams struct { - basics.AssetParams - Creator basics.Address -} - -type testLedger struct { - balances map[basics.Address]balanceRecord - applications map[basics.AppIndex]appParams - assets map[basics.AssetIndex]asaParams - trackedCreatables map[int]basics.CreatableIndex - appID basics.AppIndex - creatorAddr basics.Address - mods map[basics.AppIndex]map[string]basics.ValueDelta - logs []basics.LogItem -} - func makeApp(li uint64, lb uint64, gi uint64, gb uint64) basics.AppParams { return basics.AppParams{ ApprovalProgram: []byte{}, @@ -76,412 +44,24 @@ func makeApp(li uint64, lb uint64, gi uint64, gb uint64) basics.AppParams { } } -func makeBalanceRecord(addr basics.Address, balance uint64) balanceRecord { - br := balanceRecord{ - addr: addr, - balance: balance, - locals: make(map[basics.AppIndex]basics.TealKeyValue), - holdings: make(map[basics.AssetIndex]basics.AssetHolding), - mods: make(map[basics.AppIndex]map[string]basics.ValueDelta), - } - return br +func makeSampleEnv() (EvalParams, *logictest.Ledger) { + return makeSampleEnvWithVersion(LogicVersion) } -func makeSampleEnv() (EvalParams, *testLedger) { +func makeSampleEnvWithVersion(version uint64) (EvalParams, *logictest.Ledger) { txn := makeSampleTxn() - ep := defaultEvalParams(nil, &txn) + ep := defaultEvalParamsWithVersion(nil, &txn, version) ep.TxnGroup = makeSampleTxnGroup(txn) - ledger := makeTestLedger(map[basics.Address]uint64{}) + ledger := logictest.MakeLedger(map[basics.Address]uint64{}) ep.Ledger = ledger return ep, ledger } -func makeTestLedger(balances map[basics.Address]uint64) *testLedger { - l := new(testLedger) - l.balances = make(map[basics.Address]balanceRecord) - for addr, balance := range balances { - l.newAccount(addr, balance) - } - l.applications = make(map[basics.AppIndex]appParams) - l.assets = make(map[basics.AssetIndex]asaParams) - l.trackedCreatables = make(map[int]basics.CreatableIndex) - l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) - return l -} - -func (l *testLedger) reset() { - l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) - for addr, br := range l.balances { - br.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) - l.balances[addr] = br - } -} - -func (l *testLedger) newAccount(addr basics.Address, balance uint64) { - l.balances[addr] = makeBalanceRecord(addr, balance) -} - -func (l *testLedger) newApp(creator basics.Address, appID basics.AppIndex, params basics.AppParams) { - l.appID = appID - params = params.Clone() - if params.GlobalState == nil { - params.GlobalState = make(basics.TealKeyValue) - } - l.applications[appID] = appParams{ - Creator: creator, - AppParams: params.Clone(), - } - br, ok := l.balances[creator] - if !ok { - br = makeBalanceRecord(creator, 0) - } - br.locals[appID] = make(map[string]basics.TealValue) - l.balances[creator] = br -} - -func (l *testLedger) newAsset(creator basics.Address, assetID basics.AssetIndex, params basics.AssetParams) { - l.assets[assetID] = asaParams{ - Creator: creator, - AssetParams: params, - } - br, ok := l.balances[creator] - if !ok { - br = makeBalanceRecord(creator, 0) - } - br.holdings[assetID] = basics.AssetHolding{Amount: params.Total, Frozen: params.DefaultFrozen} - l.balances[creator] = br -} - -func (l *testLedger) setHolding(addr basics.Address, assetID uint64, amount uint64, frozen bool) { - br, ok := l.balances[addr] - if !ok { - br = makeBalanceRecord(addr, 0) - } - br.holdings[basics.AssetIndex(assetID)] = basics.AssetHolding{Amount: amount, Frozen: frozen} - l.balances[addr] = br -} - -func (l *testLedger) Round() basics.Round { - return basics.Round(rand.Uint32() + 1) -} - -func (l *testLedger) LatestTimestamp() int64 { - return int64(rand.Uint32() + 1) -} - -func (l *testLedger) Balance(addr basics.Address) (amount basics.MicroAlgos, err error) { - if l.balances == nil { - err = fmt.Errorf("empty ledger") - return - } - br, ok := l.balances[addr] - if !ok { - err = fmt.Errorf("no such address") - return - } - return basics.MicroAlgos{Raw: br.balance}, nil -} - -func (l *testLedger) MinBalance(addr basics.Address, proto *config.ConsensusParams) (amount basics.MicroAlgos, err error) { - if l.balances == nil { - err = fmt.Errorf("empty ledger") - return - } - br, ok := l.balances[addr] - if !ok { - err = fmt.Errorf("no such address") - return - } - - var min uint64 - - // First, base MinBalance - min = proto.MinBalance - - // MinBalance for each Asset - assetCost := basics.MulSaturate(proto.MinBalance, uint64(len(br.holdings))) - min = basics.AddSaturate(min, assetCost) - - // Base MinBalance + GlobalStateSchema.MinBalance + ExtraProgramPages MinBalance for each created application - for _, params := range l.applications { - if params.Creator == addr { - min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) - min = basics.AddSaturate(min, params.GlobalStateSchema.MinBalance(proto).Raw) - min = basics.AddSaturate(min, basics.MulSaturate(proto.AppFlatParamsMinBalance, uint64(params.ExtraProgramPages))) - } - } - - // Base MinBalance + LocalStateSchema.MinBalance for each opted in application - for idx := range br.locals { - min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) - min = basics.AddSaturate(min, l.applications[idx].LocalStateSchema.MinBalance(proto).Raw) - } - - return basics.MicroAlgos{Raw: min}, nil -} - -func (l *testLedger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) { - if appIdx == basics.AppIndex(0) { - appIdx = l.appID - } - params, ok := l.applications[appIdx] - if !ok { - return basics.TealValue{}, false, fmt.Errorf("no such app") - } - - // return most recent value if available - tkvm, ok := l.mods[appIdx] - if ok { - val, ok := tkvm[key] - if ok { - tv, ok := val.ToTealValue() - return tv, ok, nil - } - } - - // otherwise return original one - val, ok := params.GlobalState[key] - return val, ok, nil -} - -func (l *testLedger) SetGlobal(key string, value basics.TealValue) error { - appIdx := l.appID - params, ok := l.applications[appIdx] - if !ok { - return fmt.Errorf("no such app") - } - - // if writing the same value, return - // this simulates real ledger behavior for tests - val, ok := params.GlobalState[key] - if ok && val == value { - return nil - } - - // write to deltas - _, ok = l.mods[appIdx] - if !ok { - l.mods[appIdx] = make(map[string]basics.ValueDelta) - } - l.mods[appIdx][key] = value.ToValueDelta() - return nil -} - -func (l *testLedger) DelGlobal(key string) error { - appIdx := l.appID - params, ok := l.applications[appIdx] - if !ok { - return fmt.Errorf("no such app") - } - - exist := false - if _, ok := params.GlobalState[key]; ok { - exist = true - } - - _, ok = l.mods[appIdx] - if !ok && !exist { - // nothing to delete - return nil - } - if !ok { - l.mods[appIdx] = make(map[string]basics.ValueDelta) - } - _, ok = l.mods[appIdx][key] - if ok || exist { - l.mods[appIdx][key] = basics.ValueDelta{Action: basics.DeleteAction} - } - return nil -} - -func (l *testLedger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { - if appIdx == 0 { - appIdx = l.appID - } - br, ok := l.balances[addr] - if !ok { - return basics.TealValue{}, false, fmt.Errorf("no such address") - } - tkvd, ok := br.locals[appIdx] - if !ok { - return basics.TealValue{}, false, fmt.Errorf("no app for account") - } - - // check deltas first - tkvm, ok := br.mods[appIdx] - if ok { - val, ok := tkvm[key] - if ok { - tv, ok := val.ToTealValue() - return tv, ok, nil - } - } - - val, ok := tkvd[key] - return val, ok, nil -} - -func (l *testLedger) SetLocal(addr basics.Address, key string, value basics.TealValue, accountIdx uint64) error { - appIdx := l.appID - - br, ok := l.balances[addr] - if !ok { - return fmt.Errorf("no such address") - } - tkv, ok := br.locals[appIdx] - if !ok { - return fmt.Errorf("no app for account") - } - - // if writing the same value, return - // this simulates real ledger behavior for tests - val, ok := tkv[key] - if ok && val == value { - return nil - } - - // write to deltas - _, ok = br.mods[appIdx] - if !ok { - br.mods[appIdx] = make(map[string]basics.ValueDelta) - } - br.mods[appIdx][key] = value.ToValueDelta() - return nil -} - -func (l *testLedger) DelLocal(addr basics.Address, key string, accountIdx uint64) error { - appIdx := l.appID - - br, ok := l.balances[addr] - if !ok { - return fmt.Errorf("no such address") - } - tkv, ok := br.locals[appIdx] - if !ok { - return fmt.Errorf("no app for account") - } - exist := false - if _, ok := tkv[key]; ok { - exist = true - } - - _, ok = br.mods[appIdx] - if !ok && !exist { - // nothing to delete - return nil - } - if !ok { - br.mods[appIdx] = make(map[string]basics.ValueDelta) - } - _, ok = br.mods[appIdx][key] - if ok || exist { - br.mods[appIdx][key] = basics.ValueDelta{Action: basics.DeleteAction} - } - return nil -} - -func (l *testLedger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) { - if appIdx == 0 { - appIdx = l.appID - } - br, ok := l.balances[addr] - if !ok { - return false, fmt.Errorf("no such address") - } - _, ok = br.locals[appIdx] - return ok, nil -} - -func (l *testLedger) setTrackedCreatable(groupIdx int, cl basics.CreatableLocator) { - l.trackedCreatables[groupIdx] = cl.Index -} - -func (l *testLedger) GetCreatableID(groupIdx int) basics.CreatableIndex { - return l.trackedCreatables[groupIdx] -} - -func (l *testLedger) AssetHolding(addr basics.Address, assetID basics.AssetIndex) (basics.AssetHolding, error) { - if br, ok := l.balances[addr]; ok { - if asset, ok := br.holdings[assetID]; ok { - return asset, nil - } - return basics.AssetHolding{}, fmt.Errorf("No asset for account") - } - return basics.AssetHolding{}, fmt.Errorf("no such address") -} - -func (l *testLedger) AssetParams(assetID basics.AssetIndex) (basics.AssetParams, basics.Address, error) { - if asset, ok := l.assets[assetID]; ok { - return asset.AssetParams, asset.Creator, nil - } - return basics.AssetParams{}, basics.Address{}, fmt.Errorf("no such asset") -} - -func (l *testLedger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Address, error) { - if app, ok := l.applications[appID]; ok { - return app.AppParams, app.Creator, nil - } - return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app") -} - -func (l *testLedger) ApplicationID() basics.AppIndex { - return l.appID -} - -func (l *testLedger) CreatorAddress() basics.Address { - return l.creatorAddr -} - -func (l *testLedger) LocalSchema() basics.StateSchema { - return basics.StateSchema{ - NumUint: 100, - NumByteSlice: 100, - } -} - -func (l *testLedger) GlobalSchema() basics.StateSchema { - return basics.StateSchema{ - NumUint: 100, - NumByteSlice: 100, - } -} - -func (l *testLedger) GetDelta(txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) { - if tkv, ok := l.mods[l.appID]; ok { - evalDelta.GlobalDelta = tkv - } - if len(txn.Accounts) > 0 { - accounts := make(map[basics.Address]int) - accounts[txn.Sender] = 0 - for idx, addr := range txn.Accounts { - accounts[addr] = idx + 1 - } - evalDelta.LocalDeltas = make(map[uint64]basics.StateDelta) - for addr, br := range l.balances { - if idx, ok := accounts[addr]; ok { - if delta, ok := br.mods[l.appID]; ok { - evalDelta.LocalDeltas[uint64(idx)] = delta - } - } - } - } - evalDelta.Logs = l.logs - return -} - -func (l *testLedger) AppendLog(txn *transactions.Transaction, value string) error { - - appIdx, err := txn.IndexByAppID(l.appID) - if err != nil { - return err - } - _, ok := l.applications[l.appID] - if !ok { - return fmt.Errorf("no such app") - } - - l.logs = append(l.logs, basics.LogItem{ID: appIdx, Message: value}) - return nil +func makeOldAndNewEnv(version uint64) (EvalParams, EvalParams, *logictest.Ledger) { + new, sharedLedger := makeSampleEnv() + old, _ := makeSampleEnvWithVersion(version) + old.Ledger = sharedLedger + return old, new, sharedLedger } func TestEvalModes(t *testing.T) { @@ -651,14 +231,14 @@ log Clawback: txn.Txn.Receiver, } algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - ledger.newAsset(txn.Txn.Sender, 5, params) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NewAsset(txn.Txn.Sender, 5, params) for mode, test := range tests { t.Run(fmt.Sprintf("opcodes_mode=%d", mode), func(t *testing.T) { @@ -775,7 +355,7 @@ func TestBalance(t *testing.T) { ep, ledger := makeSampleEnv() text := "int 2; balance; int 177; ==" - ledger.newAccount(ep.Txn.Txn.Receiver, 177) + ledger.NewAccount(ep.Txn.Txn.Receiver, 177) testApp(t, text, ep, "invalid Account reference") text = `int 1; balance; int 177; ==` @@ -787,17 +367,18 @@ func TestBalance(t *testing.T) { // but legal after that testApp(t, text, ep) - text = "int 0; balance; int 13; ==" + text = "int 0; balance; int 13; ==; assert; int 1" var addr basics.Address copy(addr[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02")) - ledger.newAccount(addr, 13) - testApp(t, text, ep, "failed to fetch balance") + ledger.NewAccount(addr, 13) + testApp(t, text, ep, "assert failed") - ledger.newAccount(ep.Txn.Txn.Sender, 13) + ledger.NewAccount(ep.Txn.Txn.Sender, 13) testApp(t, text, ep) } -func testApp(t *testing.T, program string, ep EvalParams, problems ...string) basics.EvalDelta { +func testApp(t *testing.T, program string, ep EvalParams, problems ...string) transactions.EvalDelta { + t.Helper() ops := testProg(t, program, ep.Proto.LogicSigVersion) err := CheckStateful(ops.Program, ep) require.NoError(t, err) @@ -833,7 +414,7 @@ func testApp(t *testing.T, program string, ep EvalParams, problems ...string) ba require.Empty(t, delta.Logs) return delta } - return basics.EvalDelta{} + return transactions.EvalDelta{} } func TestMinBalance(t *testing.T) { @@ -843,23 +424,23 @@ func TestMinBalance(t *testing.T) { ep, ledger := makeSampleEnv() - ledger.newAccount(ep.Txn.Txn.Sender, 234) - ledger.newAccount(ep.Txn.Txn.Receiver, 123) + ledger.NewAccount(ep.Txn.Txn.Sender, 234) + ledger.NewAccount(ep.Txn.Txn.Receiver, 123) testApp(t, "int 0; min_balance; int 1001; ==", ep) // Sender makes an asset, min balance goes up - ledger.newAsset(ep.Txn.Txn.Sender, 7, basics.AssetParams{Total: 1000}) + ledger.NewAsset(ep.Txn.Txn.Sender, 7, basics.AssetParams{Total: 1000}) testApp(t, "int 0; min_balance; int 2002; ==", ep) schemas := makeApp(1, 2, 3, 4) - ledger.newApp(ep.Txn.Txn.Sender, 77, schemas) + ledger.NewApp(ep.Txn.Txn.Sender, 77, schemas) // create + optin + 10 schema base + 4 ints + 6 bytes (local - // and global count b/c newApp opts the creator in) + // and global count b/c NewApp opts the creator in) minb := 2*1002 + 10*1003 + 4*1004 + 6*1005 testApp(t, fmt.Sprintf("int 0; min_balance; int %d; ==", 2002+minb), ep) // request extra program pages, min balance increase - app := ledger.applications[77] - app.ExtraProgramPages = 2 - ledger.applications[77] = app + withepp := makeApp(1, 2, 3, 4) + withepp.ExtraProgramPages = 2 + ledger.NewApp(ep.Txn.Txn.Sender, 77, withepp) minb += 2 * 1002 testApp(t, fmt.Sprintf("int 0; min_balance; int %d; ==", 2002+minb), ep) @@ -869,7 +450,7 @@ func TestMinBalance(t *testing.T) { testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion) testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] // Receiver opts in - ledger.setHolding(ep.Txn.Txn.Receiver, 7, 1, true) + ledger.NewHolding(ep.Txn.Txn.Receiver, 7, 1, true) testApp(t, "int 1; min_balance; int 2002; ==", ep) // 1 == Accounts[0] testApp(t, "int 2; min_balance; int 1001; ==", ep, "invalid Account reference 2") @@ -891,7 +472,7 @@ func TestAppCheckOptedIn(t *testing.T) { pre.TxnGroup = txgroup testApp(t, "int 2; int 100; app_opted_in; int 1; ==", now, "ledger not available") - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Receiver: 1, txn.Txn.Sender: 1, @@ -910,7 +491,7 @@ func TestAppCheckOptedIn(t *testing.T) { testApp(t, "int 0; int 100; app_opted_in; int 0; ==", now) // Receiver opted in - ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Receiver, 100, basics.AppParams{}) testApp(t, "int 1; int 100; app_opted_in; int 1; ==", now) testApp(t, "int 1; int 2; app_opted_in; int 1; ==", now) testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id @@ -919,7 +500,7 @@ func TestAppCheckOptedIn(t *testing.T) { expect{3, "app_opted_in arg 0 wanted type uint64..."}) // Sender opted in - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) testApp(t, "int 0; int 100; app_opted_in; int 1; ==", now) } @@ -942,24 +523,8 @@ exit: int 1 ==` - txn := makeSampleTxn() - txgroup := makeSampleTxnGroup(txn) - now := defaultEvalParams(nil, nil) - now.Txn = &txn - now.TxnGroup = txgroup - pre := defaultEvalParamsWithVersion(nil, nil, directRefEnabledVersion-1) - pre.Txn = &txn - pre.TxnGroup = txgroup - - testApp(t, text, now, "ledger not available") - - ledger := makeTestLedger( - map[basics.Address]uint64{ - txn.Txn.Receiver: 1, - }, - ) - now.Ledger = ledger - pre.Ledger = ledger + pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion - 1) + ledger.NewAccount(now.Txn.Txn.Receiver, 1) testApp(t, text, now, "invalid Account reference") text = `int 1 // account idx @@ -978,11 +543,11 @@ int 1` testApp(t, text, now, "no app for account") // Make a different app (not 100) - ledger.newApp(txn.Txn.Receiver, 9999, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Receiver, 9999, basics.AppParams{}) testApp(t, text, now, "no app for account") // create the app and check the value from ApplicationArgs[0] (protocol.PaymentTx) does not exist - ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Receiver, 100, basics.AppParams{}) testApp(t, text, now) text = `int 1 // account idx @@ -994,7 +559,7 @@ err exist: byte 0x414c474f ==` - ledger.balances[txn.Txn.Receiver].locals[100][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Receiver, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) testApp(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now) @@ -1004,7 +569,7 @@ byte 0x414c474f // Next we're testing if the use of the current app's id works // as a direct reference. The error is because the sender // account is not opted into 123. - ledger.appID = basics.AppIndex(123) + ledger.NewApp(now.Txn.Txn.RekeyTo, 123, basics.AppParams{}) testApp(t, strings.Replace(text, "int 100 // app id", "int 123", -1), now, "no app for account") testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), pre, "no app for account") testApp(t, strings.Replace(text, "int 100 // app id", "int 9", -1), now, "invalid App reference 9") @@ -1012,7 +577,7 @@ byte 0x414c474f "no such address") // check special case account idx == 0 => sender - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Sender, 100, basics.AppParams{}) text = `int 0 // account idx int 100 // app id txn ApplicationArgs 0 @@ -1023,15 +588,15 @@ exist: byte 0x414c474f ==` - ledger.balances[txn.Txn.Sender].locals[100][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02\"", -1), now, "invalid Account reference") // check reading state of other app - ledger.newApp(txn.Txn.Sender, 56, basics.AppParams{}) - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Sender, 56, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Sender, 100, basics.AppParams{}) text = `int 0 // account idx int 56 // app id txn ApplicationArgs 0 @@ -1042,7 +607,7 @@ exist: byte 0x414c474f ==` - ledger.balances[txn.Txn.Sender].locals[56][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Sender, 56, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) // check app_local_get @@ -1052,7 +617,7 @@ app_local_get byte 0x414c474f ==` - ledger.balances[txn.Txn.Sender].locals[100][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now) testProg(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, @@ -1068,7 +633,7 @@ app_local_get int 0 ==` - ledger.balances[txn.Txn.Sender].locals[100][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) } @@ -1100,35 +665,19 @@ byte 0x414c474f == && ` - txn := makeSampleTxn() - txgroup := makeSampleTxnGroup(txn) - now := defaultEvalParams(nil, nil) - now.Txn = &txn - now.TxnGroup = txgroup - pre := defaultEvalParamsWithVersion(nil, nil, directRefEnabledVersion-1) - pre.Txn = &txn - pre.TxnGroup = txgroup - - testApp(t, text, now, "ledger not available") - - ledger := makeTestLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - now.Ledger = ledger - pre.Ledger = ledger + pre, now, ledger := makeOldAndNewEnv(directRefEnabledVersion - 1) + ledger.NewAccount(now.Txn.Txn.Sender, 1) now.Txn.Txn.ApplicationID = 100 now.Txn.Txn.ForeignApps = []basics.AppIndex{now.Txn.Txn.ApplicationID} testApp(t, text, now, "no such app") // create the app and check the value from ApplicationArgs[0] (protocol.PaymentTx) does not exist - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(now.Txn.Txn.Sender, 100, basics.AppParams{}) testApp(t, text, now, "err opcode") - ledger.applications[100].GlobalState[string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewGlobal(100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) @@ -1143,7 +692,7 @@ byte 0x414c474f // check app_global_get default value text = "byte 0x414c474f55; app_global_get; int 0; ==" - ledger.balances[txn.Txn.Sender].locals[100][string(protocol.PaymentTx)] = basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"} + ledger.NewLocal(now.Txn.Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"}) testApp(t, text, now) text = ` @@ -1167,7 +716,7 @@ int 4141 testApp(t, text, now) // Direct reference to the current app also works - ledger.appID = basics.AppIndex(100) + ledger.NewApp(now.Txn.Txn.Receiver, 100, basics.AppParams{}) now.Txn.Txn.ForeignApps = []basics.AppIndex{} testApp(t, strings.Replace(text, "int 1 // ForeignApps index", "int 100", -1), now) testApp(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), now) @@ -1315,7 +864,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) pre := defaultEvalParamsWithVersion(nil, &txn, directRefEnabledVersion-1) require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) now := defaultEvalParamsWithVersion(nil, &txn, version) - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1357,8 +906,8 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) Clawback: txn.Txn.Receiver, } - ledger.newAsset(txn.Txn.Sender, 55, params) - ledger.setHolding(txn.Txn.Sender, 55, 123, true) + ledger.NewAsset(txn.Txn.Sender, 55, params) + ledger.NewHolding(txn.Txn.Sender, 55, 123, true) // For consistency you can now use an indirect ref in holding_get // (recall ForeignAssets[0] = 55, which has balance 123) testApp(t, "int 0; int 0; asset_holding_get AssetBalance; int 1; ==; assert; int 123; ==", now) @@ -1398,7 +947,7 @@ err ok: intc_2 // 1 ` - ledger.setHolding(txn.Txn.Sender, 55, 123, false) + ledger.NewHolding(txn.Txn.Sender, 55, 123, false) testApp(t, source, now) // check holdings invalid offsets @@ -1424,7 +973,7 @@ ok: intc_1 ` params.DefaultFrozen = true - ledger.newAsset(txn.Txn.Sender, 55, params) + ledger.NewAsset(txn.Txn.Sender, 55, params) testApp(t, source, now) // check holdings invalid offsets ops = testProg(t, source, version) @@ -1450,7 +999,7 @@ ok: intc_1 ` params.URL = "" - ledger.newAsset(txn.Txn.Sender, 55, params) + ledger.NewAsset(txn.Txn.Sender, 55, params) testApp(t, source, now) source = `intcblock 1 9 @@ -1468,7 +1017,7 @@ ok: intc_0 ` params.URL = "foobarbaz" - ledger.newAsset(txn.Txn.Sender, 77, params) + ledger.NewAsset(txn.Txn.Sender, 77, params) testApp(t, source, now) source = `intcblock 0 1 @@ -1485,15 +1034,15 @@ ok: intc_1 ` params.URL = "" - ledger.newAsset(txn.Txn.Sender, 55, params) + ledger.NewAsset(txn.Txn.Sender, 55, params) testApp(t, source, now, "cannot compare ([]byte to uint64)") } func TestAppParams(t *testing.T) { t.Parallel() ep, ledger := makeSampleEnv() - ledger.newAccount(ep.Txn.Txn.Sender, 1) - ledger.newApp(ep.Txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewAccount(ep.Txn.Txn.Sender, 1) + ledger.NewApp(ep.Txn.Txn.Sender, 100, basics.AppParams{}) /* app id is in ForeignApps, but does not exist */ source := "int 56; app_params_get AppExtraProgramPages; int 0; ==; assert; int 0; ==" @@ -1548,35 +1097,34 @@ bytec_0 // key "ALGO" app_local_del intc_1 ` - type test struct { + type cmdtest struct { source string accNumOffset int } - tests := map[string]test{ + tests := map[string]cmdtest{ "read": {sourceRead, 20}, "write": {sourceWrite, 13}, "delete": {sourceDelete, 12}, } - for name, test := range tests { + for name, cmdtest := range tests { t.Run(fmt.Sprintf("test=%s", name), func(t *testing.T) { - source := test.source - firstCmdOffset := test.accNumOffset + source := cmdtest.source + firstCmdOffset := cmdtest.accNumOffset - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops := testProg(t, source, AssemblerMaxVersion) txn := makeSampleTxn() ep := defaultEvalParams(nil, nil) ep.Txn = &txn ep.Txn.Txn.ApplicationID = 100 - err = CheckStateful(ops.Program, ep) + err := CheckStateful(ops.Program, ep) require.NoError(t, err) _, err = EvalStateful(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "ledger not available") - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1595,7 +1143,7 @@ intc_1 require.Error(t, err) require.Contains(t, err.Error(), "no app for account") - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) if name == "read" { _, err = EvalStateful(ops.Program, ep) @@ -1603,10 +1151,10 @@ intc_1 require.Contains(t, err.Error(), "err opcode") // no such key } - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.balances[txn.Txn.Sender].locals[100]["ALGOA"] = basics.TealValue{Type: basics.TealUintType, Uint: 1} + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", basics.TealValue{Type: basics.TealUintType, Uint: 0x77}) + ledger.NewLocal(txn.Txn.Sender, 100, "ALGOA", basics.TealValue{Type: basics.TealUintType, Uint: 1}) - ledger.reset() + ledger.Reset() pass, err := EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -1631,13 +1179,13 @@ func TestAppLocalStateReadWrite(t *testing.T) { txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) // write int and bytes values source := `int 0 // account @@ -1704,15 +1252,14 @@ exist: int 0x77 == ` - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) err = CheckStateful(ops.Program, ep) require.NoError(t, err) pass, err = EvalStateful(ops.Program, ep) @@ -1744,12 +1291,11 @@ err exist2: == ` - ledger.reset() - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -1765,12 +1311,11 @@ int 0x78 // value app_local_put int 1 ` - ledger.reset() - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -1798,12 +1343,11 @@ exist: int 0x78 == ` - ledger.reset() - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -1829,12 +1373,11 @@ byte 0x414c474f // key "ALGO" int 0x78 // value app_local_put ` - ledger.reset() - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -1866,15 +1409,14 @@ int 0x79 // value app_local_put int 1 ` - ledger.reset() - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") + ledger.Reset() + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") - ledger.balances[txn.Txn.Receiver] = makeBalanceRecord(txn.Txn.Receiver, 500) - ledger.balances[txn.Txn.Receiver].locals[100] = make(basics.TealKeyValue) + ledger.NewAccount(ep.Txn.Txn.Receiver, 500) + ledger.NewLocals(txn.Txn.Receiver, 100) - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) err = CheckStateful(ops.Program, ep) require.NoError(t, err) pass, err = EvalStateful(ops.Program, ep) @@ -1946,7 +1488,7 @@ int 1 require.Error(t, err) require.Contains(t, err.Error(), "ledger not available") - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -1958,7 +1500,7 @@ int 1 require.Error(t, err) require.Contains(t, err.Error(), "no such app") - ledger.newApp(txn.Txn.Sender, 100, makeApp(0, 0, 1, 0)) + ledger.NewApp(txn.Txn.Sender, 100, makeApp(0, 0, 1, 0)) // a special test for read if name == "read" { @@ -1966,9 +1508,9 @@ int 1 require.Error(t, err) require.Contains(t, err.Error(), "err opcode") // no such key } - ledger.applications[100].GlobalState["ALGO"] = basics.TealValue{Type: basics.TealUintType, Uint: 0x77} + ledger.NewGlobal(100, "ALGO", basics.TealValue{Type: basics.TealUintType, Uint: 0x77}) - ledger.reset() + ledger.Reset() pass, err := EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -2049,13 +1591,13 @@ int 0x77 txn.Txn.ApplicationID = 100 txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) @@ -2087,15 +1629,14 @@ app_global_get int 0x77 == ` - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -2121,12 +1662,11 @@ app_global_get int 0x77 == ` - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) pass, err = EvalStateful(ops.Program, ep) require.NoError(t, err) require.True(t, pass) @@ -2167,12 +1707,11 @@ byte 0x414c474f == && ` - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NewGlobal(100, "ALGO", algoValue) - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) sb := strings.Builder{} ep.Trace = &sb err = CheckStateful(ops.Program, ep) @@ -2224,22 +1763,22 @@ byte "myval" txn.Txn.ApplicationID = 100 txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID, 101} ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep, "no such app") require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) - ledger.newApp(txn.Txn.Receiver, 101, basics.AppParams{}) - ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 + ledger.NewApp(txn.Txn.Receiver, 101, basics.AppParams{}) + ledger.NewApp(txn.Txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} - ledger.applications[101].GlobalState["mykey"] = algoValue + ledger.NewGlobal(101, "mykey", algoValue) delta = testApp(t, source, ep) require.Empty(t, delta.GlobalDelta) @@ -2270,13 +1809,13 @@ int 7 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep) require.Empty(t, delta.LocalDeltas) @@ -2317,24 +1856,24 @@ int 1 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) require.Empty(t, delta.LocalDeltas) - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) // check delete existing source = `byte 0x414c474f // key "ALGO" @@ -2353,11 +1892,11 @@ app_global_get_ex require.Equal(t, "", vd.Bytes) require.Equal(t, 0, len(delta.LocalDeltas)) - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) // check delete and write non-existing source = `byte 0x414c474f41 // key "ALGOA" @@ -2378,11 +1917,11 @@ app_global_put require.Equal(t, "", vd.Bytes) require.Empty(t, delta.LocalDeltas) - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) // check delete and write existing source = `byte 0x414c474f // key "ALGO" @@ -2398,11 +1937,12 @@ int 1 require.Equal(t, basics.SetUintAction, vd.Action) require.Empty(t, delta.LocalDeltas) - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) // check delete,write,delete existing source = `byte 0x414c474f // key "ALGO" @@ -2420,11 +1960,12 @@ int 1 require.Equal(t, basics.DeleteAction, vd.Action) require.Empty(t, delta.LocalDeltas) - ledger.reset() - delete(ledger.applications[100].GlobalState, "ALGOA") - delete(ledger.applications[100].GlobalState, "ALGO") + ledger.Reset() + ledger.Reset() + ledger.NoGlobal(100, "ALGOA") + ledger.NoGlobal(100, "ALGO") - ledger.applications[100].GlobalState["ALGO"] = algoValue + ledger.NewGlobal(100, "ALGO", algoValue) // check delete, write, delete non-existing source = `byte 0x414c474f41 // key "ALGOA" @@ -2482,15 +2023,15 @@ int 1 txn := makeSampleTxn() txn.Txn.ApplicationID = 100 ep.Txn = &txn - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) - ledger.balances[txn.Txn.Receiver] = makeBalanceRecord(txn.Txn.Receiver, 1) - ledger.balances[txn.Txn.Receiver].locals[100] = make(basics.TealKeyValue) + ledger.NewApp(txn.Txn.Sender, 100, basics.AppParams{}) + ledger.NewAccount(txn.Txn.Receiver, 1) + ledger.NewLocals(txn.Txn.Receiver, 100) sb := strings.Builder{} ep.Trace = &sb @@ -2501,7 +2042,7 @@ int 1 require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 2, len(delta.LocalDeltas)) - ledger.reset() + ledger.Reset() // test that app_local_put and _app_local_del can use byte addresses testApp(t, strings.Replace(source, "int 0 // sender", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), ep) // But won't compile in old teal @@ -2513,14 +2054,14 @@ int 1 require.Equal(t, 0, len(delta.GlobalDelta)) require.Equal(t, 2, len(delta.LocalDeltas)) - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") - delete(ledger.balances[txn.Txn.Receiver].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Receiver].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") + ledger.NoLocal(txn.Txn.Receiver, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Receiver, 100, "ALGO") algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) // check delete existing source = `int 0 // account @@ -2543,11 +2084,11 @@ app_local_get_ex require.Equal(t, uint64(0), vd.Uint) require.Equal(t, "", vd.Bytes) - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) // check delete and write non-existing source = `int 0 // account @@ -2573,11 +2114,11 @@ app_local_put require.Equal(t, uint64(0x78), vd.Uint) require.Equal(t, "", vd.Bytes) - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) // check delete and write existing source = `int 0 // account @@ -2599,11 +2140,11 @@ int 1 require.Equal(t, uint64(0x78), vd.Uint) require.Equal(t, "", vd.Bytes) - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) // check delete,write,delete existing source = `int 0 // account @@ -2628,11 +2169,11 @@ int 1 require.Equal(t, uint64(0), vd.Uint) require.Equal(t, "", vd.Bytes) - ledger.reset() - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGOA") - delete(ledger.balances[txn.Txn.Sender].locals[100], "ALGO") + ledger.Reset() + ledger.NoLocal(txn.Txn.Sender, 100, "ALGOA") + ledger.NoLocal(txn.Txn.Sender, 100, "ALGO") - ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue + ledger.NewLocal(txn.Txn.Sender, 100, "ALGO", algoValue) // check delete, write, delete non-existing source = `int 0 // account @@ -2694,7 +2235,7 @@ func TestEnumFieldErrors(t *testing.T) { require.Contains(t, err.Error(), "MinTxnFee expected field type is []byte but got uint64") txn := makeSampleTxn() - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -2711,7 +2252,7 @@ func TestEnumFieldErrors(t *testing.T) { Freeze: txn.Txn.Receiver, Clawback: txn.Txn.Receiver, } - ledger.newAsset(txn.Txn.Sender, 55, params) + ledger.NewAsset(txn.Txn.Sender, 55, params) ep.Txn = &txn ep.Ledger = ledger @@ -2779,7 +2320,7 @@ func TestReturnTypes(t *testing.T) { []byte("aoeu2"), []byte("aoeu3"), } - ledger := makeTestLedger( + ledger := logictest.MakeLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, }, @@ -2796,15 +2337,15 @@ func TestReturnTypes(t *testing.T) { Freeze: txn.Txn.Receiver, Clawback: txn.Txn.Receiver, } - ledger.newAsset(txn.Txn.Sender, 1, params) - ledger.newApp(txn.Txn.Sender, 1, basics.AppParams{}) - ledger.setTrackedCreatable(0, basics.CreatableLocator{Index: 1}) - ledger.balances[txn.Txn.Receiver] = makeBalanceRecord(txn.Txn.Receiver, 1) - ledger.balances[txn.Txn.Receiver].locals[1] = make(basics.TealKeyValue) + ledger.NewAsset(txn.Txn.Sender, 1, params) + ledger.NewApp(txn.Txn.Sender, 1, basics.AppParams{}) + ledger.SetTrackedCreatable(0, basics.CreatableLocator{Index: 1}) + ledger.NewAccount(txn.Txn.Receiver, 1000000) + ledger.NewLocals(txn.Txn.Receiver, 1) key, err := hex.DecodeString("33343536") require.NoError(t, err) algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} - ledger.balances[txn.Txn.Receiver].locals[1][string(key)] = algoValue + ledger.NewLocal(txn.Txn.Receiver, 1, string(key), algoValue) ep.Ledger = ledger @@ -2865,7 +2406,10 @@ func TestReturnTypes(t *testing.T) { source := sb.String() ops := testProg(t, source, AssemblerMaxVersion) - var cx evalContext + var trace strings.Builder + ep.Trace = &trace + + var cx EvalContext cx.EvalParams = ep cx.runModeFlags = m @@ -2874,7 +2418,7 @@ func TestReturnTypes(t *testing.T) { require.Equal( t, len(spec.Returns), len(cx.stack), - fmt.Sprintf("%s expected to return %d values but stack has %d", spec.Name, len(spec.Returns), len(cx.stack)), + fmt.Sprintf("\n%s%s expected to return %d values but stack has %d", trace.String(), spec.Name, len(spec.Returns), len(cx.stack)), ) for i := 0; i < len(spec.Returns); i++ { sp := len(cx.stack) - 1 - i @@ -2910,7 +2454,7 @@ func TestCurrentApplicationID(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() ep, ledger := makeSampleEnv() - ledger.appID = basics.AppIndex(42) + ledger.NewApp(ep.Txn.Txn.Receiver, 42, basics.AppParams{}) source := "global CurrentApplicationID; int 42; ==" testApp(t, source, ep) } @@ -2925,54 +2469,10 @@ func TestAppLoop(t *testing.T) { // Double until > 10. Should be 16 testApp(t, stateful+"int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==", ep) - testApp(t, stateful+"int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==", ep) - // Infinite loop because multiply by one instead of two testApp(t, stateful+"int 1; loop:; int 1; *; dup; int 10; <; bnz loop; int 16; ==", ep, "dynamic cost") } -func TestWriteLogs(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - - ep := defaultEvalParams(nil, nil) - txn := makeSampleTxn() - txn.Txn.ApplicationID = 100 - ep.Txn = &txn - ledger := makeTestLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 1, - }, - ) - ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) - - // write int and bytes values - source := `int 1 -loop: byte "a" -log -int 1 -+ -dup -int 30 -< -bnz loop -` - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - err = CheckStateful(ops.Program, ep) - require.NoError(t, err) - pass, err := EvalStateful(ops.Program, ep) - require.NoError(t, err) - require.True(t, pass) - delta, err := ledger.GetDelta(&ep.Txn.Txn) - require.NoError(t, err) - require.Empty(t, 0, delta.GlobalDelta) - require.Empty(t, delta.LocalDeltas) - require.Len(t, delta.Logs, 29) -} - func TestPooledAppCallsVerifyOp(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -2998,3 +2498,19 @@ func TestPooledAppCallsVerifyOp(t *testing.T) { *ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost * 3) testApp(t, source, ep) } + +func TestAppAddress(t *testing.T) { + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + source := fmt.Sprintf("global CurrentApplicationAddress; addr %s; ==;", basics.AppIndex(888).Address()) + testApp(t, source, ep) + + source = fmt.Sprintf("int 0; app_params_get AppAddress; assert; addr %s; ==;", basics.AppIndex(888).Address()) + testApp(t, source, ep) + + // To document easy construction: + // python -c 'import algosdk.encoding as e; print(e.encode_address(e.checksum(b"appID"+(888).to_bytes(8, "big"))))' + a := "U7C5FUHZM5PL5EIS2KHHLL456GS66DZBEEKL2UBQLMKH2X5X5I643ZIM6U" + source = fmt.Sprintf("int 0; app_params_get AppAddress; assert; addr %s; ==;", a) + testApp(t, source, ep) +} diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 98587f5ba2..f9f0bea344 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -33,6 +33,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logictest" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" @@ -44,10 +45,6 @@ func defaultEvalProto() config.ConsensusParams { return defaultEvalProtoWithVersion(LogicVersion) } -func defaultEvalProtoV1() config.ConsensusParams { - return defaultEvalProtoWithVersion(1) -} - func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams { return config.ConsensusParams{ LogicSigVersion: version, @@ -59,11 +56,20 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams { // These must be identical to keep an old backward compat test working MinTxnFee: 1001, MinBalance: 1001, + // Our sample txn is 42-1066 (and that's used as default in tx_begin) + MaxTxnLife: 1500, // Strange choices below so that we test against conflating them AppFlatParamsMinBalance: 1002, SchemaMinBalancePerEntry: 1003, SchemaUintMinBalance: 1004, SchemaBytesMinBalance: 1005, + + MaxInnerTransactions: 4, + + // With the addition of tx_perform, which relies on machinery + // outside logic package for validity checking, we need a more + // realistic set of consensus paramaters. + Asset: true, } } @@ -88,14 +94,14 @@ func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedT if txn != nil { pt = txn } else { - var at transactions.SignedTxn - pt = &at + pt = &transactions.SignedTxn{} } ep := EvalParams{} ep.Proto = &proto ep.Txn = pt ep.PastSideEffects = MakePastSideEffects(5) + ep.Specials = &transactions.SpecialAddresses{} if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error ep.Trace = sb } @@ -695,6 +701,35 @@ int 1 } } +func TestMulDiv(t *testing.T) { + // Demonstrate a "function" that expects three u64s on stack, + // and calculates B*C/A. (Following opcode documentation + // convention, C is top-of-stack, B is below it, and A is + // below B. + + muldiv := ` +muldiv: +mulw // multiply B*C. puts TWO u64s on stack +int 0 // high word of C as a double-word +dig 3 // pull C to TOS +divmodw +pop // pop unneeded remainder low word +pop // pop unneeded remainder high word +swap +int 0 +== +assert // ensure high word of quotient was 0 +swap // bring C to surface +pop // in order to get rid of it +retsub +` + testAccepts(t, "int 5; int 8; int 10; callsub muldiv; int 16; ==; return;"+muldiv, 4) + + testRejects(t, "int 5; int 8; int 10; callsub muldiv; int 15; ==; return;"+muldiv, 4) + + testAccepts(t, "int 500000000000; int 80000000000; int 100000000000; callsub muldiv; int 16000000000; ==; return;"+muldiv, 4) +} + func TestDivZero(t *testing.T) { partitiontest.PartitionTest(t) @@ -994,7 +1029,11 @@ const globalV4TestProgram = globalV3TestProgram + ` ` const globalV5TestProgram = globalV4TestProgram + ` -// No new globals in v5 +global CurrentApplicationAddress +len +int 32 +== +&& ` func TestGlobal(t *testing.T) { @@ -1023,17 +1062,17 @@ func TestGlobal(t *testing.T) { EvalStateful, CheckStateful, }, 5: { - CreatorAddress, globalV5TestProgram, + CurrentApplicationAddress, globalV5TestProgram, EvalStateful, CheckStateful, }, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) - ledger := makeTestLedger(nil) - ledger.appID = 42 + + ledger := logictest.MakeLedger(nil) addr, err := basics.UnmarshalChecksumAddress(testAddr) require.NoError(t, err) - ledger.creatorAddr = addr + ledger.NewApp(addr, basics.AppIndex(42), basics.AppParams{}) for v := uint64(1); v <= AssemblerMaxVersion; v++ { _, ok := tests[v] require.True(t, ok) @@ -1113,9 +1152,8 @@ txn TypeEnum int %s == &&`, symbol, string(tt)) - ops, err := AssembleStringWithVersion(text, v) - require.NoError(t, err) - err = Check(ops.Program, defaultEvalParams(nil, nil)) + ops := testProg(t, text, v) + err := Check(ops.Program, defaultEvalParams(nil, nil)) require.NoError(t, err) var txn transactions.SignedTxn txn.Txn.Type = tt @@ -1559,7 +1597,7 @@ func TestTxn(t *testing.T) { } sb := strings.Builder{} ep := defaultEvalParams(&sb, &txn) - ep.Ledger = makeTestLedger(nil) + ep.Ledger = logictest.MakeLedger(nil) ep.GroupIndex = 3 pass, err := Eval(ops.Program, ep) if !pass { @@ -1615,10 +1653,9 @@ fail: int 0 return ` - ops, err := AssembleStringWithVersion(cachedTxnProg, 2) - require.NoError(t, err) + ops := testProg(t, cachedTxnProg, 2) sb := strings.Builder{} - err = Check(ops.Program, defaultEvalParams(&sb, nil)) + err := Check(ops.Program, defaultEvalParams(&sb, nil)) if err != nil { t.Log(hex.EncodeToString(ops.Program)) t.Log(sb.String()) @@ -1663,10 +1700,8 @@ int 100 targetTxn.Txn.Type = protocol.AssetConfigTx txgroup[0] = targetTxn sb := strings.Builder{} - ledger := makeTestLedger(nil) - ledger.setTrackedCreatable(0, basics.CreatableLocator{ - Index: 100, - }) + ledger := logictest.MakeLedger(nil) + ledger.SetTrackedCreatable(0, basics.CreatableLocator{Index: 100}) ep := defaultEvalParams(&sb, &txn) ep.Ledger = ledger ep.TxnGroup = txgroup @@ -1707,8 +1742,7 @@ int 0 ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx // should fail when no creatable was created - var nilIndex basics.CreatableIndex - ledger.trackedCreatables[0] = nilIndex + ledger.SetTrackedCreatable(0, basics.CreatableLocator{}) _, err = EvalStateful(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "the txn did not create anything") @@ -2924,10 +2958,10 @@ func TestShortBytecblock2(t *testing.T) { const panicString = "out of memory, buffer overrun, stack overflow, divide by zero, halt and catch fire" -func opPanic(cx *evalContext) { +func opPanic(cx *EvalContext) { panic(panicString) } -func checkPanic(cx *evalContext) error { +func checkPanic(cx *EvalContext) error { panic(panicString) } @@ -3466,20 +3500,9 @@ func evalLoop(b *testing.B, runs int, program []byte) { } func benchmarkBasicProgram(b *testing.B, source string) { - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(b, err) - err = Check(ops.Program, defaultEvalParams(nil, nil)) - require.NoError(b, err) - evalLoop(b, b.N, ops.Program) -} - -func benchmarkExpensiveProgram(b *testing.B, source string) { - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(b, err) - err = Check(ops.Program, defaultEvalParams(nil, nil)) + ops := testProg(b, source, AssemblerMaxVersion) + err := Check(ops.Program, defaultEvalParams(nil, nil)) require.NoError(b, err) - _, err = Eval(ops.Program, defaultEvalParams(nil, nil)) - require.Error(b, err) // excessive cost evalLoop(b, b.N, ops.Program) } @@ -3494,9 +3517,8 @@ func benchmarkOperation(b *testing.B, prefix string, operation string, suffix st inst := strings.Count(operation, ";") + strings.Count(operation, "\n") source := prefix + ";" + strings.Repeat(operation+";", 2000) + ";" + suffix source = strings.ReplaceAll(source, ";", "\n") - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(b, err) - err = Check(ops.Program, defaultEvalParams(nil, nil)) + ops := testProg(b, source, AssemblerMaxVersion) + err := Check(ops.Program, defaultEvalParams(nil, nil)) require.NoError(b, err) evalLoop(b, runs, ops.Program) b.ReportMetric(float64(inst)*15.0, "waste/op") @@ -4741,8 +4763,8 @@ func TestLog(t *testing.T) { Type: protocol.ApplicationCallTx, }, } - ledger := makeTestLedger(nil) - ledger.newApp(txn.Txn.Receiver, 0, basics.AppParams{}) + ledger := logictest.MakeLedger(nil) + ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{}) sb := strings.Builder{} ep := defaultEvalParams(&sb, &txn) ep.Proto = &proto @@ -4773,24 +4795,22 @@ func TestLog(t *testing.T) { }, } - //track expected number of logs in ep.Ledger - count := 0 + //track expected number of logs in cx.Logs for i, s := range testCases { ops := testProg(t, s.source, AssemblerMaxVersion) err := CheckStateful(ops.Program, ep) require.NoError(t, err, s) - pass, err := EvalStateful(ops.Program, ep) + pass, cx, err := EvalStatefulCx(ops.Program, ep) require.NoError(t, err) require.True(t, pass) - count += s.loglen - require.Equal(t, len(ledger.logs), count) + require.Len(t, cx.Logs, s.loglen) if i == len(testCases)-1 { - require.Equal(t, strings.Repeat("a", MaxLogSize), ledger.logs[count-1].Message) + require.Equal(t, strings.Repeat("a", MaxLogSize), cx.Logs[0]) } else { - for _, l := range ledger.logs[count-s.loglen:] { - require.Equal(t, "a logging message", l.Message) + for _, l := range cx.Logs { + require.Equal(t, "a logging message", l) } } } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 55792c2597..78fba9adbb 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -331,6 +331,11 @@ const ( // CreatorAddress [32]byte CreatorAddress + // v5 + + // CurrentApplicationAddress [32]byte + CurrentApplicationAddress + invalidGlobalField ) @@ -358,6 +363,7 @@ var globalFieldSpecs = []globalFieldSpec{ {LatestTimestamp, StackUint64, runModeApplication, 2}, {CurrentApplicationID, StackUint64, runModeApplication, 2}, {CreatorAddress, StackBytes, runModeApplication, 3}, + {CurrentApplicationAddress, StackBytes, runModeApplication, 5}, } // GlobalFieldSpecByField maps GlobalField to spec @@ -512,6 +518,9 @@ const ( // AppCreator is not *in* the Params, but it is uniquely determined. AppCreator + // AppAddress is also not *in* the Params, but can be derived + AppAddress + invalidAppParamsField ) @@ -536,6 +545,7 @@ var appParamsFieldSpecs = []appParamsFieldSpec{ {AppLocalNumByteSlice, StackUint64, 5}, {AppExtraProgramPages, StackUint64, 5}, {AppCreator, StackBytes, 5}, + {AppAddress, StackBytes, 5}, } var appParamsFieldSpecByField map[AppParamsField]appParamsFieldSpec diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 836d492e0c..824c7c4b46 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -93,12 +93,13 @@ func _() { _ = x[LatestTimestamp-7] _ = x[CurrentApplicationID-8] _ = x[CreatorAddress-9] - _ = x[invalidGlobalField-10] + _ = x[CurrentApplicationAddress-10] + _ = x[invalidGlobalField-11] } -const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressinvalidGlobalField" +const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressinvalidGlobalField" -var _GlobalField_index = [...]uint8{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 136} +var _GlobalField_index = [...]uint8{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 161} func (i GlobalField) String() string { if i >= GlobalField(len(_GlobalField_index)-1) { @@ -147,12 +148,13 @@ func _() { _ = x[AppLocalNumByteSlice-5] _ = x[AppExtraProgramPages-6] _ = x[AppCreator-7] - _ = x[invalidAppParamsField-8] + _ = x[AppAddress-8] + _ = x[invalidAppParamsField-9] } -const _AppParamsField_name = "AppApprovalProgramAppClearStateProgramAppGlobalNumUintAppGlobalNumByteSliceAppLocalNumUintAppLocalNumByteSliceAppExtraProgramPagesAppCreatorinvalidAppParamsField" +const _AppParamsField_name = "AppApprovalProgramAppClearStateProgramAppGlobalNumUintAppGlobalNumByteSliceAppLocalNumUintAppLocalNumByteSliceAppExtraProgramPagesAppCreatorAppAddressinvalidAppParamsField" -var _AppParamsField_index = [...]uint8{0, 18, 38, 54, 75, 90, 110, 130, 140, 161} +var _AppParamsField_index = [...]uint8{0, 18, 38, 54, 75, 90, 110, 130, 140, 150, 171} func (i AppParamsField) String() string { if i < 0 || i >= AppParamsField(len(_AppParamsField_index)-1) { diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index e88f3820f6..e29b66a115 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions/logictest" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -45,7 +46,7 @@ func TestGlobalFieldsVersions(t *testing.T) { } require.Greater(t, len(fields), 1) - ledger := makeTestLedger(nil) + ledger := logictest.MakeLedger(nil) for _, field := range fields { text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction @@ -109,7 +110,7 @@ func TestTxnFieldVersions(t *testing.T) { } txnaVersion := uint64(appsEnabledVersion) - ledger := makeTestLedger(nil) + ledger := logictest.MakeLedger(nil) txn := makeSampleTxn() // We'll reject too early if we have a nonzero RekeyTo, because that // field must be zero for every txn in the group if this is an old diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 2ab59ed9c4..2574fb347a 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -115,7 +115,6 @@ var byteInt = StackTypes{StackBytes, StackUint64} var byteIntInt = StackTypes{StackBytes, StackUint64, StackUint64} var oneInt = StackTypes{StackUint64} var twoInts = StackTypes{StackUint64, StackUint64} -var threeInts = StackTypes{StackUint64, StackUint64, StackUint64} var oneAny = StackTypes{StackAny} var twoAny = StackTypes{StackAny, StackAny} var anyInt = StackTypes{StackAny, StackUint64} @@ -296,8 +295,11 @@ var OpSpecs = []OpSpec{ {0xae, "b~", opBytesBitNot, asmDefault, disDefault, oneBytes, oneBytes, 4, modeAny, costly(4)}, {0xaf, "bzero", opBytesZero, asmDefault, disDefault, oneInt, oneBytes, 4, modeAny, opDefault}, - // ABI support opcodes. + // AVM "effects" {0xb0, "log", opLog, asmDefault, disDefault, oneBytes, nil, 5, runModeApplication, opDefault}, + {0xb1, "tx_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, + {0xb2, "tx_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, + {0xb3, "tx_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, } type sortByOpcode []OpSpec diff --git a/data/transactions/logictest/ledger.go b/data/transactions/logictest/ledger.go new file mode 100644 index 0000000000..e7e2ec3d69 --- /dev/null +++ b/data/transactions/logictest/ledger.go @@ -0,0 +1,727 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logictest + +import ( + "fmt" + "math/rand" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/protocol" +) + +type balanceRecord struct { + addr basics.Address + auth basics.Address + balance uint64 + locals map[basics.AppIndex]basics.TealKeyValue + holdings map[basics.AssetIndex]basics.AssetHolding + mods map[basics.AppIndex]map[string]basics.ValueDelta +} + +func makeBalanceRecord(addr basics.Address, balance uint64) balanceRecord { + br := balanceRecord{ + addr: addr, + balance: balance, + locals: make(map[basics.AppIndex]basics.TealKeyValue), + holdings: make(map[basics.AssetIndex]basics.AssetHolding), + mods: make(map[basics.AppIndex]map[string]basics.ValueDelta), + } + return br +} + +// In our test ledger, we don't store the creatables with their +// creators, so we need to carry the creator around with them. +type appParams struct { + basics.AppParams + Creator basics.Address +} + +type asaParams struct { + basics.AssetParams + Creator basics.Address +} + +// Ledger is a convenient mock ledger that is used by +// data/transactions/logic It is in its own package so that it can be +// used by people developing teal code that need a fast testing setup, +// rather than running against a real network. It also might be +// expanded to support the Balances interface so that we have fewer +// mocks doing similar things. By putting it here, it is publicly +// exported, but will not be imported by non-test code, so won't bloat +// binary. +type Ledger struct { + balances map[basics.Address]balanceRecord + applications map[basics.AppIndex]appParams + assets map[basics.AssetIndex]asaParams + trackedCreatables map[int]basics.CreatableIndex + appID basics.AppIndex + mods map[basics.AppIndex]map[string]basics.ValueDelta + rnd basics.Round +} + +// MakeLedger constructs a Ledger with the given balances. +func MakeLedger(balances map[basics.Address]uint64) *Ledger { + l := new(Ledger) + l.balances = make(map[basics.Address]balanceRecord) + for addr, balance := range balances { + l.NewAccount(addr, balance) + } + l.applications = make(map[basics.AppIndex]appParams) + l.assets = make(map[basics.AssetIndex]asaParams) + l.trackedCreatables = make(map[int]basics.CreatableIndex) + l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) + return l +} + +// Reset removes all of the mods created by previous AVM execution +func (l *Ledger) Reset() { + l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) + for addr, br := range l.balances { + br.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) + l.balances[addr] = br + } +} + +// NewAccount adds a new account with a given balance to the Ledger. +func (l *Ledger) NewAccount(addr basics.Address, balance uint64) { + l.balances[addr] = makeBalanceRecord(addr, balance) +} + +// NewApp add a new AVM app to the Ledger, and arranges so that future +// executions will act as though they are that app. It only sets up +// the id and schema, it inserts no code, since testing will want to +// try many different code sequences. +func (l *Ledger) NewApp(creator basics.Address, appID basics.AppIndex, params basics.AppParams) { + l.appID = appID + params = params.Clone() + if params.GlobalState == nil { + params.GlobalState = make(basics.TealKeyValue) + } + l.applications[appID] = appParams{ + Creator: creator, + AppParams: params.Clone(), + } + br, ok := l.balances[creator] + if !ok { + br = makeBalanceRecord(creator, 0) + } + br.locals[appID] = make(map[string]basics.TealValue) + l.balances[creator] = br +} + +// NewAsset adds an asset with the given id and params to the ledger. +func (l *Ledger) NewAsset(creator basics.Address, assetID basics.AssetIndex, params basics.AssetParams) { + l.assets[assetID] = asaParams{ + Creator: creator, + AssetParams: params, + } + br, ok := l.balances[creator] + if !ok { + br = makeBalanceRecord(creator, 0) + } + br.holdings[assetID] = basics.AssetHolding{Amount: params.Total, Frozen: params.DefaultFrozen} + l.balances[creator] = br +} + +// NewHolding sets the ASA balance of a given account. +func (l *Ledger) NewHolding(addr basics.Address, assetID uint64, amount uint64, frozen bool) { + br, ok := l.balances[addr] + if !ok { + br = makeBalanceRecord(addr, 0) + } + br.holdings[basics.AssetIndex(assetID)] = basics.AssetHolding{Amount: amount, Frozen: frozen} + l.balances[addr] = br +} + +// NewLocals essentially "opts in" an address to an app id. +func (l *Ledger) NewLocals(addr basics.Address, appID uint64) { + l.balances[addr].locals[basics.AppIndex(appID)] = basics.TealKeyValue{} +} + +// NewLocal sets a local value of an app on an address +func (l *Ledger) NewLocal(addr basics.Address, appID uint64, key string, value basics.TealValue) { + l.balances[addr].locals[basics.AppIndex(appID)][key] = value +} + +// NoLocal removes a key from an address locals for an app. +func (l *Ledger) NoLocal(addr basics.Address, appID uint64, key string) { + delete(l.balances[addr].locals[basics.AppIndex(appID)], key) +} + +// NewGlobal sets a global value for an app +func (l *Ledger) NewGlobal(appID uint64, key string, value basics.TealValue) { + l.applications[basics.AppIndex(appID)].GlobalState[key] = value +} + +// NoGlobal removes a global key for an app +func (l *Ledger) NoGlobal(appID uint64, key string) { + delete(l.applications[basics.AppIndex(appID)].GlobalState, key) +} + +// Rekey sets the authAddr for an address. +func (l *Ledger) Rekey(addr basics.Address, auth basics.Address) { + if br, ok := l.balances[addr]; ok { + br.auth = auth + l.balances[addr] = br + } +} + +// Round gives the current Round of the test ledger, which is random but consistent +func (l *Ledger) Round() basics.Round { + return l.round() +} + +// LatestTimestamp gives a uint64, chosen randomly. It should +// probably increase monotonically, but no tests care yet. +func (l *Ledger) LatestTimestamp() int64 { + return int64(rand.Uint32() + 1) +} + +// Balance returns the value in an account, as MicroAlgos +func (l *Ledger) Balance(addr basics.Address) (amount basics.MicroAlgos, err error) { + br, ok := l.balances[addr] + if !ok { + return basics.MicroAlgos{Raw: 0}, nil + } + return basics.MicroAlgos{Raw: br.balance}, nil +} + +// MinBalance computes the MinBalance requirement for an account, +// under the given consensus parameters. +func (l *Ledger) MinBalance(addr basics.Address, proto *config.ConsensusParams) (amount basics.MicroAlgos, err error) { + br, ok := l.balances[addr] + if !ok { + br = makeBalanceRecord(addr, 0) + } + + var min uint64 + + // First, base MinBalance + min = proto.MinBalance + + // MinBalance for each Asset + assetCost := basics.MulSaturate(proto.MinBalance, uint64(len(br.holdings))) + min = basics.AddSaturate(min, assetCost) + + // Base MinBalance + GlobalStateSchema.MinBalance + ExtraProgramPages MinBalance for each created application + for _, params := range l.applications { + if params.Creator == addr { + min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) + min = basics.AddSaturate(min, params.GlobalStateSchema.MinBalance(proto).Raw) + min = basics.AddSaturate(min, basics.MulSaturate(proto.AppFlatParamsMinBalance, uint64(params.ExtraProgramPages))) + } + } + + // Base MinBalance + LocalStateSchema.MinBalance for each opted in application + for idx := range br.locals { + min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) + min = basics.AddSaturate(min, l.applications[idx].LocalStateSchema.MinBalance(proto).Raw) + } + + return basics.MicroAlgos{Raw: min}, nil +} + +// Authorizer returns the address that must authorize txns from a +// given address. It's either the address itself, or the value it has +// been rekeyed to. +func (l *Ledger) Authorizer(addr basics.Address) (basics.Address, error) { + br, ok := l.balances[addr] + if !ok { + return addr, nil // Not rekeyed if not present + } + if !br.auth.IsZero() { + return br.auth, nil + } + return br.addr, nil +} + +// GetGlobal returns the current value of a global in an app, taking +// into account the mods created by earlier teal execution. +func (l *Ledger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) { + if appIdx == basics.AppIndex(0) { + appIdx = l.appID + } + params, ok := l.applications[appIdx] + if !ok { + return basics.TealValue{}, false, fmt.Errorf("no such app") + } + + // return most recent value if available + tkvm, ok := l.mods[appIdx] + if ok { + val, ok := tkvm[key] + if ok { + tv, ok := val.ToTealValue() + return tv, ok, nil + } + } + + // otherwise return original one + val, ok := params.GlobalState[key] + return val, ok, nil +} + +// SetGlobal "sets" a global, but only through the mods mechanism, so +// it can be removed with Reset() +func (l *Ledger) SetGlobal(key string, value basics.TealValue) error { + appIdx := l.appID + params, ok := l.applications[appIdx] + if !ok { + return fmt.Errorf("no such app") + } + + // if writing the same value, return + // this simulates real ledger behavior for tests + val, ok := params.GlobalState[key] + if ok && val == value { + return nil + } + + // write to deltas + _, ok = l.mods[appIdx] + if !ok { + l.mods[appIdx] = make(map[string]basics.ValueDelta) + } + l.mods[appIdx][key] = value.ToValueDelta() + return nil +} + +// DelGlobal "deletes" a global, but only through the mods mechanism, so +// the deletion can be Reset() +func (l *Ledger) DelGlobal(key string) error { + appIdx := l.appID + params, ok := l.applications[appIdx] + if !ok { + return fmt.Errorf("no such app") + } + + exist := false + if _, ok := params.GlobalState[key]; ok { + exist = true + } + + _, ok = l.mods[appIdx] + if !ok && !exist { + // nothing to delete + return nil + } + if !ok { + l.mods[appIdx] = make(map[string]basics.ValueDelta) + } + _, ok = l.mods[appIdx][key] + if ok || exist { + l.mods[appIdx][key] = basics.ValueDelta{Action: basics.DeleteAction} + } + return nil +} + +// GetLocal returns the current value bound to a local key, taking +// into account mods caused by earlier executions. +func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) { + if appIdx == 0 { + appIdx = l.appID + } + br, ok := l.balances[addr] + if !ok { + return basics.TealValue{}, false, fmt.Errorf("no such address") + } + tkvd, ok := br.locals[appIdx] + if !ok { + return basics.TealValue{}, false, fmt.Errorf("no app for account") + } + + // check deltas first + tkvm, ok := br.mods[appIdx] + if ok { + val, ok := tkvm[key] + if ok { + tv, ok := val.ToTealValue() + return tv, ok, nil + } + } + + val, ok := tkvd[key] + return val, ok, nil +} + +// SetLocal "sets" the current value bound to a local key using the +// mods mechanism, so it can be Reset() +func (l *Ledger) SetLocal(addr basics.Address, key string, value basics.TealValue, accountIdx uint64) error { + appIdx := l.appID + + br, ok := l.balances[addr] + if !ok { + return fmt.Errorf("no such address") + } + tkv, ok := br.locals[appIdx] + if !ok { + return fmt.Errorf("no app for account") + } + + // if writing the same value, return + // this simulates real ledger behavior for tests + val, ok := tkv[key] + if ok && val == value { + return nil + } + + // write to deltas + _, ok = br.mods[appIdx] + if !ok { + br.mods[appIdx] = make(map[string]basics.ValueDelta) + } + br.mods[appIdx][key] = value.ToValueDelta() + return nil +} + +// DelLocal "deletes" the current value bound to a local key using the +// mods mechanism, so it can be Reset() +func (l *Ledger) DelLocal(addr basics.Address, key string, accountIdx uint64) error { + appIdx := l.appID + + br, ok := l.balances[addr] + if !ok { + return fmt.Errorf("no such address") + } + tkv, ok := br.locals[appIdx] + if !ok { + return fmt.Errorf("no app for account") + } + exist := false + if _, ok := tkv[key]; ok { + exist = true + } + + _, ok = br.mods[appIdx] + if !ok && !exist { + // nothing to delete + return nil + } + if !ok { + br.mods[appIdx] = make(map[string]basics.ValueDelta) + } + _, ok = br.mods[appIdx][key] + if ok || exist { + br.mods[appIdx][key] = basics.ValueDelta{Action: basics.DeleteAction} + } + return nil +} + +// OptedIn returns whether an Address has opted into the app (usually +// from NewLocals, but potentially from executing AVM inner +// transactions. +func (l *Ledger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) { + if appIdx == 0 { + appIdx = l.appID + } + br, ok := l.balances[addr] + if !ok { + return false, fmt.Errorf("no such address") + } + _, ok = br.locals[appIdx] + return ok, nil +} + +// SetTrackedCreatable remembers that the given cl "happened" in txn +// groupIdx of the group, for use by GetCreatableID. +func (l *Ledger) SetTrackedCreatable(groupIdx int, cl basics.CreatableLocator) { + l.trackedCreatables[groupIdx] = cl.Index +} + +// GetCreatableID returns the creatable constructed in a given transaction +// slot. For the test ledger, that's been set up by SetTrackedCreatable +func (l *Ledger) GetCreatableID(groupIdx int) basics.CreatableIndex { + return l.trackedCreatables[groupIdx] +} + +// AssetHolding gives the amount of an ASA held by an account, or +// error if the account is not opted into the asset. +func (l *Ledger) AssetHolding(addr basics.Address, assetID basics.AssetIndex) (basics.AssetHolding, error) { + if br, ok := l.balances[addr]; ok { + if asset, ok := br.holdings[assetID]; ok { + return asset, nil + } + return basics.AssetHolding{}, fmt.Errorf("No asset for account") + } + return basics.AssetHolding{}, fmt.Errorf("no such address") +} + +// AssetParams gives the parameters of an ASA if it exists +func (l *Ledger) AssetParams(assetID basics.AssetIndex) (basics.AssetParams, basics.Address, error) { + if asset, ok := l.assets[assetID]; ok { + return asset.AssetParams, asset.Creator, nil + } + return basics.AssetParams{}, basics.Address{}, fmt.Errorf("no such asset") +} + +// AppParams gives the parameters of an App if it exists +func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Address, error) { + if app, ok := l.applications[appID]; ok { + return app.AppParams, app.Creator, nil + } + return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app") +} + +// ApplicationID gives ID of the "currently running" app. For this +// test ledger, that is chosen explicitly. +func (l *Ledger) ApplicationID() basics.AppIndex { + return l.appID +} + +// CreatorAddress returns of the address that created the "currently running" app. +func (l *Ledger) CreatorAddress() basics.Address { + _, addr, _ := l.AppParams(l.appID) + return addr +} + +// GetDelta translates the mods set by AVM execution into the standard +// format of an EvalDelta. +func (l *Ledger) GetDelta(txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { + if tkv, ok := l.mods[l.appID]; ok { + evalDelta.GlobalDelta = tkv + } + if len(txn.Accounts) > 0 { + accounts := make(map[basics.Address]int) + accounts[txn.Sender] = 0 + for idx, addr := range txn.Accounts { + accounts[addr] = idx + 1 + } + evalDelta.LocalDeltas = make(map[uint64]basics.StateDelta) + for addr, br := range l.balances { + if idx, ok := accounts[addr]; ok { + if delta, ok := br.mods[l.appID]; ok { + evalDelta.LocalDeltas[uint64(idx)] = delta + } + } + } + } + return +} + +func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error { + fbr, ok := l.balances[from] + if !ok { + fbr = makeBalanceRecord(from, 0) + } + tbr, ok := l.balances[to] + if !ok { + tbr = makeBalanceRecord(to, 0) + } + if fbr.balance < amount { + return fmt.Errorf("insufficient balance") + } + fbr.balance -= amount + tbr.balance += amount + // We do not check min balances yet. They are checked when txn is complete. + l.balances[from] = fbr + l.balances[to] = tbr + return nil +} + +func (l *Ledger) pay(from basics.Address, pay transactions.PaymentTxnFields) error { + err := l.move(from, pay.Receiver, pay.Amount.Raw) + if err != nil { + return err + } + if !pay.CloseRemainderTo.IsZero() { + sbr := l.balances[from] + if len(sbr.holdings) > 0 { + return fmt.Errorf("unable to close, Sender (%s) has holdings", from) + } + if len(sbr.locals) > 0 { + return fmt.Errorf("unable to close, Sender (%s) is opted in to apps", from) + } + // Should also check app creations. + // Need not check asa creations, as you can't opt out if you created. + // (though this test ledger doesn't know that) + remainder := sbr.balance + if remainder > 0 { + return l.move(from, pay.CloseRemainderTo, remainder) + } + } + return nil +} + +func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFields) error { + to := xfer.AssetReceiver + aid := xfer.XferAsset + amount := xfer.AssetAmount + close := xfer.AssetCloseTo + + fbr, ok := l.balances[from] + if !ok { + fbr = makeBalanceRecord(from, 0) + } + fholding, ok := fbr.holdings[aid] + if !ok { + if from == to && amount == 0 { + // opt in + if params, exists := l.assets[aid]; exists { + fbr.holdings[aid] = basics.AssetHolding{ + Frozen: params.DefaultFrozen, + } + return nil + } + return fmt.Errorf("Asset (%d) does not exist", aid) + } + return fmt.Errorf("Sender (%s) not opted in to %d", from, aid) + } + if fholding.Frozen { + return fmt.Errorf("Sender (%s) is frozen for %d", from, aid) + } + tbr, ok := l.balances[to] + if !ok { + tbr = makeBalanceRecord(to, 0) + } + tholding, ok := tbr.holdings[aid] + if !ok && amount > 0 { + return fmt.Errorf("AssetReceiver (%s) not opted in to %d", to, aid) + } + if fholding.Amount < amount { + return fmt.Errorf("insufficient balance") + } + + // Not just an optimization. + // amount >0 : allows axfer to not opted in account + // from != to : prevents overwriting the same balance record with only + // the second change, and ensures fholding remains correct + // for closeTo handling. + if amount > 0 && from != to { + fholding.Amount -= amount + fbr.holdings[aid] = fholding + l.balances[from] = fbr + + tholding.Amount += amount + tbr.holdings[aid] = tholding + l.balances[to] = tbr + } + + if !close.IsZero() && fholding.Amount > 0 { + cbr, ok := l.balances[close] + if !ok { + cbr = makeBalanceRecord(close, 0) + } + cholding, ok := cbr.holdings[aid] + if !ok { + return fmt.Errorf("AssetCloseTo (%s) not opted in to %d", to, aid) + } + + // Opt out + delete(fbr.holdings, aid) + l.balances[from] = fbr + + cholding.Amount += fholding.Amount + cbr.holdings[aid] = cholding + l.balances[close] = cbr + } + + return nil +} + +/* It's gross to reimplement this here, rather than have a way to use + a ledger that's backed by our mock, but uses the "real" code + (cowRoundState which implements Balances), as a better test. To + allow that, we need to move our mocks into separate packages so + they can be combined in yet *another* package, and avoid circular + imports. + + This is currently unable to fill the ApplyData objects. That would + require a whole new level of code duplication. +*/ + +// Perform causes txn to "occur" against the ledger. The returned ad is empty. +func (l *Ledger) Perform(txn *transactions.Transaction, spec transactions.SpecialAddresses) (transactions.ApplyData, error) { + var ad transactions.ApplyData + + err := l.move(txn.Sender, spec.FeeSink, txn.Fee.Raw) + if err != nil { + return ad, err + } + switch txn.Type { + case protocol.PaymentTx: + err = l.pay(txn.Sender, txn.PaymentTxnFields) + case protocol.AssetTransferTx: + err = l.axfer(txn.Sender, txn.AssetTransferTxnFields) + default: + err = fmt.Errorf("%s txn in AVM", txn.Type) + } + return ad, err +} + +// Get() through allocated() implement cowForLogicLedger, so we should +// be able to make logicLedger with this inside. That let's us to +// write tests and then poke around and see how the balance table +// inside is affected. + +// Get returns the AccountData of an address. This test ledger does +// not handle rewards, so the pening rewards flag is ignored. +func (l *Ledger) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { + br, ok := l.balances[addr] + if !ok { + return basics.AccountData{}, fmt.Errorf("addr %s not in test.Ledger", addr.String()) + } + return basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: br.balance}, + AssetParams: map[basics.AssetIndex]basics.AssetParams{}, + Assets: map[basics.AssetIndex]basics.AssetHolding{}, + AppLocalStates: map[basics.AppIndex]basics.AppLocalState{}, + AppParams: map[basics.AppIndex]basics.AppParams{}, + }, nil +} + +// GetCreator returns the creator of the given creatable, an app or asa. +func (l *Ledger) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) { + if ctype == basics.AssetCreatable { + params, found := l.assets[basics.AssetIndex(cidx)] + return params.Creator, found, nil + } + if ctype == basics.AppCreatable { + params, found := l.applications[basics.AppIndex(cidx)] + return params.Creator, found, nil + } + return basics.Address{}, false, fmt.Errorf("%v %d is not in test.Ledger", ctype, cidx) +} + +// SetKey creates a new key-value in {addr, aidx, global} storage +func (l *Ledger) SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error { + if global { + l.NewGlobal(uint64(aidx), key, value) + } else { + l.NewLocal(addr, uint64(aidx), key, value) + } + return nil +} + +// DelKey removes a key from {addr, aidx, global} storage +func (l *Ledger) DelKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error { + if global { + l.NoGlobal(uint64(aidx), key) + } else { + l.NoLocal(addr, uint64(aidx), key) + } + return nil +} + +func (l *Ledger) round() basics.Round { + if l.rnd == basics.Round(0) { + l.rnd = basics.Round(rand.Uint32() + 1) + } + return l.rnd +} diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 365e2d5408..b46a5694da 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -3,6 +3,8 @@ package transactions // Code generated by github.com/algorand/msgp DO NOT EDIT. import ( + "sort" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -58,6 +60,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// EvalDelta +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // Header // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -1611,6 +1621,365 @@ func (z *CompactCertTxnFields) MsgIsZero() bool { return ((*z).CertRound.MsgIsZero()) && ((*z).CertType.MsgIsZero()) && ((*z).Cert.MsgIsZero()) } +// MarshalMsg implements msgp.Marshaler +func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0005Len := uint32(4) + var zb0005Mask uint8 /* 5 bits */ + if (*z).GlobalDelta.MsgIsZero() { + zb0005Len-- + zb0005Mask |= 0x2 + } + if len((*z).InnerTxns) == 0 { + zb0005Len-- + zb0005Mask |= 0x4 + } + if len((*z).LocalDeltas) == 0 { + zb0005Len-- + zb0005Mask |= 0x8 + } + if len((*z).Logs) == 0 { + zb0005Len-- + zb0005Mask |= 0x10 + } + // variable map header, size zb0005Len + o = append(o, 0x80|uint8(zb0005Len)) + if zb0005Len != 0 { + if (zb0005Mask & 0x2) == 0 { // if not empty + // string "gd" + o = append(o, 0xa2, 0x67, 0x64) + o = (*z).GlobalDelta.MarshalMsg(o) + } + if (zb0005Mask & 0x4) == 0 { // if not empty + // string "itx" + o = append(o, 0xa3, 0x69, 0x74, 0x78) + if (*z).InnerTxns == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).InnerTxns))) + } + for zb0004 := range (*z).InnerTxns { + o = (*z).InnerTxns[zb0004].MarshalMsg(o) + } + } + if (zb0005Mask & 0x8) == 0 { // if not empty + // string "ld" + o = append(o, 0xa2, 0x6c, 0x64) + if (*z).LocalDeltas == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).LocalDeltas))) + } + zb0001_keys := make([]uint64, 0, len((*z).LocalDeltas)) + for zb0001 := range (*z).LocalDeltas { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(SortUint64(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).LocalDeltas[zb0001] + _ = zb0002 + o = msgp.AppendUint64(o, zb0001) + o = zb0002.MarshalMsg(o) + } + } + if (zb0005Mask & 0x10) == 0 { // if not empty + // string "lg" + o = append(o, 0xa2, 0x6c, 0x67) + if (*z).Logs == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).Logs))) + } + for zb0003 := range (*z).Logs { + o = msgp.AppendString(o, (*z).Logs[zb0003]) + } + } + } + return +} + +func (_ *EvalDelta) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*EvalDelta) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *EvalDelta) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0005 int + var zb0006 bool + zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0005, zb0006, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0005 > 0 { + zb0005-- + bts, err = (*z).GlobalDelta.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "GlobalDelta") + return + } + } + if zb0005 > 0 { + zb0005-- + var zb0007 int + var zb0008 bool + zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") + return + } + if zb0007 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0007), uint64(config.MaxEvalDeltaAccounts)) + err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") + return + } + if zb0008 { + (*z).LocalDeltas = nil + } else if (*z).LocalDeltas == nil { + (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0007) + } + for zb0007 > 0 { + var zb0001 uint64 + var zb0002 basics.StateDelta + zb0007-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LocalDeltas") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LocalDeltas", zb0001) + return + } + (*z).LocalDeltas[zb0001] = zb0002 + } + } + if zb0005 > 0 { + zb0005-- + var zb0009 int + var zb0010 bool + zb0009, zb0010, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Logs") + return + } + if zb0009 > config.MaxLogCalls { + err = msgp.ErrOverflow(uint64(zb0009), uint64(config.MaxLogCalls)) + err = msgp.WrapError(err, "struct-from-array", "Logs") + return + } + if zb0010 { + (*z).Logs = nil + } else if (*z).Logs != nil && cap((*z).Logs) >= zb0009 { + (*z).Logs = ((*z).Logs)[:zb0009] + } else { + (*z).Logs = make([]string, zb0009) + } + for zb0003 := range (*z).Logs { + (*z).Logs[zb0003], bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Logs", zb0003) + return + } + } + } + if zb0005 > 0 { + zb0005-- + var zb0011 int + var zb0012 bool + zb0011, zb0012, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "InnerTxns") + return + } + if zb0011 > config.MaxInnerTransactions { + err = msgp.ErrOverflow(uint64(zb0011), uint64(config.MaxInnerTransactions)) + err = msgp.WrapError(err, "struct-from-array", "InnerTxns") + return + } + if zb0012 { + (*z).InnerTxns = nil + } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0011 { + (*z).InnerTxns = ((*z).InnerTxns)[:zb0011] + } else { + (*z).InnerTxns = make([]SignedTxnWithAD, zb0011) + } + for zb0004 := range (*z).InnerTxns { + bts, err = (*z).InnerTxns[zb0004].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "InnerTxns", zb0004) + return + } + } + } + if zb0005 > 0 { + err = msgp.ErrTooManyArrayFields(zb0005) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0006 { + (*z) = EvalDelta{} + } + for zb0005 > 0 { + zb0005-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "gd": + bts, err = (*z).GlobalDelta.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "GlobalDelta") + return + } + case "ld": + var zb0013 int + var zb0014 bool + zb0013, zb0014, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "LocalDeltas") + return + } + if zb0013 > config.MaxEvalDeltaAccounts { + err = msgp.ErrOverflow(uint64(zb0013), uint64(config.MaxEvalDeltaAccounts)) + err = msgp.WrapError(err, "LocalDeltas") + return + } + if zb0014 { + (*z).LocalDeltas = nil + } else if (*z).LocalDeltas == nil { + (*z).LocalDeltas = make(map[uint64]basics.StateDelta, zb0013) + } + for zb0013 > 0 { + var zb0001 uint64 + var zb0002 basics.StateDelta + zb0013-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LocalDeltas") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "LocalDeltas", zb0001) + return + } + (*z).LocalDeltas[zb0001] = zb0002 + } + case "lg": + var zb0015 int + var zb0016 bool + zb0015, zb0016, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Logs") + return + } + if zb0015 > config.MaxLogCalls { + err = msgp.ErrOverflow(uint64(zb0015), uint64(config.MaxLogCalls)) + err = msgp.WrapError(err, "Logs") + return + } + if zb0016 { + (*z).Logs = nil + } else if (*z).Logs != nil && cap((*z).Logs) >= zb0015 { + (*z).Logs = ((*z).Logs)[:zb0015] + } else { + (*z).Logs = make([]string, zb0015) + } + for zb0003 := range (*z).Logs { + (*z).Logs[zb0003], bts, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Logs", zb0003) + return + } + } + case "itx": + var zb0017 int + var zb0018 bool + zb0017, zb0018, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "InnerTxns") + return + } + if zb0017 > config.MaxInnerTransactions { + err = msgp.ErrOverflow(uint64(zb0017), uint64(config.MaxInnerTransactions)) + err = msgp.WrapError(err, "InnerTxns") + return + } + if zb0018 { + (*z).InnerTxns = nil + } else if (*z).InnerTxns != nil && cap((*z).InnerTxns) >= zb0017 { + (*z).InnerTxns = ((*z).InnerTxns)[:zb0017] + } else { + (*z).InnerTxns = make([]SignedTxnWithAD, zb0017) + } + for zb0004 := range (*z).InnerTxns { + bts, err = (*z).InnerTxns[zb0004].UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "InnerTxns", zb0004) + return + } + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *EvalDelta) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*EvalDelta) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *EvalDelta) Msgsize() (s int) { + s = 1 + 3 + (*z).GlobalDelta.Msgsize() + 3 + msgp.MapHeaderSize + if (*z).LocalDeltas != nil { + for zb0001, zb0002 := range (*z).LocalDeltas { + _ = zb0001 + _ = zb0002 + s += 0 + msgp.Uint64Size + zb0002.Msgsize() + } + } + s += 3 + msgp.ArrayHeaderSize + for zb0003 := range (*z).Logs { + s += msgp.StringPrefixSize + len((*z).Logs[zb0003]) + } + s += 4 + msgp.ArrayHeaderSize + for zb0004 := range (*z).InnerTxns { + s += (*z).InnerTxns[zb0004].Msgsize() + } + return +} + +// MsgIsZero returns whether this is a zero value +func (z *EvalDelta) MsgIsZero() bool { + return ((*z).GlobalDelta.MsgIsZero()) && (len((*z).LocalDeltas) == 0) && (len((*z).Logs) == 0) && (len((*z).InnerTxns) == 0) +} + // MarshalMsg implements msgp.Marshaler func (z *Header) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index 8c8697f18d..0113b5ada7 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -372,8 +372,66 @@ func BenchmarkUnmarshalCompactCertTxnFields(b *testing.B) { } } +func TestMarshalUnmarshalEvalDelta(t *testing.T) { + v := EvalDelta{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingEvalDelta(t *testing.T) { + protocol.RunEncodingTest(t, &EvalDelta{}) +} + +func BenchmarkMarshalMsgEvalDelta(b *testing.B) { + v := EvalDelta{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgEvalDelta(b *testing.B) { + v := EvalDelta{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalEvalDelta(b *testing.B) { + v := EvalDelta{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalHeader(t *testing.T) { - partitiontest.PartitionTest(t) v := Header{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -433,7 +491,6 @@ func BenchmarkUnmarshalHeader(b *testing.B) { } func TestMarshalUnmarshalKeyregTxnFields(t *testing.T) { - partitiontest.PartitionTest(t) v := KeyregTxnFields{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -493,7 +550,6 @@ func BenchmarkUnmarshalKeyregTxnFields(b *testing.B) { } func TestMarshalUnmarshalLogicSig(t *testing.T) { - partitiontest.PartitionTest(t) v := LogicSig{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -553,7 +609,6 @@ func BenchmarkUnmarshalLogicSig(b *testing.B) { } func TestMarshalUnmarshalPaymentTxnFields(t *testing.T) { - partitiontest.PartitionTest(t) v := PaymentTxnFields{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -613,7 +668,6 @@ func BenchmarkUnmarshalPaymentTxnFields(b *testing.B) { } func TestMarshalUnmarshalPayset(t *testing.T) { - partitiontest.PartitionTest(t) v := Payset{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -673,7 +727,6 @@ func BenchmarkUnmarshalPayset(b *testing.B) { } func TestMarshalUnmarshalSignedTxn(t *testing.T) { - partitiontest.PartitionTest(t) v := SignedTxn{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -733,7 +786,6 @@ func BenchmarkUnmarshalSignedTxn(b *testing.B) { } func TestMarshalUnmarshalSignedTxnInBlock(t *testing.T) { - partitiontest.PartitionTest(t) v := SignedTxnInBlock{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -793,7 +845,6 @@ func BenchmarkUnmarshalSignedTxnInBlock(b *testing.B) { } func TestMarshalUnmarshalSignedTxnWithAD(t *testing.T) { - partitiontest.PartitionTest(t) v := SignedTxnWithAD{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -853,7 +904,6 @@ func BenchmarkUnmarshalSignedTxnWithAD(b *testing.B) { } func TestMarshalUnmarshalTransaction(t *testing.T) { - partitiontest.PartitionTest(t) v := Transaction{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) @@ -913,7 +963,6 @@ func BenchmarkUnmarshalTransaction(b *testing.B) { } func TestMarshalUnmarshalTxGroup(t *testing.T) { - partitiontest.PartitionTest(t) v := TxGroup{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) diff --git a/data/transactions/signedtxn.go b/data/transactions/signedtxn.go index 8b54300d9a..c30cd53036 100644 --- a/data/transactions/signedtxn.go +++ b/data/transactions/signedtxn.go @@ -18,6 +18,7 @@ package transactions import ( "errors" + "fmt" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -87,7 +88,7 @@ func (s SignedTxnInBlock) GetEncodedLength() int { // This is just s.AuthAddr or, if s.AuthAddr is zero, s.Txn.Sender. // It's provided as a convenience method. func (s SignedTxn) Authorizer() basics.Address { - if (s.AuthAddr == basics.Address{}) { + if s.AuthAddr.IsZero() { return s.Txn.Sender } return s.AuthAddr @@ -129,3 +130,32 @@ func WrapSignedTxnsWithAD(txgroup []SignedTxn) []SignedTxnWithAD { } return txgroupad } + +// FeeCredit computes the amount of fee credit that can be spent on +// inner txns because it was more than required. +func FeeCredit(txgroup []SignedTxn, minFee uint64) (uint64, error) { + minFeeCount := uint64(0) + feesPaid := uint64(0) + for _, stxn := range txgroup { + if stxn.Txn.Type != protocol.CompactCertTx { + minFeeCount++ + } + feesPaid = basics.AddSaturate(feesPaid, stxn.Txn.Fee.Raw) + } + feeNeeded, overflow := basics.OMul(minFee, minFeeCount) + if overflow { + return 0, fmt.Errorf("txgroup fee requirement overflow") + } + // feesPaid may have saturated. That's ok. Since we know + // feeNeeded did not overflow, simple comparison tells us + // feesPaid was enough. + if feesPaid < feeNeeded { + return 0, fmt.Errorf("txgroup had %d in fees, which is less than the minimum %d * %d", + feesPaid, minFeeCount, minFee) + } + // Now, if feesPaid *did* saturate, you will not get "credit" for + // all those fees while executing AVM code that might create + // transactions. But you'll get the max uint64 - good luck + // spending it. + return feesPaid - feeNeeded, nil +} diff --git a/data/transactions/sort.go b/data/transactions/sort.go new file mode 100644 index 0000000000..3746ab6402 --- /dev/null +++ b/data/transactions/sort.go @@ -0,0 +1,37 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package transactions + +// SortUint64 implements sorting by uint64 keys for +// canonical encoding of maps in msgpack format. +//msgp:ignore SortUint64 +//msgp:sort uint64 SortUint64 +type SortUint64 []uint64 + +func (a SortUint64) Len() int { return len(a) } +func (a SortUint64) Less(i, j int) bool { return a[i] < a[j] } +func (a SortUint64) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// SortString implements sorting by string keys for +// canonical encoding of maps in msgpack format. +//msgp:ignore SortString +//msgp:sort string SortString +type SortString []string + +func (a SortString) Len() int { return len(a) } +func (a SortString) Less(i, j int) bool { return a[i] < a[j] } +func (a SortString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/data/transactions/teal.go b/data/transactions/teal.go new file mode 100644 index 0000000000..e2f6718b76 --- /dev/null +++ b/data/transactions/teal.go @@ -0,0 +1,111 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package transactions + +import ( + "bytes" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" +) + +// EvalDelta stores StateDeltas for an application's global key/value store, as +// well as StateDeltas for some number of accounts holding local state for that +// application +type EvalDelta struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + GlobalDelta basics.StateDelta `codec:"gd"` + + // When decoding EvalDeltas, the integer key represents an offset into + // [txn.Sender, txn.Accounts[0], txn.Accounts[1], ...] + LocalDeltas map[uint64]basics.StateDelta `codec:"ld,allocbound=config.MaxEvalDeltaAccounts"` + + Logs []string `codec:"lg,allocbound=config.MaxLogCalls"` + + // Intentionally, temporarily wrong - need to decide how to + // allocbound properly when structure is recursive. Even a bound + // of 2 would allow arbitrarily large object if deep. + InnerTxns []SignedTxnWithAD `codec:"itx,allocbound=config.MaxInnerTransactions"` +} + +// Equal compares two EvalDeltas and returns whether or not they are +// equivalent. It does not care about nilness equality of LocalDeltas, +// because the msgpack codec will encode/decode an empty map as nil, and we want +// an empty generated EvalDelta to equal an empty one we decode off the wire. +func (ed EvalDelta) Equal(o EvalDelta) bool { + // LocalDeltas length should be the same + if len(ed.LocalDeltas) != len(o.LocalDeltas) { + return false + } + + // All keys and local StateDeltas should be the same + for k, v := range ed.LocalDeltas { + // Other LocalDelta must have value for key + ov, ok := o.LocalDeltas[k] + if !ok { + return false + } + + // Other LocalDelta must have same value for key + if !ov.Equal(v) { + return false + } + } + + // GlobalDeltas must be equal + if !ed.GlobalDelta.Equal(o.GlobalDelta) { + return false + } + + // Logs must be equal + if len(ed.Logs) != len(o.Logs) { + return false + } + for i, l := range ed.Logs { + if l != o.Logs[i] { + return false + } + } + + // InnerTxns must be equal + if len(ed.InnerTxns) != len(o.InnerTxns) { + return false + } + for i, txn := range ed.InnerTxns { + if !txn.SignedTxn.equal(o.InnerTxns[i].SignedTxn) { + return false + } + if !txn.ApplyData.Equal(o.InnerTxns[i].ApplyData) { + return false + } + } + + return true +} + +// equal compares two SignedTransactions for equality. It's not +// exported because it ought to be written as (many, very, very +// tedious) field comparisons. == is not defined on almost any of the +// subfields because of slices. +func (stx SignedTxn) equal(o SignedTxn) bool { + stxenc := stx.MarshalMsg(protocol.GetEncodingBuf()) + defer protocol.PutEncodingBuf(stxenc) + oenc := o.MarshalMsg(protocol.GetEncodingBuf()) + defer protocol.PutEncodingBuf(oenc) + return bytes.Equal(stxenc, oenc) +} diff --git a/data/transactions/teal_test.go b/data/transactions/teal_test.go new file mode 100644 index 0000000000..32eea6abbf --- /dev/null +++ b/data/transactions/teal_test.go @@ -0,0 +1,189 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package transactions + +import ( + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestEvalDeltaEqual(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + + d1 := EvalDelta{} + d2 := EvalDelta{} + a.True(d1.Equal(d2)) + + d2 = EvalDelta{ + GlobalDelta: nil, + LocalDeltas: nil, + Logs: nil, + } + a.True(d1.Equal(d2)) + + d2 = EvalDelta{ + GlobalDelta: basics.StateDelta{}, + LocalDeltas: map[uint64]basics.StateDelta{}, + Logs: []string{}, + } + a.True(d1.Equal(d2)) + + d2 = EvalDelta{ + GlobalDelta: basics.StateDelta{"test": {Action: basics.SetUintAction, Uint: 0}}, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + GlobalDelta: basics.StateDelta{"test": {Action: basics.SetUintAction, Uint: 0}}, + } + a.True(d1.Equal(d2)) + + d2 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetUintAction, Uint: 0}}, + }, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetUintAction, Uint: 1}}, + }, + } + a.False(d1.Equal(d2)) + + d2 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetUintAction, Uint: 1}}, + }, + } + a.True(d1.Equal(d2)) + + d1 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetBytesAction, Bytes: "val"}}, + }, + } + d2 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetBytesAction, Bytes: "val"}}, + }, + } + a.True(d1.Equal(d2)) + + d2 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 0: {"test": {Action: basics.SetBytesAction, Bytes: "val1"}}, + }, + } + a.False(d1.Equal(d2)) + + d2 = EvalDelta{ + LocalDeltas: map[uint64]basics.StateDelta{ + 1: {"test": {Action: basics.SetBytesAction, Bytes: "val"}}, + }, + } + a.False(d1.Equal(d2)) + + d2 = EvalDelta{ + Logs: []string{"val"}, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + Logs: []string{"val2"}, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + Logs: []string{"val", "val2"}, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + Logs: []string{"val"}, + } + a.True(d1.Equal(d2)) + + // Test inner transaction equality + d1 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{}, + } + d2 = EvalDelta{ + InnerTxns: nil, + } + a.True(d1.Equal(d2)) + + // Test inner transaction equality + d1 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{{ + SignedTxn: SignedTxn{ + Lsig: LogicSig{ + Logic: []byte{0x01}, + }, + }, + }}, + } + d2 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{{ + SignedTxn: SignedTxn{ + Lsig: LogicSig{ + Logic: []byte{0x01}, + }, + }, + }}, + } + a.True(d1.Equal(d2)) + d2 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{{ + SignedTxn: SignedTxn{ + Lsig: LogicSig{ + Logic: []byte{0x02}, + Args: [][]byte{}, + }, + }, + }}, + } + a.False(d1.Equal(d2)) + + d1 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{{ + SignedTxn: SignedTxn{ + Txn: Transaction{ + Type: protocol.TxType("pay"), + }, + }, + }}, + } + d2 = EvalDelta{ + InnerTxns: []SignedTxnWithAD{{ + SignedTxn: SignedTxn{ + Txn: Transaction{ + Type: protocol.TxType("axfer"), + }, + }, + }}, + } + a.False(d1.Equal(d2)) + +} diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 552255796e..923e794785 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -112,7 +112,7 @@ type ApplyData struct { SenderRewards basics.MicroAlgos `codec:"rs"` ReceiverRewards basics.MicroAlgos `codec:"rr"` CloseRewards basics.MicroAlgos `codec:"rc"` - EvalDelta basics.EvalDelta `codec:"dt"` + EvalDelta EvalDelta `codec:"dt"` } // Equal returns true if two ApplyDatas are equal, ignoring nilness equality on diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index cb2d6d8759..4e710610dc 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -177,7 +177,7 @@ func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.B return } // feesPaid may have saturated. That's ok. Since we know - // feeNeeded did not overlfow, simple comparison tells us + // feeNeeded did not overflow, simple comparison tells us // feesPaid was enough. if feesPaid < feeNeeded { err = fmt.Errorf("txgroup had %d in fees, which is less than the minimum %d * %d", diff --git a/data/txntest/txn.go b/data/txntest/txn.go index 98357f9579..5d3a6d77f0 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -33,11 +33,15 @@ package txntest import ( + "fmt" + "strings" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/compactcert" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/protocol" ) @@ -47,7 +51,7 @@ type Txn struct { Type protocol.TxType Sender basics.Address - Fee basics.MicroAlgos + Fee uint64 FirstValid basics.Round LastValid basics.Round Note []byte @@ -65,7 +69,7 @@ type Txn struct { Nonparticipation bool Receiver basics.Address - Amount basics.MicroAlgos + Amount uint64 CloseRemainderTo basics.Address ConfigAsset basics.AssetIndex @@ -89,8 +93,8 @@ type Txn struct { ForeignAssets []basics.AssetIndex LocalStateSchema basics.StateSchema GlobalStateSchema basics.StateSchema - ApprovalProgram []byte - ClearStateProgram []byte + ApprovalProgram string + ClearStateProgram string ExtraProgramPages uint32 CertRound basics.Round @@ -98,15 +102,46 @@ type Txn struct { Cert compactcert.Cert } +// Noted returns a new Txn with the given note field. +func (tx *Txn) Noted(note string) *Txn { + copy := &Txn{} + *copy = *tx + copy.Note = []byte(note) + return copy +} + // FillDefaults populates some obvious defaults from config params, // unless they have already been set. func (tx *Txn) FillDefaults(params config.ConsensusParams) { - if tx.Fee.IsZero() { - tx.Fee = basics.MicroAlgos{Raw: params.MinTxnFee} + if tx.Fee == 0 { + tx.Fee = params.MinTxnFee } if tx.LastValid == 0 { tx.LastValid = tx.FirstValid + basics.Round(params.MaxTxnLife) } + if tx.ApprovalProgram != "" && !strings.Contains(tx.ApprovalProgram, "#pragma version") { + pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) + tx.ApprovalProgram = pragma + tx.ApprovalProgram + } + if tx.ApprovalProgram != "" && tx.ClearStateProgram == "" { + tx.ClearStateProgram = "int 0" + } + if tx.ClearStateProgram != "" && !strings.Contains(tx.ClearStateProgram, "#pragma version") { + pragma := fmt.Sprintf("#pragma version %d\n", params.LogicSigVersion) + tx.ClearStateProgram = pragma + tx.ClearStateProgram + } +} + +func assemble(source string) []byte { + if source == "" { + return nil + } + ops, err := logic.AssembleString(source) + if err != nil { + fmt.Printf("Bad program %v", ops.Errors) + panic(ops.Errors) + } + return ops.Program } // Txn produces a transactions.Transaction from the fields in this Txn @@ -115,7 +150,7 @@ func (tx Txn) Txn() transactions.Transaction { Type: tx.Type, Header: transactions.Header{ Sender: tx.Sender, - Fee: tx.Fee, + Fee: basics.MicroAlgos{Raw: tx.Fee}, FirstValid: tx.FirstValid, LastValid: tx.LastValid, Note: tx.Note, @@ -135,7 +170,7 @@ func (tx Txn) Txn() transactions.Transaction { }, PaymentTxnFields: transactions.PaymentTxnFields{ Receiver: tx.Receiver, - Amount: tx.Amount, + Amount: basics.MicroAlgos{Raw: tx.Amount}, CloseRemainderTo: tx.CloseRemainderTo, }, AssetConfigTxnFields: transactions.AssetConfigTxnFields{ @@ -163,8 +198,8 @@ func (tx Txn) Txn() transactions.Transaction { ForeignAssets: tx.ForeignAssets, LocalStateSchema: tx.LocalStateSchema, GlobalStateSchema: tx.GlobalStateSchema, - ApprovalProgram: tx.ApprovalProgram, - ClearStateProgram: tx.ClearStateProgram, + ApprovalProgram: assemble(tx.ApprovalProgram), + ClearStateProgram: assemble(tx.ClearStateProgram), ExtraProgramPages: tx.ExtraProgramPages, }, CompactCertTxnFields: transactions.CompactCertTxnFields{ @@ -181,3 +216,34 @@ func (tx Txn) Txn() transactions.Transaction { func (tx Txn) SignedTxn() transactions.SignedTxn { return transactions.SignedTxn{Txn: tx.Txn()} } + +// SignedTxnWithAD produces unsigned, transactions.SignedTxnWithAD +// from the fields in this Txn. This seemingly pointless operation +// exists, again, for convenience when driving tests. +func (tx Txn) SignedTxnWithAD() transactions.SignedTxnWithAD { + return transactions.SignedTxnWithAD{SignedTxn: tx.SignedTxn()} +} + +// SignedTxns turns a list of Txns into a slice of SignedTxns with +// GroupIDs set properly to make them a transaction group. Maybe +// another name is more approrpriate +func SignedTxns(txns ...*Txn) []transactions.SignedTxn { + txgroup := transactions.TxGroup{ + TxGroupHashes: make([]crypto.Digest, len(txns)), + } + for i, txn := range txns { + txn.Group = crypto.Digest{} + txgroup.TxGroupHashes[i] = crypto.HashObj(txn.Txn()) + } + group := crypto.HashObj(txgroup) + for _, txn := range txns { + txn.Group = group + } + + stxns := make([]transactions.SignedTxn, len(txns)) + for i, txn := range txns { + stxns[i] = txn.SignedTxn() + } + return stxns + +} diff --git a/ledger/appcow.go b/ledger/appcow.go index c334da05e5..22623c13ce 100644 --- a/ledger/appcow.go +++ b/ledger/appcow.go @@ -456,12 +456,6 @@ func (cb *roundCowState) DelKey(addr basics.Address, aidx basics.AppIndex, globa return nil // note: deletion cannot cause us to violate maxCount } -// AppendLog adds message in logs. idx is expected to be an index in txn.ForeignApps -func (cb *roundCowState) AppendLog(idx uint64, value string) error { - cb.logs = append(cb.logs, basics.LogItem{ID: idx, Message: value}) - return nil -} - // MakeDebugBalances creates a ledger suitable for dryrun and debugger func MakeDebugBalances(l ledgerForCowBase, round basics.Round, proto protocol.ConsensusVersion, prevTimestamp int64) apply.Balances { base := &roundCowBase{ @@ -482,34 +476,38 @@ func MakeDebugBalances(l ledgerForCowBase, round basics.Round, proto protocol.Co // StatefulEval runs application. // Execution happens in a child cow and all modifications are merged into parent if the program passes -func (cb *roundCowState) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (pass bool, evalDelta basics.EvalDelta, err error) { +func (cb *roundCowState) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (pass bool, evalDelta transactions.EvalDelta, err error) { // Make a child cow to eval our program in calf := cb.child(1) params.Ledger, err = newLogicLedger(calf, aidx) if err != nil { - return false, basics.EvalDelta{}, err + return false, transactions.EvalDelta{}, err } // Eval the program - pass, err = logic.EvalStateful(program, params) + var cx *logic.EvalContext + pass, cx, err = logic.EvalStatefulCx(program, params) if err != nil { - return false, basics.EvalDelta{}, ledgercore.LogicEvalError{Err: err} + return false, transactions.EvalDelta{}, ledgercore.LogicEvalError{Err: err} } // If program passed, build our eval delta, and commit to state changes if pass { evalDelta, err = calf.BuildEvalDelta(aidx, ¶ms.Txn.Txn) if err != nil { - return false, basics.EvalDelta{}, err + return false, transactions.EvalDelta{}, err } calf.commitToParent() + evalDelta.Logs = cx.Logs + evalDelta.InnerTxns = cx.InnerTxns } return pass, evalDelta, nil } -// BuildEvalDelta converts internal sdeltas and logs into basics.EvalDelta -func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) { +// BuildEvalDelta creates an EvalDelta by converting internal sdeltas +// into the (Global|Local)Delta fields. +func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { // sdeltas foundGlobal := false for addr, smod := range cb.sdeltas { @@ -517,13 +515,13 @@ func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions. // Check that all of these deltas are for the correct app if aapp.aidx != aidx { err = fmt.Errorf("found storage delta for different app during StatefulEval/BuildDelta: %d != %d", aapp.aidx, aidx) - return basics.EvalDelta{}, err + return transactions.EvalDelta{}, err } if aapp.global { // Check that there is at most one global delta if foundGlobal { err = fmt.Errorf("found more than one global delta during StatefulEval/BuildDelta: %d", aapp.aidx) - return basics.EvalDelta{}, err + return transactions.EvalDelta{}, err } evalDelta.GlobalDelta = sdelta.kvCow.serialize() foundGlobal = true @@ -543,7 +541,7 @@ func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions. } else { addrOffset, err = txn.IndexByAddress(addr, txn.Sender) if err != nil { - return basics.EvalDelta{}, err + return transactions.EvalDelta{}, err } } @@ -558,9 +556,6 @@ func (cb *roundCowState) BuildEvalDelta(aidx basics.AppIndex, txn *transactions. } } - // logs - evalDelta.Logs = cb.logs - return } diff --git a/ledger/appcow_test.go b/ledger/appcow_test.go index 167c918f71..28096d8bc3 100644 --- a/ledger/appcow_test.go +++ b/ledger/appcow_test.go @@ -389,7 +389,7 @@ func TestCowBuildDelta(t *testing.T) { cow.sdeltas[creator][storagePtr{aidx, true}] = &storageDelta{} ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) - a.Equal(basics.EvalDelta{GlobalDelta: basics.StateDelta{}}, ed) + a.Equal(transactions.EvalDelta{GlobalDelta: basics.StateDelta{}}, ed) cow.sdeltas[creator][storagePtr{aidx + 1, true}] = &storageDelta{} ed, err = cow.BuildEvalDelta(aidx, &txn) @@ -422,7 +422,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, LocalDeltas: map[uint64]basics.StateDelta{0: {}}, }, @@ -435,7 +435,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta{}, LocalDeltas: map[uint64]basics.StateDelta{}, }, @@ -458,7 +458,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta(nil), LocalDeltas: map[uint64]basics.StateDelta{ 0: { @@ -498,7 +498,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta(nil), LocalDeltas: map[uint64]basics.StateDelta{ 1: { @@ -532,7 +532,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta(nil), LocalDeltas: map[uint64]basics.StateDelta{ 0: { @@ -563,7 +563,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta(nil), LocalDeltas: map[uint64]basics.StateDelta{ 1: { @@ -591,7 +591,7 @@ func TestCowBuildDelta(t *testing.T) { ed, err = cow.BuildEvalDelta(aidx, &txn) a.NoError(err) a.Equal( - basics.EvalDelta{ + transactions.EvalDelta{ GlobalDelta: basics.StateDelta(nil), LocalDeltas: map[uint64]basics.StateDelta{ 0: { @@ -601,36 +601,6 @@ func TestCowBuildDelta(t *testing.T) { }, ed, ) - - // check logDelta is added - cow.logs = []basics.LogItem{{ID: 0, Message: "hello,world"}} - cow.sdeltas[sender][storagePtr{aidx, false}] = &storageDelta{ - action: remainAllocAction, - kvCow: stateDelta{ - "key1": valueDelta{ - old: basics.TealValue{Type: basics.TealUintType, Uint: 1}, - new: basics.TealValue{Type: basics.TealUintType, Uint: 2}, - oldExists: true, - newExists: true, - }, - }, - accountIdx: 1, - } - ed, err = cow.BuildEvalDelta(aidx, &txn) - a.NoError(err) - a.Equal( - basics.EvalDelta{ - GlobalDelta: basics.StateDelta(nil), - LocalDeltas: map[uint64]basics.StateDelta{ - 0: { - "key1": basics.ValueDelta{Action: basics.SetUintAction, Uint: 2}, - }, - }, - Logs: []basics.LogItem{{ID: 0, Message: "hello,world"}}, - }, - ed, - ) - } func TestCowDeltaSerialize(t *testing.T) { @@ -1360,19 +1330,3 @@ func TestCowDelKey(t *testing.T) { a.Panics(func() { c.DelKey(getRandomAddress(a), aidx, false, key, 0) }) a.Panics(func() { c.DelKey(addr, aidx+1, false, key, 0) }) } -func TestCowAppendLog(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := getRandomAddress(a) - aidx := basics.AppIndex(0) - c := getCow([]modsData{ - {addr, basics.CreatableIndex(aidx), basics.AppCreatable}, - }) - - c.logs = []basics.LogItem{} - err := c.AppendLog(uint64(aidx), "val") - a.NoError(err) - a.Equal(len(c.logs), 1) -} diff --git a/ledger/applications.go b/ledger/applications.go index 241b9c87bc..e8ce84ad31 100644 --- a/ledger/applications.go +++ b/ledger/applications.go @@ -22,6 +22,8 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/apply" + "github.com/algorand/go-algorand/protocol" ) type logicLedger struct { @@ -35,13 +37,11 @@ type cowForLogicLedger interface { GetCreatableID(groupIdx int) basics.CreatableIndex GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) GetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) (basics.TealValue, bool, error) - BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (basics.EvalDelta, error) + BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (transactions.EvalDelta, error) SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error DelKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, accountIdx uint64) error - AppendLog(idx uint64, value string) error - round() basics.Round prevTimestamp() int64 allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) @@ -87,6 +87,17 @@ func (al *logicLedger) MinBalance(addr basics.Address, proto *config.ConsensusPa return record.MinBalance(proto), nil } +func (al *logicLedger) Authorizer(addr basics.Address) (basics.Address, error) { + record, err := al.cow.Get(addr, false) // pending rewards unneeded + if err != nil { + return basics.Address{}, err + } + if !record.AuthAddr.IsZero() { + return record.AuthAddr, nil + } + return addr, nil +} + func (al *logicLedger) GetCreatableID(groupIdx int) basics.CreatableIndex { return al.cow.GetCreatableID(groupIdx) } @@ -234,17 +245,50 @@ func (al *logicLedger) DelGlobal(key string) error { return al.cow.DelKey(al.creator, al.aidx, true, key, 0) } -func (al *logicLedger) GetDelta(txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) { +func (al *logicLedger) GetDelta(txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { return al.cow.BuildEvalDelta(al.aidx, txn) } -func (al *logicLedger) AppendLog(txn *transactions.Transaction, value string) error { - idx, err := txn.IndexByAppID(txn.ApplicationID) - if idx != 0 { - return fmt.Errorf("index offset is not 0. logging is allowed for current app only") +func (al *logicLedger) balances() (apply.Balances, error) { + balances, ok := al.cow.(apply.Balances) + if !ok { + return nil, fmt.Errorf("cannot get a Balances object from %v", al) + } + return balances, nil +} + +func (al *logicLedger) Perform(tx *transactions.Transaction, spec transactions.SpecialAddresses) (transactions.ApplyData, error) { + var ad transactions.ApplyData + + balances, err := al.balances() + if err != nil { + return ad, err + } + + // move fee to pool + err = balances.Move(tx.Sender, spec.FeeSink, tx.Fee, &ad.SenderRewards, nil) + if err != nil { + return ad, err + } + + switch tx.Type { + case protocol.PaymentTx: + err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, spec, &ad) + case protocol.AssetTransferTx: + err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, balances, spec, &ad) + default: + err = fmt.Errorf("%s tx in AVM", tx.Type) } if err != nil { - return err + return ad, err } - return al.cow.AppendLog(idx, value) + + // We don't check min balances during in app txns. + + // func (eval *BlockEvaluator) checkMinBalance will take care of + // it when the top-level txn concludes, as because cow will return + // all changed accounts in modifiedAccounts(). + + return ad, nil + } diff --git a/ledger/applications_test.go b/ledger/applications_test.go index f64eb58810..43bdca4317 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -61,7 +61,6 @@ type mockCowForLogicLedger struct { brs map[basics.Address]basics.AccountData stores map[storeLocator]basics.TealKeyValue tcs map[int]basics.CreatableIndex - logs []basics.LogItem } func (c *mockCowForLogicLedger) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { @@ -90,8 +89,8 @@ func (c *mockCowForLogicLedger) GetKey(addr basics.Address, aidx basics.AppIndex return tv, found, nil } -func (c *mockCowForLogicLedger) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) { - return basics.EvalDelta{}, nil +func (c *mockCowForLogicLedger) BuildEvalDelta(aidx basics.AppIndex, txn *transactions.Transaction) (evalDelta transactions.EvalDelta, err error) { + return transactions.EvalDelta{}, nil } func (c *mockCowForLogicLedger) SetKey(addr basics.Address, aidx basics.AppIndex, global bool, key string, value basics.TealValue, accountIdx uint64) error { @@ -127,11 +126,6 @@ func (c *mockCowForLogicLedger) allocated(addr basics.Address, aidx basics.AppIn return found, nil } -func (c *mockCowForLogicLedger) AppendLog(aidx uint64, value string) error { - c.logs = append(c.logs, basics.LogItem{ID: aidx, Message: value}) - return nil -} - func newCowMock(creatables []modsData) *mockCowForLogicLedger { var m mockCowForLogicLedger m.cr = make(map[creatableLocator]basics.Address, len(creatables)) @@ -242,6 +236,7 @@ func TestLogicLedgerAsset(t *testing.T) { c.brs = map[basics.Address]basics.AccountData{ addr1: {AssetParams: map[basics.AssetIndex]basics.AssetParams{assetIdx: {Total: 1000}}}, } + ap, creator, err := l.AssetParams(assetIdx) a.NoError(err) a.Equal(addr1, creator) @@ -533,7 +528,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, - transactions.ApplyData{EvalDelta: basics.EvalDelta{ + transactions.ApplyData{EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "local"}}}}, }) a.NoError(err) @@ -581,7 +576,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, - transactions.ApplyData{EvalDelta: basics.EvalDelta{ + transactions.ApplyData{EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{"gk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "global"}}}, }) a.NoError(err) @@ -599,7 +594,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, - transactions.ApplyData{EvalDelta: basics.EvalDelta{ + transactions.ApplyData{EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "local"}}}}, }) a.NoError(err) @@ -724,7 +719,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, transactions.ApplyData{ - EvalDelta: basics.EvalDelta{ + EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{ Action: basics.SetBytesAction, Bytes: "local", @@ -787,7 +782,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, - transactions.ApplyData{EvalDelta: basics.EvalDelta{ + transactions.ApplyData{EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{"gk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "global"}}}, }) a.NoError(err) @@ -856,7 +851,7 @@ return` blk = makeNewEmptyBlock(t, l, genesisID, genesisInitState.Accounts) ad1 := transactions.ApplyData{ - EvalDelta: basics.EvalDelta{ + EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{0: {"lk1": basics.ValueDelta{ Action: basics.SetBytesAction, Bytes: "local1", @@ -979,7 +974,7 @@ return` ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, nil, initKeys, appCall, transactions.ApplyData{ - EvalDelta: basics.EvalDelta{ + EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{0: {"lk": basics.ValueDelta{ Action: basics.SetBytesAction, Bytes: "local", @@ -1154,7 +1149,7 @@ return` stx2 := sign(initKeys, payment) blk := makeNewEmptyBlock(t, l, genesisID, genesisInitState.Accounts) - txib1, err := blk.EncodeSignedTxn(stx1, transactions.ApplyData{EvalDelta: basics.EvalDelta{ + txib1, err := blk.EncodeSignedTxn(stx1, transactions.ApplyData{EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{ "gk": basics.ValueDelta{Action: basics.SetBytesAction, Bytes: "global"}, }}, @@ -1340,7 +1335,7 @@ func testAppAccountDeltaIndicesCompatibility(t *testing.T, source string, accoun ApplicationCallTxnFields: appCallFields, } err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, appCall, transactions.ApplyData{ - EvalDelta: basics.EvalDelta{ + EvalDelta: transactions.EvalDelta{ LocalDeltas: map[uint64]basics.StateDelta{ accountIdx: { "lk0": basics.ValueDelta{ @@ -1370,33 +1365,3 @@ func testAppAccountDeltaIndicesCompatibility(t *testing.T, source string, accoun a.Contains(blk.Payset[0].ApplyData.EvalDelta.LocalDeltas[accountIdx], "lk1") a.Equal(blk.Payset[0].ApplyData.EvalDelta.LocalDeltas[accountIdx]["lk1"].Bytes, "local1") } - -func TestLogicLedgerAppendLog(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - - addr := getRandomAddress(a) - aidx := basics.AppIndex(1) - c := newCowMock([]modsData{ - {addr, basics.CreatableIndex(1), basics.AppCreatable}, - }) - l, err := newLogicLedger(c, aidx) - a.NoError(err) - a.NotNil(l) - - appCallFields := transactions.ApplicationCallTxnFields{ - OnCompletion: transactions.NoOpOC, - ApplicationID: 0, - Accounts: []basics.Address{}, - } - appCall := transactions.Transaction{ - Type: protocol.ApplicationCallTx, - ApplicationCallTxnFields: appCallFields, - } - - err = l.AppendLog(&appCall, "a") - a.NoError(err) - a.Equal(len(c.logs), 1) - a.Equal(c.logs[0].Message, "a") -} diff --git a/ledger/apply/application.go b/ledger/apply/application.go index 644c8e3960..fab18a925c 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -317,7 +317,7 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio // If we are returning a non-nil error, then don't return a // non-empty EvalDelta. Not required for correctness. if err != nil && ad != nil { - ad.EvalDelta = basics.EvalDelta{} + ad.EvalDelta = transactions.EvalDelta{} } }() diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go index 2316448215..124a40936b 100644 --- a/ledger/apply/application_test.go +++ b/ledger/apply/application_test.go @@ -114,7 +114,7 @@ type testBalances struct { // logic evaluator control pass bool - delta basics.EvalDelta + delta transactions.EvalDelta err error } @@ -225,7 +225,7 @@ func (b *testBalances) DeallocateAsset(addr basics.Address, index basics.AssetIn return nil } -func (b *testBalances) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta basics.EvalDelta, err error) { +func (b *testBalances) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta transactions.EvalDelta, err error) { return b.pass, b.delta, b.err } @@ -258,7 +258,7 @@ func (b *testBalancesPass) Deallocate(addr basics.Address, aidx basics.AppIndex, return nil } -func (b *testBalancesPass) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta basics.EvalDelta, err error) { +func (b *testBalancesPass) StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta transactions.EvalDelta, err error) { return true, b.delta, nil } @@ -281,14 +281,14 @@ func (b *testBalances) SetParams(params config.ConsensusParams) { type testEvaluator struct { pass bool - delta basics.EvalDelta + delta transactions.EvalDelta appIdx basics.AppIndex } // Eval for tests that fail on program version > 10 and returns pass/delta from its own state rather than running the program -func (e *testEvaluator) Eval(program []byte) (pass bool, stateDelta basics.EvalDelta, err error) { +func (e *testEvaluator) Eval(program []byte) (pass bool, stateDelta transactions.EvalDelta, err error) { if len(program) < 1 || program[0] > 10 { - return false, basics.EvalDelta{}, fmt.Errorf("mock eval error") + return false, transactions.EvalDelta{}, fmt.Errorf("mock eval error") } return e.pass, e.delta, nil } @@ -604,7 +604,7 @@ func TestAppCallApplyCreateOptIn(t *testing.T) { b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} - b.delta = basics.EvalDelta{GlobalDelta: gd} + b.delta = transactions.EvalDelta{GlobalDelta: gd} err := ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) @@ -801,7 +801,7 @@ func TestAppCallClearState(t *testing.T) { br = b.putBalances[sender] a.Equal(0, len(br.AppLocalStates)) a.Equal(basics.StateSchema{}, br.TotalAppSchema) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) b.ResetWrites() @@ -820,7 +820,7 @@ func TestAppCallClearState(t *testing.T) { // one put: to opt out b.pass = false - b.delta = basics.EvalDelta{GlobalDelta: nil} + b.delta = transactions.EvalDelta{GlobalDelta: nil} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) a.Equal(1, b.put) @@ -834,7 +834,7 @@ func TestAppCallClearState(t *testing.T) { // check existing application with logic err ClearStateProgram. // one to opt out, one deallocate, no error from ApplicationCall b.pass = true - b.delta = basics.EvalDelta{GlobalDelta: nil} + b.delta = transactions.EvalDelta{GlobalDelta: nil} b.err = ledgercore.LogicEvalError{Err: fmt.Errorf("test error")} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) @@ -849,7 +849,7 @@ func TestAppCallClearState(t *testing.T) { // check existing application with non-logic err ClearStateProgram. // ApplicationCall must fail b.pass = true - b.delta = basics.EvalDelta{GlobalDelta: nil} + b.delta = transactions.EvalDelta{GlobalDelta: nil} b.err = fmt.Errorf("test error") err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.Error(err) @@ -865,23 +865,23 @@ func TestAppCallClearState(t *testing.T) { b.pass = true b.err = nil gd := basics.StateDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} - b.delta = basics.EvalDelta{GlobalDelta: gd} + b.delta = transactions.EvalDelta{GlobalDelta: gd} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) a.Equal(1, b.put) a.Equal(appIdx, b.deAllocatedAppIdx) a.Equal(0, len(br.AppLocalStates)) a.Equal(basics.StateSchema{}, br.TotalAppSchema) - a.Equal(basics.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) b.ResetWrites() b.pass = true b.err = nil - logs := []basics.LogItem{{ID: 0, Message: "a"}} - b.delta = basics.EvalDelta{Logs: []basics.LogItem{{ID: 0, Message: "a"}}} + logs := []string{"a"} + b.delta = transactions.EvalDelta{Logs: []string{"a"}} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) - a.Equal(basics.EvalDelta{Logs: logs}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) } func TestAppCallApplyCloseOut(t *testing.T) { @@ -932,7 +932,7 @@ func TestAppCallApplyCloseOut(t *testing.T) { a.Equal(0, b.put) br := b.balances[creator] a.Equal(cbr, br) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) // check closing on empty sender's balance record b.pass = true @@ -943,13 +943,13 @@ func TestAppCallApplyCloseOut(t *testing.T) { a.Equal(0, b.put) br = b.balances[creator] a.Equal(cbr, br) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) b.ResetWrites() // check a happy case gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} - b.delta = basics.EvalDelta{GlobalDelta: gd} + b.delta = transactions.EvalDelta{GlobalDelta: gd} b.balances[sender] = basics.AccountData{ AppLocalStates: map[basics.AppIndex]basics.AppLocalState{appIdx: {}}, } @@ -961,18 +961,18 @@ func TestAppCallApplyCloseOut(t *testing.T) { a.Equal(basics.TealKeyValue(nil), br.AppParams[appIdx].GlobalState) br = b.putBalances[sender] a.Equal(0, len(br.AppLocalStates)) - a.Equal(basics.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) a.Equal(basics.StateSchema{NumUint: 0}, br.TotalAppSchema) b.ResetWrites() - logs := []basics.LogItem{{ID: 0, Message: "a"}} - b.delta = basics.EvalDelta{Logs: []basics.LogItem{{ID: 0, Message: "a"}}} + logs := []string{"a"} + b.delta = transactions.EvalDelta{Logs: []string{"a"}} b.balances[sender] = basics.AccountData{ AppLocalStates: map[basics.AppIndex]basics.AppLocalState{appIdx: {}}, } err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) - a.Equal(basics.EvalDelta{Logs: logs}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) } func TestAppCallApplyUpdate(t *testing.T) { @@ -1025,7 +1025,7 @@ func TestAppCallApplyUpdate(t *testing.T) { a.Equal(0, b.put) br := b.balances[creator] a.Equal(cbr, br) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) // check updating on empty sender's balance record - happy case b.pass = true @@ -1038,7 +1038,7 @@ func TestAppCallApplyUpdate(t *testing.T) { br = b.putBalances[creator] a.Equal([]byte{2}, br.AppParams[appIdx].ApprovalProgram) a.Equal([]byte{2}, br.AppParams[appIdx].ClearStateProgram) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) //check program len check happens in future consensus proto version b.SetProto(protocol.ConsensusFuture) @@ -1067,11 +1067,11 @@ func TestAppCallApplyUpdate(t *testing.T) { b.balances[creator] = cp b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator} - logs := []basics.LogItem{{ID: 0, Message: "a"}} - b.delta = basics.EvalDelta{Logs: []basics.LogItem{{ID: 0, Message: "a"}}} + logs := []string{"a"} + b.delta = transactions.EvalDelta{Logs: []string{"a"}} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) - a.Equal(basics.EvalDelta{Logs: logs}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) // check extraProgramPages is used appr := make([]byte, 2*proto.MaxAppProgramLen+1) @@ -1180,7 +1180,7 @@ func TestAppCallApplyDelete(t *testing.T) { a.Equal(0, b.put) br := b.balances[creator] a.Equal(cbr, br) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) // check calculation on ConsensusV28. TotalExtraAppPages does not change b.SetProto(protocol.ConsensusV28) @@ -1198,7 +1198,7 @@ func TestAppCallApplyDelete(t *testing.T) { br = b.putBalances[creator] a.Equal(basics.AppParams{}, br.AppParams[appIdx]) a.Equal(basics.StateSchema{}, br.TotalAppSchema) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) a.Equal(uint32(1), br.TotalExtraAppPages) b.ResetWrites() @@ -1228,7 +1228,7 @@ func TestAppCallApplyDelete(t *testing.T) { br = b.putBalances[creator] a.Equal(basics.AppParams{}, br.AppParams[appIdx]) a.Equal(basics.StateSchema{}, br.TotalAppSchema) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) if initTotalExtraPages <= params.ExtraProgramPages { a.Equal(uint32(0), br.TotalExtraAppPages) } else { @@ -1236,11 +1236,11 @@ func TestAppCallApplyDelete(t *testing.T) { } b.ResetWrites() } - logs := []basics.LogItem{{ID: 0, Message: "a"}} - b.delta = basics.EvalDelta{Logs: []basics.LogItem{{ID: 0, Message: "a"}}} + logs := []string{"a"} + b.delta = transactions.EvalDelta{Logs: []string{"a"}} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) - a.Equal(basics.EvalDelta{Logs: logs}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) } func TestAppCallApplyCreateClearState(t *testing.T) { @@ -1276,14 +1276,14 @@ func TestAppCallApplyCreateClearState(t *testing.T) { b.pass = true gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} - b.delta = basics.EvalDelta{GlobalDelta: gd} + b.delta = transactions.EvalDelta{GlobalDelta: gd} // check creation on empty balance record err := ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.Error(err) a.Contains(err.Error(), "not currently opted in") a.Equal(appIdx, b.allocatedAppIdx) - a.Equal(basics.EvalDelta{}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{}, ad.EvalDelta) br := b.balances[creator] a.Equal([]byte{1}, br.AppParams[appIdx].ApprovalProgram) a.Equal([]byte{2}, br.AppParams[appIdx].ClearStateProgram) @@ -1326,20 +1326,20 @@ func TestAppCallApplyCreateDelete(t *testing.T) { b.pass = true gd := map[string]basics.ValueDelta{"uint": {Action: basics.SetUintAction, Uint: 1}} - b.delta = basics.EvalDelta{GlobalDelta: gd} + b.delta = transactions.EvalDelta{GlobalDelta: gd} // check creation on empty balance record err := ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) a.Equal(appIdx, b.allocatedAppIdx) - a.Equal(basics.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{GlobalDelta: gd}, ad.EvalDelta) br := b.balances[creator] a.Equal(basics.AppParams{}, br.AppParams[appIdx]) - logs := []basics.LogItem{{ID: 0, Message: "a"}} - b.delta = basics.EvalDelta{Logs: []basics.LogItem{{ID: 0, Message: "a"}}} + logs := []string{"a"} + b.delta = transactions.EvalDelta{Logs: []string{"a"}} err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter) a.NoError(err) - a.Equal(basics.EvalDelta{Logs: logs}, ad.EvalDelta) + a.Equal(transactions.EvalDelta{Logs: logs}, ad.EvalDelta) } diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go index 739d7803ca..988ae8cb90 100644 --- a/ledger/apply/apply.go +++ b/ledger/apply/apply.go @@ -19,6 +19,7 @@ package apply import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" ) @@ -51,9 +52,9 @@ type Balances interface { DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error // StatefulEval executes a TEAL program in stateful mode on the balances. - // It returns whether the program passed and its error. It alo returns + // It returns whether the program passed and its error. It also returns // an EvalDelta that contains the changes made by the program. - StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta basics.EvalDelta, err error) + StatefulEval(params logic.EvalParams, aidx basics.AppIndex, program []byte) (passed bool, evalDelta transactions.EvalDelta, err error) // Move MicroAlgos from one account to another, doing all necessary overflow checking (convenience method) // TODO: Does this need to be part of the balances interface, or can it just be implemented here as a function that calls Put and Get? diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go index 5a058b4866..ecb4e90727 100644 --- a/ledger/apply/keyreg_test.go +++ b/ledger/apply/keyreg_test.go @@ -78,8 +78,8 @@ func (balances keyregTestBalances) DeallocateAsset(addr basics.Address, index ba return nil } -func (balances keyregTestBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, basics.EvalDelta, error) { - return false, basics.EvalDelta{}, nil +func (balances keyregTestBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, transactions.EvalDelta, error) { + return false, transactions.EvalDelta{}, nil } func TestKeyregApply(t *testing.T) { diff --git a/ledger/apply/mockBalances_test.go b/ledger/apply/mockBalances_test.go index a1b64a2f24..d08e992e86 100644 --- a/ledger/apply/mockBalances_test.go +++ b/ledger/apply/mockBalances_test.go @@ -19,6 +19,7 @@ package apply import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/protocol" ) @@ -65,8 +66,8 @@ func (balances mockBalances) DeallocateAsset(addr basics.Address, index basics.A return nil } -func (balances mockBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, basics.EvalDelta, error) { - return false, basics.EvalDelta{}, nil +func (balances mockBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, transactions.EvalDelta, error) { + return false, transactions.EvalDelta{}, nil } func (balances mockBalances) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go new file mode 100644 index 0000000000..b93604ca46 --- /dev/null +++ b/ledger/apptxn_test.go @@ -0,0 +1,626 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "fmt" + "testing" + + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +// main wraps up some TEAL source in a header and footer so that it is +// an app that does nothing at create time, but otherwise runs source, +// then approves, if the source avoids panicing and leaves the stack +// empty. +func main(source string) string { + return fmt.Sprintf(`txn ApplicationID + bz end + %s + end: int 1`, source) +} + +// TestPayAction ensures a pay in teal affects balances +func TestPayAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + create := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Receiver + tx_submit +`), + } + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: basics.AppIndex(1).Address(), + Amount: 200000, // account min balance, plus fees + } + + payout1 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: basics.AppIndex(1), + Accounts: []basics.Address{addrs[1]}, // pay self + } + + eval := l.nextBlock(t) + eval.txns(t, &create, &fund, &payout1) + l.endBlock(t, eval) + + ad0 := l.micros(t, addrs[0]) + ad1 := l.micros(t, addrs[1]) + app := l.micros(t, basics.AppIndex(1).Address()) + + // create(1000) and fund(1000 + 200000) + require.Equal(t, uint64(202000), genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0) + // paid 5000, but 1000 fee + require.Equal(t, uint64(4000), ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw) + // app still has 194000 (paid out 5000, and paid fee to do it) + require.Equal(t, uint64(194000), app) + + // Build up Residue in RewardsState so it's ready to pay + for i := 1; i < 10; i++ { + eval = l.nextBlock(t) + l.endBlock(t, eval) + } + + eval = l.nextBlock(t) + payout2 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: basics.AppIndex(1), + Accounts: []basics.Address{addrs[2]}, // pay other + } + eval.txn(t, &payout2) + // confirm that modifiedAccounts can see account in inner txn + found := false + for _, addr := range eval.state.modifiedAccounts() { + if addr == addrs[2] { + found = true + } + } + require.True(t, found) + l.endBlock(t, eval) + + payInBlock := eval.block.Payset[0] + rewards := payInBlock.ApplyData.SenderRewards.Raw + require.Greater(t, rewards, uint64(2000)) // some biggish number + inners := payInBlock.ApplyData.EvalDelta.InnerTxns + require.Len(t, inners, 1) + + // addr[2] is going to get the same rewards as addr[1], who + // originally sent the top-level txn. Both had their algo balance + // touched and has very nearly the same balance. + require.Equal(t, rewards, inners[0].ReceiverRewards.Raw) + // app gets none, because it has less than 1A + require.Equal(t, uint64(0), inners[0].SenderRewards.Raw) + + ad1 = l.micros(t, addrs[1]) + ad2 := l.micros(t, addrs[2]) + app = l.micros(t, basics.AppIndex(1).Address()) + + // paid 5000, in first payout (only), but paid 1000 fee in each payout txn + require.Equal(t, rewards+3000, ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw) + // app still has 188000 (paid out 10000, and paid 2k fees to do it) + // no rewards because owns less than an algo + require.Equal(t, uint64(200000)-10000-2000, app) + + // paid 5000 by payout2, never paid any fees, got same rewards + require.Equal(t, rewards+uint64(5000), ad2-genBalances.Balances[addrs[2]].MicroAlgos.Raw) + + // Now fund the app account much more, so we can confirm it gets rewards. + tenkalgos := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: basics.AppIndex(1).Address(), + Amount: 10 * 1000 * 1000000, // account min balance, plus fees + } + eval = l.nextBlock(t) + eval.txn(t, &tenkalgos) + l.endBlock(t, eval) + beforepay := l.micros(t, basics.AppIndex(1).Address()) + + // Build up Residue in RewardsState so it's ready to pay again + for i := 1; i < 10; i++ { + eval = l.nextBlock(t) + l.endBlock(t, eval) + } + eval = l.nextBlock(t) + eval.txn(t, payout2.Noted("2")) + l.endBlock(t, eval) + + afterpay := l.micros(t, basics.AppIndex(1).Address()) + + payInBlock = eval.block.Payset[0] + inners = payInBlock.ApplyData.EvalDelta.InnerTxns + require.Len(t, inners, 1) + + appreward := inners[0].SenderRewards.Raw + require.Greater(t, appreward, uint64(1000)) + + require.Equal(t, beforepay+appreward-5000-1000, afterpay) +} + +// TestAxferAction ensures axfers in teal have the intended effects +func TestAxferAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + asa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 1000000, + Decimals: 3, + UnitName: "oz", + AssetName: "Gold", + URL: "https://gold.rush/", + }, + } + + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + int axfer + tx_field TypeEnum + txn Assets 0 + tx_field XferAsset + + txn ApplicationArgs 0 + byte "optin" + == + bz withdraw + // let AssetAmount default to 0 + global CurrentApplicationAddress + tx_field AssetReceiver + b submit +withdraw: + txn ApplicationArgs 0 + byte "close" + == + bz noclose + txn Accounts 1 + tx_field AssetCloseTo + b skipamount +noclose: int 10000 + tx_field AssetAmount +skipamount: + txn Accounts 1 + tx_field AssetReceiver +submit: tx_submit +`), + } + + eval := l.nextBlock(t) + eval.txns(t, &asa, &app) + l.endBlock(t, eval) + + // Would be better to pull these out of block, or at least check them. + asaIndex := basics.AssetIndex(1) + appIndex := basics.AppIndex(2) + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appIndex.Address(), + Amount: 300000, // account min balance, optin min balance, plus fees + // stay under 1M, to avoid rewards complications + } + + eval = l.nextBlock(t) + eval.txn(t, &fund) + l.endBlock(t, eval) + + fundgold := txntest.Txn{ + Type: "axfer", + Sender: addrs[0], + XferAsset: asaIndex, + AssetReceiver: appIndex.Address(), + AssetAmount: 20000, + } + + // Fail, because app account is not opted in. + eval = l.nextBlock(t) + eval.txn(t, &fundgold, fmt.Sprintf("asset %d missing", asaIndex)) + l.endBlock(t, eval) + + amount, in := l.asa(t, appIndex.Address(), asaIndex) + require.False(t, in) + require.Equal(t, amount, uint64(0)) + + optin := txntest.Txn{ + Type: "appl", + ApplicationID: appIndex, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("optin")}, + ForeignAssets: []basics.AssetIndex{asaIndex}, + } + + // Tell the app to opt itself in. + eval = l.nextBlock(t) + eval.txn(t, &optin) + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.True(t, in) + require.Equal(t, amount, uint64(0)) + + // Now, suceed, because opted in. + eval = l.nextBlock(t) + eval.txn(t, &fundgold) + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.True(t, in) + require.Equal(t, amount, uint64(20000)) + + withdraw := txntest.Txn{ + Type: "appl", + ApplicationID: appIndex, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("withdraw")}, + ForeignAssets: []basics.AssetIndex{asaIndex}, + Accounts: []basics.Address{addrs[0]}, + } + eval = l.nextBlock(t) + eval.txn(t, &withdraw) + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.True(t, in) + require.Equal(t, amount, uint64(10000)) + + eval = l.nextBlock(t) + eval.txn(t, withdraw.Noted("2")) + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.True(t, in) // Zero left, but still opted in + require.Equal(t, amount, uint64(0)) + + eval = l.nextBlock(t) + eval.txn(t, withdraw.Noted("3"), "underflow on subtracting") + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.True(t, in) // Zero left, but still opted in + require.Equal(t, amount, uint64(0)) + + close := txntest.Txn{ + Type: "appl", + ApplicationID: appIndex, + Sender: addrs[0], + ApplicationArgs: [][]byte{[]byte("close")}, + ForeignAssets: []basics.AssetIndex{asaIndex}, + Accounts: []basics.Address{addrs[0]}, + } + + eval = l.nextBlock(t) + eval.txn(t, &close) + l.endBlock(t, eval) + + amount, in = l.asa(t, appIndex.Address(), asaIndex) + require.False(t, in) // Zero left, not opted in + require.Equal(t, amount, uint64(0)) + + // Now, fail again, opted out + eval = l.nextBlock(t) + eval.txn(t, fundgold.Noted("2"), fmt.Sprintf("asset %d missing", asaIndex)) + l.endBlock(t, eval) + + // Do it all again, so we can test closeTo when we have a non-zero balance + // Tell the app to opt itself in. + eval = l.nextBlock(t) + eval.txns(t, optin.Noted("a"), fundgold.Noted("a")) + l.endBlock(t, eval) + + amount, _ = l.asa(t, appIndex.Address(), asaIndex) + require.Equal(t, uint64(20000), amount) + left, _ := l.asa(t, addrs[0], asaIndex) + + eval = l.nextBlock(t) + eval.txn(t, close.Noted("a")) + l.endBlock(t, eval) + + amount, _ = l.asa(t, appIndex.Address(), asaIndex) + require.Equal(t, uint64(0), amount) + back, _ := l.asa(t, addrs[0], asaIndex) + require.Equal(t, uint64(20000), back-left) +} + +// TestClawbackAction ensures an app address can act as clawback address. +func TestClawbackAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + // Would be better to pull these out of block, or at least check them. + asaIndex := basics.AssetIndex(1) + appIndex := basics.AppIndex(2) + + asa := txntest.Txn{ + Type: "acfg", + Sender: addrs[0], + AssetParams: basics.AssetParams{ + Total: 1000000, + Decimals: 3, + UnitName: "oz", + AssetName: "Gold", + URL: "https://gold.rush/", + Clawback: appIndex.Address(), + }, + } + + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + + int axfer + tx_field TypeEnum + + txn Assets 0 + tx_field XferAsset + + txn Accounts 1 + tx_field AssetSender + + txn Accounts 2 + tx_field AssetReceiver + + int 1000 + tx_field AssetAmount + + tx_submit +`), + } + + optin := txntest.Txn{ + Type: "axfer", + Sender: addrs[1], + AssetReceiver: addrs[1], + XferAsset: asaIndex, + } + eval := l.nextBlock(t) + eval.txns(t, &asa, &app, &optin) + l.endBlock(t, eval) + + bystander := addrs[2] // Has no authority of its own + overpay := txntest.Txn{ + Type: "pay", + Sender: bystander, + Receiver: bystander, + Fee: 2000, // Overpay fee so that app account can be unfunded + } + clawmove := txntest.Txn{ + Type: "appl", + Sender: bystander, + ApplicationID: appIndex, + ForeignAssets: []basics.AssetIndex{asaIndex}, + Accounts: []basics.Address{addrs[0], addrs[1]}, + } + eval = l.nextBlock(t) + eval.txgroup(t, &overpay, &clawmove) + l.endBlock(t, eval) + + amount, _ := l.asa(t, addrs[1], asaIndex) + require.Equal(t, amount, uint64(1000)) +} + +// TestRekeyAction ensures an app can transact for a rekeyed account +func TestRekeyAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + appIndex := basics.AppIndex(1) + ezpayer := txntest.Txn{ + Type: "appl", + Sender: addrs[5], + ApprovalProgram: main(` + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Sender + txn Accounts 2 + tx_field Receiver + txn NumAccounts + int 3 + == + bz skipclose + txn Accounts 3 + tx_field CloseRemainderTo +skipclose: + tx_submit +`), + } + + rekey := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: addrs[0], + RekeyTo: appIndex.Address(), + } + + eval := l.nextBlock(t) + eval.txns(t, &ezpayer, &rekey) + l.endBlock(t, eval) + + useacct := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appIndex, + Accounts: []basics.Address{addrs[0], addrs[2]}, // pay 2 from 0 (which was rekeyed) + } + eval = l.nextBlock(t) + eval.txn(t, &useacct) + l.endBlock(t, eval) + + // App was never funded (didn't spend from it's own acct) + require.Equal(t, uint64(0), l.micros(t, basics.AppIndex(1).Address())) + // addrs[2] got paid + require.Equal(t, uint64(5000), l.micros(t, addrs[2])-l.micros(t, addrs[6])) + // addrs[0] paid 5k + rekey fee + inner txn fee + require.Equal(t, uint64(7000), l.micros(t, addrs[6])-l.micros(t, addrs[0])) + + baduse := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appIndex, + Accounts: []basics.Address{addrs[2], addrs[0]}, // pay 0 from 2 + } + eval = l.nextBlock(t) + eval.txn(t, &baduse, "unauthorized") + l.endBlock(t, eval) + + // Now, we close addrs[0], which wipes its rekey status. Reopen + // it, and make sure the app can't spend. + + close := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appIndex, + Accounts: []basics.Address{addrs[0], addrs[2], addrs[3]}, // close to 3 + } + eval = l.nextBlock(t) + eval.txn(t, &close) + l.endBlock(t, eval) + + require.Equal(t, uint64(0), l.micros(t, addrs[0])) + + payback := txntest.Txn{ + Type: "pay", + Sender: addrs[3], + Receiver: addrs[0], + Amount: 10_000_000, + } + eval = l.nextBlock(t) + eval.txn(t, &payback) + l.endBlock(t, eval) + + require.Equal(t, uint64(10_000_000), l.micros(t, addrs[0])) + + eval = l.nextBlock(t) + eval.txn(t, useacct.Noted("2"), "unauthorized") + l.endBlock(t, eval) +} + +// TestRekeyActionCloseAccount ensures closing and reopening a rekeyed account in a single app call +// properly removes the app as an authorizer for the account +func TestRekeyActionCloseAccount(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + appIndex := basics.AppIndex(1) + create := txntest.Txn{ + Type: "appl", + Sender: addrs[5], + ApprovalProgram: main(` + // close account 1 + tx_begin + int pay + tx_field TypeEnum + txn Accounts 1 + tx_field Sender + txn Accounts 2 + tx_field CloseRemainderTo + tx_submit + + // reopen account 1 + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Receiver + tx_submit + // send from account 1 again (should fail because closing an account erases rekeying) + tx_begin + int pay + tx_field TypeEnum + int 1 + tx_field Amount + txn Accounts 1 + tx_field Sender + txn Accounts 2 + tx_field Receiver + tx_submit +`), + } + + rekey := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: addrs[0], + RekeyTo: appIndex.Address(), + } + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[1], + Receiver: appIndex.Address(), + Amount: 1_000_000, + } + + eval := l.nextBlock(t) + eval.txns(t, &create, &rekey, &fund) + l.endBlock(t, eval) + + useacct := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appIndex, + Accounts: []basics.Address{addrs[0], addrs[2]}, + } + eval = l.nextBlock(t) + eval.txn(t, &useacct, "unauthorized") + l.endBlock(t, eval) +} diff --git a/ledger/cow.go b/ledger/cow.go index 66c75f7f18..e618a59196 100644 --- a/ledger/cow.go +++ b/ledger/cow.go @@ -63,9 +63,6 @@ type roundCowState struct { // must be incorporated into mods.accts before passing deltas forward sdeltas map[basics.Address]map[storagePtr]*storageDelta - // logs populated in AppCall transaction - logs []basics.LogItem - // either or not maintain compatibility with original app refactoring behavior // this is needed for generating old eval delta in new code compatibilityMode bool @@ -86,7 +83,6 @@ func makeRoundCowState(b roundCowParent, hdr bookkeeping.BlockHeader, proto conf mods: ledgercore.MakeStateDelta(&hdr, prevTimestamp, hint, 0), sdeltas: make(map[basics.Address]map[storagePtr]*storageDelta), trackedCreatables: make(map[int]basics.CreatableIndex), - logs: make([]basics.LogItem, 0), } // compatibilityMode retains producing application' eval deltas under the following rule: diff --git a/ledger/eval.go b/ledger/eval.go index bc049b526d..15c0d04871 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -351,6 +351,7 @@ type BlockEvaluator struct { block bookkeeping.Block blockTxBytes int + specials transactions.SpecialAddresses blockGenerated bool // prevent repeated GenerateBlock calls @@ -414,10 +415,14 @@ func startEvaluator(l ledgerForEvaluator, hdr bookkeeping.BlockHeader, proto con } eval := &BlockEvaluator{ - validate: validate, - generate: generate, - prevHeader: prevHeader, - block: bookkeeping.Block{BlockHeader: hdr}, + validate: validate, + generate: generate, + prevHeader: prevHeader, + block: bookkeeping.Block{BlockHeader: hdr}, + specials: transactions.SpecialAddresses{ + FeeSink: hdr.FeeSink, + RewardsPool: hdr.RewardsPool, + }, proto: proto, genesisHash: l.GenesisHash(), l: l, @@ -630,12 +635,7 @@ func (eval *BlockEvaluator) testTransaction(txn transactions.SignedTxn, cow *rou return err } - // Well-formed on its own? - spec := transactions.SpecialAddresses{ - FeeSink: eval.block.BlockHeader.FeeSink, - RewardsPool: eval.block.BlockHeader.RewardsPool, - } - err = txn.Txn.WellFormed(spec, eval.proto) + err = txn.Txn.WellFormed(eval.specials, eval.proto) if err != nil { return fmt.Errorf("transaction %v: malformed: %v", txn.ID(), err) } @@ -671,12 +671,13 @@ func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithA // prepareEvalParams creates a logic.EvalParams for each ApplicationCall // transaction in the group -func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) (res []*logic.EvalParams) { +func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) []*logic.EvalParams { var groupNoAD []transactions.SignedTxn var pastSideEffects []logic.EvalSideEffects var minTealVersion uint64 pooledApplicationBudget := uint64(0) - res = make([]*logic.EvalParams, len(txgroup)) + var credit uint64 + res := make([]*logic.EvalParams, len(txgroup)) for i, txn := range txgroup { // Ignore any non-ApplicationCall transactions if txn.SignedTxn.Txn.Type != protocol.ApplicationCallTx { @@ -696,6 +697,8 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi } pastSideEffects = logic.MakePastSideEffects(len(txgroup)) minTealVersion = logic.ComputeMinTealVersion(groupNoAD) + credit, _ = transactions.FeeCredit(groupNoAD, eval.proto.MinTxnFee) + // intentionally ignoring error here, fees had to have been enough to get here } res[i] = &logic.EvalParams{ @@ -706,9 +709,11 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi PastSideEffects: pastSideEffects, MinTealVersion: &minTealVersion, PooledApplicationBudget: &pooledApplicationBudget, + FeeCredit: &credit, + Specials: &eval.specials, } } - return + return res } // transactionGroup tentatively executes a group of transactions as part of this block evaluation. @@ -729,8 +734,6 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit var groupTxBytes int cow := eval.state.child(len(txgroup)) - - // Prepare eval params for any ApplicationCall transactions in the group evalParams := eval.prepareEvalParams(txgroup) // Evaluate each transaction in the group @@ -863,13 +866,8 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } } - spec := transactions.SpecialAddresses{ - FeeSink: eval.block.BlockHeader.FeeSink, - RewardsPool: eval.block.BlockHeader.RewardsPool, - } - // Apply the transaction, updating the cow balances - applyData, err := eval.applyTransaction(txn.Txn, cow, evalParams, spec, cow.txnCounter()) + applyData, err := eval.applyTransaction(txn.Txn, cow, evalParams, cow.txnCounter()) if err != nil { return fmt.Errorf("transaction %v: %v", txid, err) } @@ -913,11 +911,11 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } // applyTransaction changes the balances according to this transaction. -func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balances *roundCowState, evalParams *logic.EvalParams, spec transactions.SpecialAddresses, ctr uint64) (ad transactions.ApplyData, err error) { +func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balances *roundCowState, evalParams *logic.EvalParams, ctr uint64) (ad transactions.ApplyData, err error) { params := balances.ConsensusParams() // move fee to pool - err = balances.Move(tx.Sender, spec.FeeSink, tx.Fee, &ad.SenderRewards, nil) + err = balances.Move(tx.Sender, eval.specials.FeeSink, tx.Fee, &ad.SenderRewards, nil) if err != nil { return } @@ -945,19 +943,19 @@ func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balanc switch tx.Type { case protocol.PaymentTx: - err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, spec, &ad) + err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, eval.specials, &ad) case protocol.KeyRegistrationTx: - err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, balances, spec, &ad, balances.round()) + err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, balances, eval.specials, &ad, balances.round()) case protocol.AssetConfigTx: - err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, balances, spec, &ad, ctr) + err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, balances, eval.specials, &ad, ctr) case protocol.AssetTransferTx: - err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, balances, spec, &ad) + err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, balances, eval.specials, &ad) case protocol.AssetFreezeTx: - err = apply.AssetFreeze(tx.AssetFreezeTxnFields, tx.Header, balances, spec, &ad) + err = apply.AssetFreeze(tx.AssetFreezeTxnFields, tx.Header, balances, eval.specials, &ad) case protocol.ApplicationCallTx: err = apply.ApplicationCall(tx.ApplicationCallTxnFields, tx.Header, balances, &ad, evalParams, ctr) diff --git a/ledger/eval_test.go b/ledger/eval_test.go index 9975ef0af7..b0dc43881a 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -74,6 +74,7 @@ func TestBlockEvaluator(t *testing.T) { newBlock := bookkeeping.MakeBlock(genesisInitState.Block.BlockHeader) eval, err := l.StartEvaluator(newBlock.BlockHeader, 0) + require.Equal(t, eval.specials.FeeSink, testSinkAddr) require.NoError(t, err) genHash := genesisInitState.Block.BlockHeader.GenesisHash @@ -355,34 +356,18 @@ func TestPrepareEvalParams(t *testing.T) { } // Create some sample transactions - payment := transactions.SignedTxnWithAD{ - SignedTxn: transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.PaymentTx, - Header: transactions.Header{ - Sender: basics.Address{1, 2, 3, 4}, - }, - PaymentTxnFields: transactions.PaymentTxnFields{ - Receiver: basics.Address{4, 3, 2, 1}, - Amount: basics.MicroAlgos{Raw: 100}, - }, - }, - }, - } - - appcall1 := transactions.SignedTxnWithAD{ - SignedTxn: transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.ApplicationCallTx, - Header: transactions.Header{ - Sender: basics.Address{1, 2, 3, 4}, - }, - ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - ApplicationID: basics.AppIndex(1), - }, - }, - }, - } + payment := txntest.Txn{ + Type: protocol.PaymentTx, + Sender: basics.Address{1, 2, 3, 4}, + Receiver: basics.Address{4, 3, 2, 1}, + Amount: 100, + }.SignedTxnWithAD() + + appcall1 := txntest.Txn{ + Type: protocol.ApplicationCallTx, + Sender: basics.Address{1, 2, 3, 4}, + ApplicationID: basics.AppIndex(1), + }.SignedTxnWithAD() appcall2 := appcall1 appcall2.SignedTxn.Txn.ApplicationCallTxnFields.ApplicationID = basics.AppIndex(2) @@ -538,14 +523,14 @@ ok: { SignedTxn: stxn1, ApplyData: transactions.ApplyData{ - EvalDelta: basics.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ + EvalDelta: transactions.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ "creator": {Action: basics.SetBytesAction, Bytes: string(addrs[0][:])}}, }}, }, { SignedTxn: stxn2, ApplyData: transactions.ApplyData{ - EvalDelta: basics.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ + EvalDelta: transactions.EvalDelta{GlobalDelta: map[string]basics.ValueDelta{ "caller": {Action: basics.SetBytesAction, Bytes: string(addrs[0][:])}}, }}, }, @@ -585,86 +570,34 @@ func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["creator"]) } -func testEvalAppPoolingGroup(t *testing.T, schema basics.StateSchema, approvalProgram string, consensusVersion protocol.ConsensusVersion) (*BlockEvaluator, error) { - genesisInitState, addrs, keys := genesis(10) - - dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - const inMem = true - cfg := config.GetDefaultLocal() - l, err := OpenLedger(logging.Base(), dbName, inMem, genesisInitState, cfg) - require.NoError(t, err) +func testEvalAppPoolingGroup(t *testing.T, schema basics.StateSchema, approvalProgram string, consensusVersion protocol.ConsensusVersion) error { + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) defer l.Close() - newBlock := bookkeeping.MakeBlock(genesisInitState.Block.BlockHeader) - eval, err := l.StartEvaluator(newBlock.BlockHeader, 0) - require.NoError(t, err) - eval.validate = true - eval.generate = false + eval := l.nextBlock(t) eval.proto = config.Consensus[consensusVersion] - ops, err := logic.AssembleString(approvalProgram) - require.NoError(t, err, ops.Errors) - approval := ops.Program - ops, err = logic.AssembleString("#pragma version 4\nint 1") - require.NoError(t, err) - clear := ops.Program - - genHash := genesisInitState.Block.BlockHeader.GenesisHash - header := transactions.Header{ - Sender: addrs[0], - Fee: minFee, - FirstValid: newBlock.Round(), - LastValid: newBlock.Round(), - GenesisHash: genHash, - } - appcall1 := transactions.Transaction{ - Type: protocol.ApplicationCallTx, - Header: header, - ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - GlobalStateSchema: schema, - ApprovalProgram: approval, - ClearStateProgram: clear, - }, + appcall1 := txntest.Txn{ + Sender: addrs[0], + Type: protocol.ApplicationCallTx, + GlobalStateSchema: schema, + ApprovalProgram: approvalProgram, } - appcall2 := transactions.Transaction{ - Type: protocol.ApplicationCallTx, - Header: header, - ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{ - ApplicationID: basics.AppIndex(1), - }, + appcall2 := txntest.Txn{ + Sender: addrs[0], + Type: protocol.ApplicationCallTx, + ApplicationID: basics.AppIndex(1), } - appcall3 := appcall2 - appcall3.Header.Sender = addrs[1] - - var group transactions.TxGroup - group.TxGroupHashes = []crypto.Digest{crypto.HashObj(appcall1), crypto.HashObj(appcall2), crypto.HashObj(appcall3)} - appcall1.Group = crypto.HashObj(group) - appcall2.Group = crypto.HashObj(group) - appcall3.Group = crypto.HashObj(group) - stxn1 := appcall1.Sign(keys[0]) - stxn2 := appcall2.Sign(keys[0]) - stxn3 := appcall3.Sign(keys[1]) - - g := []transactions.SignedTxnWithAD{ - { - SignedTxn: stxn1, - }, - { - SignedTxn: stxn2, - }, - { - SignedTxn: stxn3, - }, - } - txgroup := []transactions.SignedTxn{stxn1, stxn2, stxn3} - err = eval.TestTransactionGroup(txgroup) - if err != nil { - return eval, err + appcall3 := txntest.Txn{ + Sender: addrs[1], + Type: protocol.ApplicationCallTx, + ApplicationID: basics.AppIndex(1), } - err = eval.transactionGroup(g) - return eval, err + + return eval.txgroup(t, &appcall1, &appcall2, &appcall3) } // TestEvalAppPooledBudgetWithTxnGroup ensures 3 app call txns can successfully pool @@ -706,7 +639,7 @@ func TestEvalAppPooledBudgetWithTxnGroup(t *testing.T) { for i, param := range params { for j, testCase := range cases { t.Run(fmt.Sprintf("i=%d,j=%d", i, j), func(t *testing.T) { - _, err := testEvalAppPoolingGroup(t, basics.StateSchema{NumByteSlice: 3}, testCase.prog, param) + err := testEvalAppPoolingGroup(t, basics.StateSchema{NumByteSlice: 3}, testCase.prog, param) if !testCase.isSuccessV29 && reflect.DeepEqual(param, protocol.ConsensusV29) { require.Error(t, err) require.Contains(t, err.Error(), testCase.expectedErrorV29) @@ -1303,7 +1236,7 @@ func TestModifiedAssetHoldings(t *testing.T) { createTxn := txntest.Txn{ Type: "acfg", Sender: addrs[0], - Fee: basics.MicroAlgos{Raw: 2000}, + Fee: 2000, AssetParams: basics.AssetParams{ Total: 3, Decimals: 0, @@ -1317,7 +1250,7 @@ func TestModifiedAssetHoldings(t *testing.T) { optInTxn := txntest.Txn{ Type: "axfer", Sender: addrs[1], - Fee: basics.MicroAlgos{Raw: 2000}, + Fee: 2000, XferAsset: assetid, AssetAmount: 0, AssetReceiver: addrs[1], @@ -1349,7 +1282,7 @@ func TestModifiedAssetHoldings(t *testing.T) { optOutTxn := txntest.Txn{ Type: "axfer", Sender: addrs[1], - Fee: basics.MicroAlgos{Raw: 1000}, + Fee: 1000, XferAsset: assetid, AssetReceiver: addrs[0], AssetCloseTo: addrs[0], @@ -1358,7 +1291,7 @@ func TestModifiedAssetHoldings(t *testing.T) { closeTxn := txntest.Txn{ Type: "acfg", Sender: addrs[0], - Fee: basics.MicroAlgos{Raw: 1000}, + Fee: 1000, ConfigAsset: assetid, } @@ -1427,7 +1360,9 @@ func newTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto. Status: basics.NotParticipating, } - accts[rewards] = basics.AccountData{MicroAlgos: basics.MicroAlgos{Raw: amount}} + accts[rewards] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + } genBalances := bookkeeping.MakeGenesisBalances(accts, sink, rewards) @@ -1450,7 +1385,8 @@ func newTestLedgerImpl(t testing.TB, balances bookkeeping.GenesisBalances, inMem crypto.RandBytes(genHash[:]) genBlock, err := bookkeeping.MakeGenesisBlock(protocol.ConsensusFuture, balances, "test", genHash) - + require.False(t, genBlock.FeeSink.IsZero()) + require.False(t, genBlock.RewardsPool.IsZero()) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) cfg := config.GetDefaultLocal() cfg.Archival = true @@ -1468,8 +1404,9 @@ func (ledger *Ledger) nextBlock(t testing.TB) *BlockEvaluator { rnd := ledger.Latest() hdr, err := ledger.BlockHdr(rnd) require.NoError(t, err) - eval, err := startEvaluator(ledger, bookkeeping.MakeBlock(hdr).BlockHeader, - config.Consensus[hdr.CurrentProtocol], 0, false, true) + + nextHdr := bookkeeping.MakeBlock(hdr).BlockHeader + eval, err := ledger.StartEvaluator(nextHdr, 0) require.NoError(t, err) return eval } @@ -1491,7 +1428,20 @@ func (ledger *Ledger) lookup(t testing.TB, addr basics.Address) basics.AccountDa return ad } -func (eval *BlockEvaluator) txn(t testing.TB, txn *txntest.Txn) { +// micros gets the current microAlgo balance for an address +func (ledger *Ledger) micros(t testing.TB, addr basics.Address) uint64 { + return ledger.lookup(t, addr).MicroAlgos.Raw +} + +// asa gets the current balance and optin status for some asa for an address +func (ledger *Ledger) asa(t testing.TB, addr basics.Address, asset basics.AssetIndex) (uint64, bool) { + if holding, ok := ledger.lookup(t, addr).Assets[asset]; ok { + return holding.Amount, true + } + return 0, false +} + +func (eval *BlockEvaluator) fillDefaults(txn *txntest.Txn) { if txn.GenesisHash.IsZero() { txn.GenesisHash = eval.genesisHash } @@ -1499,19 +1449,56 @@ func (eval *BlockEvaluator) txn(t testing.TB, txn *txntest.Txn) { txn.FirstValid = eval.Round() } txn.FillDefaults(eval.proto) +} + +func (eval *BlockEvaluator) txn(t testing.TB, txn *txntest.Txn, problem ...string) { + t.Helper() + eval.fillDefaults(txn) stxn := txn.SignedTxn() err := eval.testTransaction(stxn, eval.state.child(1)) - require.NoError(t, err) + if err != nil { + if len(problem) == 1 { + require.Contains(t, err.Error(), problem[0]) + } else { + require.NoError(t, err) // Will obviously fail + } + return + } err = eval.Transaction(stxn, transactions.ApplyData{}) - require.NoError(t, err) + if err != nil { + if len(problem) == 1 { + require.Contains(t, err.Error(), problem[0]) + } else { + require.NoError(t, err) // Will obviously fail + } + return + } + require.Len(t, problem, 0) } func (eval *BlockEvaluator) txns(t testing.TB, txns ...*txntest.Txn) { + t.Helper() for _, txn := range txns { eval.txn(t, txn) } } +func (eval *BlockEvaluator) txgroup(t testing.TB, txns ...*txntest.Txn) error { + t.Helper() + for _, txn := range txns { + eval.fillDefaults(txn) + } + txgroup := txntest.SignedTxns(txns...) + + err := eval.TestTransactionGroup(txgroup) + if err != nil { + return err + } + + err = eval.transactionGroup(transactions.WrapSignedTxnsWithAD(txgroup)) + return err +} + func TestRewardsInAD(t *testing.T) { partitiontest.PartitionTest(t) @@ -1616,10 +1603,9 @@ func TestModifiedAppLocalStates(t *testing.T) { const appid basics.AppIndex = 1 createTxn := txntest.Txn{ - Type: "appl", - Sender: addrs[0], - ApprovalProgram: []byte{0x02, 0x20, 0x01, 0x01, 0x22}, - ClearStateProgram: []byte{0x02, 0x20, 0x01, 0x01, 0x22}, + Type: "appl", + Sender: addrs[0], + ApprovalProgram: "int 1", } optInTxn := txntest.Txn{ @@ -1797,12 +1783,11 @@ func TestAppInsMinBalance(t *testing.T) { for i := 0; i < maxAppsOptedIn; i++ { creator := addrs[acctIdx] createTxn := txntest.Txn{ - Type: protocol.ApplicationCallTx, - Sender: creator, - ApprovalProgram: []byte{0x02, 0x20, 0x01, 0x01, 0x22}, - ClearStateProgram: []byte{0x02, 0x20, 0x01, 0x01, 0x22}, - LocalStateSchema: basics.StateSchema{NumByteSlice: maxLocalSchemaEntries}, - Note: randomNote(), + Type: protocol.ApplicationCallTx, + Sender: creator, + ApprovalProgram: "int 1", + LocalStateSchema: basics.StateSchema{NumByteSlice: maxLocalSchemaEntries}, + Note: randomNote(), } txnsCreate = append(txnsCreate, &createTxn) count := appsCreated[creator] diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 432e118486..ba2ac5a39b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -866,7 +866,7 @@ int 1 ApplicationCallTxnFields: appcreateFields, } - ad := transactions.ApplyData{EvalDelta: basics.EvalDelta{GlobalDelta: basics.StateDelta{ + ad := transactions.ApplyData{EvalDelta: transactions.EvalDelta{GlobalDelta: basics.StateDelta{ "counter": basics.ValueDelta{Action: basics.SetUintAction, Uint: 1}, }}} a.NoError(l.appendUnvalidatedTx(t, initAccounts, initSecrets, appcreate, ad)) @@ -891,7 +891,7 @@ int 1 ApplicationCallTxnFields: appcallFields, } appcall.ApplicationID = appIdx - ad = transactions.ApplyData{EvalDelta: basics.EvalDelta{ + ad = transactions.ApplyData{EvalDelta: transactions.EvalDelta{ GlobalDelta: basics.StateDelta{ "counter": basics.ValueDelta{Action: basics.SetUintAction, Uint: 2}, }, @@ -993,7 +993,7 @@ int 1 // [1] ApplicationCallTxnFields: appcreateFields, } - ad := transactions.ApplyData{EvalDelta: basics.EvalDelta{GlobalDelta: basics.StateDelta{ + ad := transactions.ApplyData{EvalDelta: transactions.EvalDelta{GlobalDelta: basics.StateDelta{ "key": basics.ValueDelta{Action: basics.SetUintAction, Uint: uint64(value)}, }}} @@ -1032,7 +1032,7 @@ int 1 // [1] Header: correctTxHeader, ApplicationCallTxnFields: appcallFields1, } - ad1 := transactions.ApplyData{EvalDelta: basics.EvalDelta{GlobalDelta: basics.StateDelta{ + ad1 := transactions.ApplyData{EvalDelta: transactions.EvalDelta{GlobalDelta: basics.StateDelta{ "key": basics.ValueDelta{Action: basics.SetUintAction, Uint: uint64(base + value1)}, }}} @@ -1048,7 +1048,7 @@ int 1 // [1] Header: correctTxHeader, ApplicationCallTxnFields: appcallFields2, } - ad2 := transactions.ApplyData{EvalDelta: basics.EvalDelta{GlobalDelta: basics.StateDelta{ + ad2 := transactions.ApplyData{EvalDelta: transactions.EvalDelta{GlobalDelta: basics.StateDelta{ "key": basics.ValueDelta{Action: basics.SetUintAction, Uint: uint64(base + value1 + value2)}, }}} diff --git a/protocol/codec_tester.go b/protocol/codec_tester.go index 883b3028fd..9fcbf7c35b 100644 --- a/protocol/codec_tester.go +++ b/protocol/codec_tester.go @@ -203,6 +203,14 @@ func randomizeValue(v reflect.Value, datapath string, tag string) error { return nil } + /* Consider cutting off recursive structures by stopping at some datapath depth. + + if len(datapath) > 200 { + // Cut off recursive structures + return nil + } + */ + switch v.Kind() { case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v.SetUint(rand.Uint64()) diff --git a/protocol/hash.go b/protocol/hash.go index 808a7ead2c..846b03c279 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -23,6 +23,7 @@ type HashID string // Hash IDs for specific object types, in lexicographic order. // Hash IDs must be PREFIX-FREE (no hash ID is a prefix of another). const ( + AppIndex HashID = "appID" AuctionBid HashID = "aB" AuctionDeposit HashID = "aD" AuctionOutcomes HashID = "aO" diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index 5ad255790f..ab160d96e2 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -32,12 +32,12 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func checkEqual(expected []basics.LogItem, actual []basics.LogItem) bool { +func checkEqual(expected []string, actual []string) bool { if len(expected) != len(actual) { return false } for i, e := range expected { - if !e.Equal(actual[i]) { + if e != actual[i] { return false } } @@ -113,12 +113,12 @@ log round, err = client.CurrentRound() a.NoError(err) - logs := make([]basics.LogItem, 32) + logs := make([]string, 32) for i := range logs { - logs[i] = basics.LogItem{ID: 0, Message: "a"} + logs[i] = "a" } - logs[30] = basics.LogItem{ID: 0, Message: "b"} - logs[31] = basics.LogItem{ID: 0, Message: "c"} + logs[30] = "b" + logs[31] = "c" b, err := client.BookkeepingBlock(round) for _, ps := range b.Payset { diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 69db074c82..c49303cd4a 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -990,12 +990,10 @@ return a.NotNil(txn.Logs) a.Equal(32, len(*txn.Logs)) for i, l := range *txn.Logs { - a.Equal(*txn.ApplicationIndex, l.Id) - assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), l.Value) + assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), l) } //check non-create app call - expectedAppID := *txn.ApplicationIndex wh, err = testClient.GetUnencryptedWalletHandle() a.NoError(err) addresses, err = testClient.ListAddresses(wh) @@ -1029,8 +1027,7 @@ return a.NotNil(txn.Logs) a.Equal(32, len(*txn.Logs)) for i, l := range *txn.Logs { - a.Equal(expectedAppID, l.Id) - assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), l.Value) + assert.Equal(t, base64.StdEncoding.EncodeToString([]byte(string(rune('B'+i)))), l) } } diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py index c225b9483d..79b53a291b 100755 --- a/test/scripts/e2e_client_runner.py +++ b/test/scripts/e2e_client_runner.py @@ -402,6 +402,7 @@ def xrun(cmd, *args, **kwargs): def main(): start = time.time() ap = argparse.ArgumentParser() + ap.add_argument('--interactive', default=False, action='store_true') ap.add_argument('scripts', nargs='*', help='scripts to run') ap.add_argument('--keep-temps', default=False, action='store_true', help='if set, keep all the test files') ap.add_argument('--timeout', default=500, type=int, help='integer seconds to wait for the scripts to run') @@ -447,13 +448,12 @@ def main(): create_kmd_config_with_unsafe_scrypt(env['ALGORAND_DATA']) create_kmd_config_with_unsafe_scrypt(env['ALGORAND_DATA2']) - xrun(['goal', '-v'], env=env, timeout=5) xrun(['goal', 'node', 'status'], env=env, timeout=5) rs = RunSet(env) for scriptname in args.scripts: - rs.start(scriptname, args.timeout-10) + rs.start(os.path.abspath(scriptname), args.timeout-10) rs.wait(args.timeout) if rs.errors: retcode = 1 @@ -462,6 +462,10 @@ def main(): logger.info('finished OK %f seconds', time.time() - start) logger.info('statuses-json: %s', json.dumps(rs.statuses)) + if args.interactive: + os.environ['ALGORAND_DATA'] = env['ALGORAND_DATA'] + os.system(os.environ['SHELL']) + # ensure 'network stop' and 'network delete' also make they job goal_network_stop(netdir, env, normal_cleanup=True) if not args.keep_temps: diff --git a/test/scripts/e2e_subs/app-accounts.sh b/test/scripts/e2e_subs/app-accounts.sh new file mode 100755 index 0000000000..fe0078597d --- /dev/null +++ b/test/scripts/e2e_subs/app-accounts.sh @@ -0,0 +1,118 @@ +#!/bin/bash + +filename=$(basename "$0") +scriptname="${filename%.*}" +date "+${scriptname} start %Y%m%d_%H%M%S" + + +my_dir="$(dirname "$0")" +source "$my_dir/rest.sh" "$@" +function rest() { + curl -q -s -H "Authorization: Bearer $PUB_TOKEN" "$NET$1" +} + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +TEAL=test/scripts/e2e_subs/tealprogs + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') +# Create a smaller account so rewards won't change balances. +SMALL=$(${gcmd} account new | awk '{ print $6 }') +# Under one algo receives no rewards +${gcmd} clerk send -a 1000000 -f "$ACCOUNT" -t "$SMALL" + +function balance { + acct=$1; shift + goal account balance -a "$acct" | awk '{print $1}' +} + +[ "$(balance "$ACCOUNT")" = 999998999000 ] +[ "$(balance "$SMALL")" = 1000000 ] + +APPID=$(${gcmd} app create --creator "${SMALL}" --approval-prog=${TEAL}/app-escrow.teal --global-byteslices 4 --global-ints 0 --local-byteslices 0 --local-ints 1 --clear-prog=${TEAL}/approve-all.teal | grep Created | awk '{ print $6 }') +[ "$(balance "$SMALL")" = 999000 ] # 1000 fee + +function appl { + method=$1; shift + ${gcmd} app call --app-id="$APPID" --from="$SMALL" --app-arg="str:$method" "$@" +} +function app-txid { + # When app (call or optin) submits, this is how the txid is + # printed. Not in appl() because appl is also used with -o to + # create tx + grep -o -E 'txid [A-Z0-9]{52}' | cut -c 6- | head -1 +} + +APPACCT=$(python -c "import algosdk.encoding as e; print(e.encode_address(e.checksum(b'appID'+($APPID).to_bytes(8, 'big'))))") + +function payin { + amount=$1; shift + ${gcmd} clerk send -f "$SMALL" -t "$APPACCT" -a "$amount" "$@" +} + +T=$TEMPDIR + +function sign { + ${gcmd} clerk sign -i "$T/$1.tx" -o "$T/$1.stx" +} + +TXID=$(${gcmd} app optin --app-id "$APPID" --from "${SMALL}" | app-txid) +# Rest succeeds, no stray inner-txn array +[ "$(rest "/v2/transactions/pending/$TXID" | jq '.["inner-txn"]')" == null ] +[ "$(balance "$SMALL")" = 998000 ] # 1000 fee + +appl "deposit():void" -o "$T/deposit.tx" +payin 150000 -o "$T/pay1.tx" +cat "$T/deposit.tx" "$T/pay1.tx" | ${gcmd} clerk group -i - -o "$T/group.tx" +sign group +${gcmd} clerk rawsend -f "$T/group.stx" +[ "$(balance "$SMALL")" = 846000 ] # 2 fees, 150,000 deposited +[ "$(balance "$APPACCT")" = 150000 ] + +# Withdraw 20,000 in app. Confirm that inner txn is visible to transaction API. +TXID=$(appl "withdraw(uint64):void" --app-arg="int:20000" | app-txid) +[ "$(rest "/v2/transactions/pending/$TXID" \ + | jq '.["inner-txns"][0].txn.txn.amt')" = 20000 ] +[ "$(rest "/v2/transactions/pending/$TXID?format=msgpack" | msgpacktool -d \ + | jq '.["inner-txns"][0].txn.txn.type')" = '"pay"' ] +# Now confirm it's in blocks API (this time in our internal form) +ROUND=$(rest "/v2/transactions/pending/$TXID" | jq '.["confirmed-round"]') +rest "/v2/blocks/$ROUND" | jq .block.txns[0].dt.itx + +[ "$(balance "$SMALL")" = 865000 ] # 1 fee, 20,000 withdrawn +[ "$(balance "$APPACCT")" = 129000 ] # 20k withdraw, fee paid by app account + +appl "withdraw(uint64):void" --app-arg="int:10000" --fee 2000 +[ "$(balance "$SMALL")" = 873000 ] # 2000 fee, 10k withdrawn +[ "$(balance "$APPACCT")" = 119000 ] # 10k withdraw, fee credit used + +# Try to get app account below zero +# (By app logic, it's OK - 150k was deposited, but fees have cut in) +appl "withdraw(uint64):void" --app-arg="int:120000" && exit 1 +[ "$(balance "$SMALL")" = 873000 ] # no change +[ "$(balance "$APPACCT")" = 119000 ] # no change + +# Try to get app account below min balance by withdrawing too much +appl "withdraw(uint64):void" --app-arg="int:20000" && exit 1 +[ "$(balance "$SMALL")" = 873000 ] # no change +[ "$(balance "$APPACCT")" = 119000 ] # no change + +# Try to get app account below min balance b/c of fee +appl "withdraw(uint64):void" --app-arg="int:18001" && exit 1 +[ "$(balance "$SMALL")" = 873000 ] # no change +[ "$(balance "$APPACCT")" = 119000 ] # no change + +# Show that it works AT exactly min balance +appl "withdraw(uint64):void" --app-arg="int:18000" +[ "$(balance "$SMALL")" = 890000 ] # +17k (18k - fee) +[ "$(balance "$APPACCT")" = 100000 ] # -19k (18k + fee) + + +date "+${scriptname} OK %Y%m%d_%H%M%S" diff --git a/test/scripts/e2e_subs/tealprogs/app-escrow.teal b/test/scripts/e2e_subs/tealprogs/app-escrow.teal new file mode 100644 index 0000000000..315f397db6 --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/app-escrow.teal @@ -0,0 +1,166 @@ +#pragma version 5 + // This application accepts payments in algo, and holds them + // until the requester asks for them back. Depositors invoke + // the app with "deposit" as arg[0], and make a pay + // transaction to the app account in the following txn slot. + // The app records the deposit in the user's local state (so + // the user must be opted-in), though they may do so in their + // initial call. + + // To withdraw, users call with "withdraw" in arg[0], and a + // big-endian integer amount in arg[1]. If the withdrawal + // amount is less than the amount deposited, the app pays out + // the request, and decrements the user's balance. + + // ApplicationID is zero in inital creation txn + txn ApplicationID + bz handle_createapp + + // Handle possible OnCompletion type. We don't have to + // worry about handling ClearState, because the + // ClearStateProgram will execute in that case, not the + // ApprovalProgram. + + txn OnCompletion + int NoOp + == + bnz handle_noop + + txn OnCompletion + int OptIn + == + bnz handle_optin + + txn OnCompletion + int CloseOut + == + bnz handle_closeout + + txn OnCompletion + int UpdateApplication + == + bnz handle_updateapp + + txn OnCompletion + int DeleteApplication + == + bnz handle_deleteapp + // Unexpected OnCompletion value. Should be unreachable. + err + +handle_createapp: + int 1 + return + +handle_optin: + // Let anyone optin with a single txn, with no arguments. If + // it's not a single txn, fall through to handle_noop, so that + // a deposit can be made while opting in. + // We should standardize a behaviour like this in ABI. + global GroupSize + int 1 + == + bz handle_noop + int 1 + return + + +handle_noop: + txn ApplicationArgs 0 + byte "deposit():void" + == + bz not_deposit + + + byte "deposit" + callsub debug + + // Handle a deposit. Next txn slot must pay our app account + txn GroupIndex + int 1 + + + dup + dup + + gtxns TypeEnum + int pay // axfer if we want an ASA escrower + == + assert + + gtxns Receiver + global CurrentApplicationAddress + == + assert + + gtxns Amount // For ASA escrow, use AssetAmount + + + // Track the amount this sender deposited in their local state + int 0 + byte "balance" + dup2 + app_local_get + uncover 3 // pull up the Amount + + + app_local_put + + int 1 + return + +not_deposit: + txn ApplicationArgs 0 + byte "withdraw(uint64):void" + == + bz not_withdraw + + // Handle withdraw. + + int 0 + byte "balance" + dup2 + app_local_get + + // Subtract the request and replace. Rejects on underflow + txn ApplicationArgs 1 + btoi + - + app_local_put + + tx_begin + int pay + tx_field TypeEnum + + txn ApplicationArgs 1 + btoi + tx_field Amount + + txn Sender + tx_field Receiver + tx_submit + + int 1 + return +not_withdraw: + // Unknown call "method" + err + +handle_closeout: + int 1 + return + +handle_updateapp: +handle_deleteapp: + txn Sender + global CreatorAddress + == + return + + +bad: + err + +debug: + byte "debug" + swap + app_global_put + retsub