diff --git a/cmd/serve.go b/cmd/serve.go
index 5c6f30ff7..26cd9cce2 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -1,6 +1,10 @@
package cmd
import (
+ "net/http"
+ "net/http/pprof"
+ "time"
+
apilib "github.com/formancehq/go-libs/v2/api"
"github.com/formancehq/go-libs/v2/health"
"github.com/formancehq/go-libs/v2/httpserver"
@@ -8,9 +12,8 @@ import (
"github.com/formancehq/ledger/internal/storage/driver"
"github.com/go-chi/chi/v5"
"go.opentelemetry.io/otel/sdk/metric"
- "net/http"
- "net/http/pprof"
- "time"
+
+ "github.com/formancehq/ledger/internal/bus"
"github.com/formancehq/go-libs/v2/auth"
"github.com/formancehq/go-libs/v2/aws/iam"
@@ -19,7 +22,7 @@ import (
"github.com/formancehq/go-libs/v2/otlp/otlptraces"
"github.com/formancehq/go-libs/v2/publish"
"github.com/formancehq/ledger/internal/api"
- "github.com/formancehq/ledger/internal/bus"
+
ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger"
systemcontroller "github.com/formancehq/ledger/internal/controller/system"
"github.com/formancehq/ledger/internal/storage"
@@ -37,6 +40,7 @@ const (
AutoUpgradeFlag = "auto-upgrade"
ExperimentalFeaturesFlag = "experimental-features"
BulkMaxSizeFlag = "bulk-max-size"
+ NumscriptInterpreterFlag = "experimental-numscript-interpreter"
)
func NewServeCommand() *cobra.Command {
@@ -55,6 +59,7 @@ func NewServeCommand() *cobra.Command {
if err != nil {
return err
}
+ numscriptInterpreter, _ := cmd.Flags().GetBool(NumscriptInterpreterFlag)
bulkMaxSize, err := cmd.Flags().GetInt(BulkMaxSizeFlag)
if err != nil {
@@ -71,6 +76,7 @@ func NewServeCommand() *cobra.Command {
bunconnect.Module(*connectionOptions, service.IsDebug(cmd)),
storage.NewFXModule(serveConfiguration.autoUpgrade),
systemcontroller.NewFXModule(systemcontroller.ModuleConfiguration{
+ NumscriptInterpreter: numscriptInterpreter,
NSCacheConfiguration: ledgercontroller.CacheConfiguration{
MaxCount: serveConfiguration.numscriptCacheMaxCount,
},
@@ -120,6 +126,7 @@ func NewServeCommand() *cobra.Command {
cmd.Flags().String(BindFlag, "0.0.0.0:3068", "API bind address")
cmd.Flags().Bool(ExperimentalFeaturesFlag, false, "Enable features configurability")
cmd.Flags().Int(BulkMaxSizeFlag, api.DefaultBulkMaxSize, "Bulk max size (default 100)")
+ cmd.Flags().Bool(NumscriptInterpreterFlag, false, "Enable experimental numscript rewrite")
service.AddFlags(cmd.Flags())
bunconnect.AddFlags(cmd.Flags())
diff --git a/docs/api/README.md b/docs/api/README.md
index 7cd5c29c8..063650048 100644
--- a/docs/api/README.md
+++ b/docs/api/README.md
@@ -3189,6 +3189,8 @@ Authorization ( Scopes: ledger:write )
|*anonymous*|IMPORT|
|*anonymous*|TIMEOUT|
|*anonymous*|BULK_SIZE_EXCEEDED|
+|*anonymous*|INTERPRETER_PARSE|
+|*anonymous*|INTERPRETER_RUNTIME|
V2LedgerInfoResponse
diff --git a/go.mod b/go.mod
index fdcc389b5..7b9548711 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/formancehq/ledger
-go 1.22.0
+go 1.22.1
toolchain go1.22.7
@@ -14,12 +14,14 @@ require (
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10
github.com/bluele/gcache v0.0.2
github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63
+ github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417
github.com/formancehq/stack/ledger/client v0.0.0-00010101000000-000000000000
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/invopop/jsonschema v0.12.0
+ github.com/jackc/pgx/v5 v5.7.1
github.com/jamiealquiza/tachymeter v2.0.0+incompatible
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/nats-io/nats.go v1.37.0
@@ -27,6 +29,7 @@ require (
github.com/onsi/gomega v1.34.2
github.com/ory/dockertest/v3 v3.11.0
github.com/pborman/uuid v1.2.1
+ github.com/pkg/errors v0.9.1
github.com/shomali11/xsql v0.0.0-20190608141458-bf76292144df
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
@@ -39,7 +42,7 @@ require (
go.opentelemetry.io/otel/metric v1.31.0
go.opentelemetry.io/otel/sdk/metric v1.31.0
go.opentelemetry.io/otel/trace v1.31.0
- go.uber.org/fx v1.22.2
+ go.uber.org/fx v1.23.0
go.uber.org/mock v0.4.0
golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.8.0
@@ -56,6 +59,7 @@ require (
github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 // indirect
github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 // indirect
github.com/ajg/form v1.5.1 // indirect
+ github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.32.2 // indirect
github.com/aws/aws-sdk-go-v2/config v1.27.43 // indirect
@@ -111,7 +115,6 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
- github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
@@ -137,7 +140,6 @@ require (
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.14 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
@@ -182,6 +184,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
+ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
diff --git a/go.sum b/go.sum
index b0f8ee3d5..cbd2831e8 100644
--- a/go.sum
+++ b/go.sum
@@ -24,6 +24,8 @@ github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
+github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
+github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c=
github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA=
github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
@@ -95,14 +97,18 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/formancehq/go-libs/v2 v2.0.1-0.20241017152835-2c30f563ab46 h1:8wZtnWSIYNV7DwD0Jr4HsbcRgezOrgDJ2Q0w9ABieKc=
-github.com/formancehq/go-libs/v2 v2.0.1-0.20241017152835-2c30f563ab46/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY=
-github.com/formancehq/go-libs/v2 v2.0.1-0.20241017153232-1a62cecf1a61 h1:GSIhsdo/YXuZXI4q8xA8IrdOkkjfFp6O+DiNywk8s8U=
-github.com/formancehq/go-libs/v2 v2.0.1-0.20241017153232-1a62cecf1a61/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY=
github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63 h1:DN6gDFwh3zO9VwV6Nt2tj4/BEecyfWfOdHp1YYJ5sBA=
github.com/formancehq/go-libs/v2 v2.0.1-0.20241022185745-110c95803b63/go.mod h1:LgxayMN6wgAQbkB3ioBDTHOVMKp1rC6Q55M1CvG44xY=
+github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417 h1:LOd5hxnXDIBcehFrpW1OnXk+VSs0yJXeu1iAOO+Hji4=
+github.com/formancehq/numscript v0.0.9-0.20241009144012-1150c14a1417/go.mod h1:btuSv05cYwi9BvLRxVs5zrunU+O1vTgigG1T6UsawcY=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
+github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
+github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
+github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
+github.com/gkampitakis/go-snaps v0.5.4 h1:GX+dkKmVsRenz7SoTbdIEL4KQARZctkMiZ8ZKprRwT8=
+github.com/gkampitakis/go-snaps v0.5.4/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
@@ -216,6 +222,8 @@ github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMD
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
+github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -299,6 +307,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
+github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
@@ -385,8 +401,8 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
-go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw=
-go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
+go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
+go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
@@ -402,6 +418,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
+golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
diff --git a/internal/api/v2/controllers_bulk.go b/internal/api/v2/controllers_bulk.go
index b3b70e8dd..afa1218d9 100644
--- a/internal/api/v2/controllers_bulk.go
+++ b/internal/api/v2/controllers_bulk.go
@@ -115,6 +115,10 @@ func ProcessBulk(
code = ErrNoPostings
case errors.Is(err, ledgercontroller.ErrTransactionReferenceConflict{}):
code = ErrConflict
+ case errors.Is(err, ledgercontroller.ErrParsing{}):
+ code = ErrInterpreterParse
+ case errors.Is(err, ledgercontroller.ErrRuntime{}):
+ code = ErrInterpreterRuntime
default:
code = api.ErrorInternal
}
diff --git a/internal/api/v2/controllers_transactions_create.go b/internal/api/v2/controllers_transactions_create.go
index b54129fbd..912c18b45 100644
--- a/internal/api/v2/controllers_transactions_create.go
+++ b/internal/api/v2/controllers_transactions_create.go
@@ -7,7 +7,9 @@ import (
ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger"
"errors"
+
"github.com/formancehq/go-libs/v2/api"
+
"github.com/formancehq/ledger/internal/api/common"
)
@@ -50,6 +52,10 @@ func createTransaction(w http.ResponseWriter, r *http.Request) {
api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err)
case errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}):
api.BadRequest(w, ErrValidation, err)
+ case errors.Is(err, ledgercontroller.ErrParsing{}):
+ api.BadRequest(w, ErrInterpreterParse, err)
+ case errors.Is(err, ledgercontroller.ErrRuntime{}):
+ api.BadRequest(w, ErrInterpreterRuntime, err)
default:
common.HandleCommonErrors(w, r, err)
}
diff --git a/internal/api/v2/errors.go b/internal/api/v2/errors.go
index 0e88416c6..7306c6019 100644
--- a/internal/api/v2/errors.go
+++ b/internal/api/v2/errors.go
@@ -9,4 +9,7 @@ const (
ErrCompilationFailed = "COMPILATION_FAILED"
ErrMetadataOverride = "METADATA_OVERRIDE"
ErrBulkSizeExceeded = "BULK_SIZE_EXCEEDED"
+
+ ErrInterpreterParse = "INTERPRETER_PARSE"
+ ErrInterpreterRuntime = "INTERPRETER_RUNTIME"
)
diff --git a/internal/controller/ledger/errors.go b/internal/controller/ledger/errors.go
index e2572cbb9..00ef329ed 100644
--- a/internal/controller/ledger/errors.go
+++ b/internal/controller/ledger/errors.go
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/formancehq/go-libs/v2/platform/postgres"
+ "github.com/formancehq/numscript"
"github.com/formancehq/ledger/internal/machine"
@@ -188,6 +189,35 @@ func newErrCompilationFailed(err error) ErrCompilationFailed {
}
}
+type ErrRuntime struct {
+ Source string
+ Inner numscript.InterpreterError
+}
+
+func (e ErrRuntime) Error() string {
+ return e.Inner.Error()
+}
+
+func (e ErrRuntime) Is(err error) bool {
+ _, ok := err.(ErrRuntime)
+ return ok
+}
+
+type ErrParsing struct {
+ Source string
+ // Precondition: Errors is not empty
+ Errors []numscript.ParserError
+}
+
+func (e ErrParsing) Error() string {
+ return numscript.ParseErrorsToString(e.Errors, e.Source)
+}
+
+func (e ErrParsing) Is(err error) bool {
+ _, ok := err.(ErrParsing)
+ return ok
+}
+
// ErrMetadataOverride is used when a metadata is defined at numscript level AND at the input level
type ErrMetadataOverride struct {
key string
diff --git a/internal/controller/ledger/numscript_parser.go b/internal/controller/ledger/numscript_parser.go
index a06d2e081..87c373b7d 100644
--- a/internal/controller/ledger/numscript_parser.go
+++ b/internal/controller/ledger/numscript_parser.go
@@ -6,6 +6,7 @@ import (
"github.com/bluele/gcache"
"github.com/formancehq/ledger/internal/machine/script/compiler"
+ "github.com/formancehq/numscript"
)
//go:generate mockgen -write_source_comment=false -write_package_comment=false -source numscript_parser.go -destination numscript_parser_generated_test.go -package ledger . NumscriptParser
@@ -32,6 +33,26 @@ func NewDefaultNumscriptParser() *DefaultNumscriptParser {
var _ NumscriptParser = (*DefaultNumscriptParser)(nil)
+type InterpreterNumscriptParser struct{}
+
+func (n *InterpreterNumscriptParser) Parse(script string) (NumscriptRuntime, error) {
+ result := numscript.Parse(script)
+ errs := result.GetParsingErrors()
+ if len(errs) != 0 {
+ return nil, ErrParsing{
+ Source: script,
+ Errors: errs,
+ }
+ }
+ return NewDefaultInterpreterMachineAdapter(result), nil
+}
+
+func NewInterpreterNumscriptParser() *InterpreterNumscriptParser {
+ return &InterpreterNumscriptParser{}
+}
+
+var _ NumscriptParser = (*InterpreterNumscriptParser)(nil)
+
type CacheConfiguration struct {
MaxCount uint
}
diff --git a/internal/controller/ledger/numscript_runtime.go b/internal/controller/ledger/numscript_runtime.go
index 0effc17fd..eec14e643 100644
--- a/internal/controller/ledger/numscript_runtime.go
+++ b/internal/controller/ledger/numscript_runtime.go
@@ -4,15 +4,15 @@ import (
"context"
"fmt"
- "github.com/formancehq/ledger/internal/machine"
-
"errors"
"github.com/formancehq/go-libs/v2/collectionutils"
"github.com/formancehq/go-libs/v2/metadata"
ledger "github.com/formancehq/ledger/internal"
+ "github.com/formancehq/ledger/internal/machine"
"github.com/formancehq/ledger/internal/machine/vm"
"github.com/formancehq/ledger/internal/machine/vm/program"
+ "github.com/formancehq/numscript"
)
type NumscriptExecutionResult struct {
@@ -85,3 +85,51 @@ func NewMachineNumscriptRuntimeAdapter(p program.Program) *MachineNumscriptRunti
}
var _ NumscriptRuntime = (*MachineNumscriptRuntimeAdapter)(nil)
+
+// numscript rewrite implementation
+var _ NumscriptRuntime = (*DefaultInterpreterMachineAdapter)(nil)
+
+type DefaultInterpreterMachineAdapter struct {
+ parseResult numscript.ParseResult
+}
+
+func NewDefaultInterpreterMachineAdapter(parseResult numscript.ParseResult) *DefaultInterpreterMachineAdapter {
+ return &DefaultInterpreterMachineAdapter{
+ parseResult: parseResult,
+ }
+}
+
+func (d *DefaultInterpreterMachineAdapter) Execute(ctx context.Context, tx TX, vars map[string]string) (*NumscriptExecutionResult, error) {
+ execResult, err := d.parseResult.Run(ctx, vars, newNumscriptRewriteAdapter(tx))
+ if err != nil {
+ return nil, ErrRuntime{
+ Source: d.parseResult.GetSource(),
+ Inner: err,
+ }
+ }
+
+ return &NumscriptExecutionResult{
+ Postings: collectionutils.Map(execResult.Postings, func(posting numscript.Posting) ledger.Posting {
+ return ledger.Posting(posting)
+ }),
+ Metadata: castMetadata(execResult.Metadata),
+ AccountMetadata: castAccountsMetadata(execResult.AccountsMetadata),
+ }, nil
+}
+
+func castMetadata(numscriptMeta numscript.Metadata) metadata.Metadata {
+ meta := metadata.Metadata{}
+ for k, v := range numscriptMeta {
+ meta[k] = v.String()
+ }
+ return meta
+}
+
+func castAccountsMetadata(numscriptAccountsMetadata numscript.AccountsMetadata) map[string]metadata.Metadata {
+ m := make(map[string]metadata.Metadata)
+ for k, v := range numscriptAccountsMetadata {
+ m[k] = v
+ }
+ return m
+
+}
diff --git a/internal/controller/ledger/store.go b/internal/controller/ledger/store.go
index 86a2d6be3..55bc839c4 100644
--- a/internal/controller/ledger/store.go
+++ b/internal/controller/ledger/store.go
@@ -7,6 +7,7 @@ import (
"math/big"
"github.com/formancehq/go-libs/v2/migrations"
+ "github.com/formancehq/numscript"
"github.com/formancehq/go-libs/v2/bun/bunpaginate"
"github.com/formancehq/go-libs/v2/metadata"
@@ -297,3 +298,42 @@ func NewListLedgersQuery(pageSize uint64) ListLedgersQuery {
PageSize: pageSize,
}
}
+
+// numscript rewrite implementation
+
+var _ numscript.Store = (*numscriptRewriteAdapter)(nil)
+
+func newNumscriptRewriteAdapter(tx TX) *numscriptRewriteAdapter {
+ return &numscriptRewriteAdapter{
+ TX: tx,
+ }
+}
+
+type numscriptRewriteAdapter struct {
+ TX TX
+}
+
+func (s *numscriptRewriteAdapter) GetBalances(ctx context.Context, q numscript.BalanceQuery) (numscript.Balances, error) {
+ vmBalances, err := s.TX.GetBalances(ctx, BalanceQuery(q))
+ if err != nil {
+ return nil, err
+ }
+ return numscript.Balances(vmBalances), nil
+}
+
+func (s *numscriptRewriteAdapter) GetAccountsMetadata(ctx context.Context, q numscript.MetadataQuery) (numscript.AccountsMetadata, error) {
+ m := numscript.AccountsMetadata{}
+
+ // we ignore the needed metadata values and just return all of them
+ for address := range q {
+ v, err := s.TX.GetAccount(ctx, GetAccountQuery{
+ Addr: address,
+ })
+ if err != nil {
+ return nil, err
+ }
+ m[v.Address] = v.Metadata
+ }
+
+ return m, nil
+}
diff --git a/internal/controller/system/controller.go b/internal/controller/system/controller.go
index e721e2ac8..86b6b63d7 100644
--- a/internal/controller/system/controller.go
+++ b/internal/controller/system/controller.go
@@ -132,6 +132,7 @@ func NewDefaultController(store Store, listener ledgercontroller.Listener, opts
store: store,
listener: listener,
registry: ledgercontroller.NewStateRegistry(),
+ parser: ledgercontroller.NewDefaultNumscriptParser(),
}
for _, opt := range append(defaultOptions, opts...) {
opt(ret)
@@ -172,7 +173,6 @@ func WithEnableFeatures(v bool) Option {
}
var defaultOptions = []Option{
- WithParser(ledgercontroller.NewDefaultNumscriptParser()),
WithMeter(noopmetrics.Meter{}),
WithTracer(nooptracer.Tracer{}),
}
diff --git a/internal/controller/system/module.go b/internal/controller/system/module.go
index 18f839583..b8d4b5691 100644
--- a/internal/controller/system/module.go
+++ b/internal/controller/system/module.go
@@ -18,6 +18,7 @@ type ModuleConfiguration struct {
NSCacheConfiguration ledgercontroller.CacheConfiguration
DatabaseRetryConfiguration DatabaseRetryConfiguration
EnableFeatures bool
+ NumscriptInterpreter bool
}
func NewFXModule(configuration ModuleConfiguration) fx.Option {
@@ -31,23 +32,25 @@ func NewFXModule(configuration ModuleConfiguration) fx.Option {
meterProvider metric.MeterProvider,
tracerProvider trace.TracerProvider,
) *DefaultController {
- options := make([]Option, 0)
+ var parser ledgercontroller.NumscriptParser = ledgercontroller.NewDefaultNumscriptParser()
+ if configuration.NumscriptInterpreter {
+ parser = ledgercontroller.NewInterpreterNumscriptParser()
+ }
+
if configuration.NSCacheConfiguration.MaxCount != 0 {
- options = append(options, WithParser(ledgercontroller.NewCachedNumscriptParser(
- ledgercontroller.NewDefaultNumscriptParser(),
- configuration.NSCacheConfiguration,
- )))
+ parser = ledgercontroller.NewCachedNumscriptParser(parser, ledgercontroller.CacheConfiguration{
+ MaxCount: configuration.NSCacheConfiguration.MaxCount,
+ })
}
return NewDefaultController(
store,
listener,
- append(options,
- WithDatabaseRetryConfiguration(configuration.DatabaseRetryConfiguration),
- WithMeter(meterProvider.Meter("core")),
- WithTracer(tracerProvider.Tracer("core")),
- WithEnableFeatures(configuration.EnableFeatures),
- )...,
+ WithParser(parser),
+ WithDatabaseRetryConfiguration(configuration.DatabaseRetryConfiguration),
+ WithMeter(meterProvider.Meter("core")),
+ WithTracer(tracerProvider.Tracer("core")),
+ WithEnableFeatures(configuration.EnableFeatures),
)
}),
)
diff --git a/ledger b/ledger
new file mode 100755
index 000000000..a7d7905c8
Binary files /dev/null and b/ledger differ
diff --git a/openapi.yaml b/openapi.yaml
index e0c60b639..4a7d602fe 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -3453,6 +3453,8 @@ components:
- IMPORT
- TIMEOUT
- BULK_SIZE_EXCEEDED
+ - INTERPRETER_PARSE
+ - INTERPRETER_RUNTIME
example: VALIDATION
V2LedgerInfoResponse:
type: object
diff --git a/openapi/v2.yaml b/openapi/v2.yaml
index a06345033..3e2ed140d 100644
--- a/openapi/v2.yaml
+++ b/openapi/v2.yaml
@@ -14,24 +14,24 @@ paths:
operationId: v2GetInfo
x-speakeasy-name-override: GetInfo
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ConfigInfoResponse'
+ $ref: "#/components/schemas/V2ConfigInfoResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
5XX:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -68,18 +68,18 @@ paths:
type: string
example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2LedgerListResponse'
+ $ref: "#/components/schemas/V2LedgerListResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -99,18 +99,18 @@ paths:
tags:
- ledger.v2
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2GetLedgerResponse'
+ $ref: "#/components/schemas/V2GetLedgerResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -124,16 +124,16 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2CreateLedgerRequest'
+ $ref: "#/components/schemas/V2CreateLedgerRequest"
responses:
- '204':
+ "204":
description: OK
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -156,22 +156,22 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2UpdateLedgerMetadataRequest'
+ $ref: "#/components/schemas/V2UpdateLedgerMetadataRequest"
responses:
- '204':
+ "204":
description: OK
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
5XX:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -198,14 +198,14 @@ paths:
tags:
- ledger.v2
responses:
- '204':
+ "204":
description: OK
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -225,18 +225,18 @@ paths:
type: string
example: ledger001
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2LedgerInfoResponse'
+ $ref: "#/components/schemas/V2LedgerInfoResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -259,26 +259,26 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2Bulk'
+ $ref: "#/components/schemas/V2Bulk"
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2BulkResponse'
- '400':
+ $ref: "#/components/schemas/V2BulkResponse"
+ "400":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2BulkResponse'
+ $ref: "#/components/schemas/V2BulkResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -310,7 +310,7 @@ paths:
type: object
additionalProperties: true
responses:
- '204':
+ "204":
description: OK
headers:
Count:
@@ -323,7 +323,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -385,18 +385,18 @@ paths:
type: object
additionalProperties: true
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2AccountsCursorResponse'
+ $ref: "#/components/schemas/V2AccountsCursorResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -443,18 +443,18 @@ paths:
type: string
format: date-time
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2AccountResponse'
+ $ref: "#/components/schemas/V2AccountResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -506,10 +506,10 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
required: true
responses:
- '204':
+ "204":
description: No Content
content: {}
default:
@@ -517,7 +517,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -559,7 +559,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -582,18 +582,18 @@ paths:
type: string
example: ledger001
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2StatsResponse'
+ $ref: "#/components/schemas/V2StatsResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -625,7 +625,7 @@ paths:
type: object
additionalProperties: true
responses:
- '204':
+ "204":
description: OK
headers:
Count:
@@ -638,7 +638,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -712,18 +712,18 @@ paths:
type: object
additionalProperties: true
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2TransactionsCursorResponse'
+ $ref: "#/components/schemas/V2TransactionsCursorResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -756,8 +756,7 @@ paths:
type: string
- name: force
in: query
- description:
- Disable balance checks when passing postings
+ description: Disable balance checks when passing postings
schema:
type: boolean
example: true
@@ -770,20 +769,20 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2PostTransaction'
+ $ref: "#/components/schemas/V2PostTransaction"
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2CreateTransactionResponse'
+ $ref: "#/components/schemas/V2CreateTransactionResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -824,18 +823,18 @@ paths:
type: string
format: date-time
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2GetTransactionResponse'
+ $ref: "#/components/schemas/V2GetTransactionResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -881,9 +880,9 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
responses:
- '204':
+ "204":
description: No Content
content: {}
default:
@@ -891,7 +890,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -936,7 +935,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -985,18 +984,18 @@ paths:
type: boolean
example: true
responses:
- '201':
+ "201":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2RevertTransactionResponse'
+ $ref: "#/components/schemas/V2RevertTransactionResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -1034,18 +1033,18 @@ paths:
type: object
additionalProperties: true
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2AggregateBalancesResponse'
+ $ref: "#/components/schemas/V2AggregateBalancesResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -1122,18 +1121,18 @@ paths:
type: object
additionalProperties: true
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2VolumesWithBalanceCursorResponse'
+ $ref: "#/components/schemas/V2VolumesWithBalanceCursorResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -1190,18 +1189,18 @@ paths:
type: object
additionalProperties: true
responses:
- '200':
+ "200":
description: OK
content:
application/json:
schema:
- $ref: '#/components/schemas/V2LogsCursorResponse'
+ $ref: "#/components/schemas/V2LogsCursorResponse"
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:read
@@ -1225,14 +1224,14 @@ paths:
schema:
type: string
responses:
- '204':
+ "204":
description: Import OK
default:
description: Error
content:
application/json:
schema:
- $ref: '#/components/schemas/V2ErrorResponse'
+ $ref: "#/components/schemas/V2ErrorResponse"
security:
- Authorization:
- ledger:write
@@ -1252,7 +1251,7 @@ paths:
type: string
example: ledger001
responses:
- '200':
+ "200":
description: Import OK
default:
description: Error
@@ -1271,9 +1270,9 @@ components:
type: oauth2
flows:
clientCredentials:
- tokenUrl: '/api/auth/oauth/token'
- refreshUrl: '/api/auth/oauth/token'
- scopes: { }
+ tokenUrl: "/api/auth/oauth/token"
+ refreshUrl: "/api/auth/oauth/token"
+ scopes: {}
schemas:
V2AccountsCursorResponse:
@@ -1302,11 +1301,11 @@ components:
example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=
next:
type: string
- example: ''
+ example: ""
data:
type: array
items:
- $ref: '#/components/schemas/V2Account'
+ $ref: "#/components/schemas/V2Account"
V2TransactionsCursorResponse:
type: object
required:
@@ -1333,11 +1332,11 @@ components:
example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=
next:
type: string
- example: ''
+ example: ""
data:
type: array
items:
- $ref: '#/components/schemas/V2Transaction'
+ $ref: "#/components/schemas/V2Transaction"
V2LogsCursorResponse:
type: object
required:
@@ -1364,25 +1363,25 @@ components:
example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=
next:
type: string
- example: ''
+ example: ""
data:
type: array
items:
- $ref: '#/components/schemas/V2Log'
+ $ref: "#/components/schemas/V2Log"
V2AccountResponse:
type: object
required:
- data
properties:
data:
- $ref: '#/components/schemas/V2Account'
+ $ref: "#/components/schemas/V2Account"
V2AggregateBalancesResponse:
type: object
required:
- data
properties:
data:
- $ref: '#/components/schemas/V2AssetsBalances'
+ $ref: "#/components/schemas/V2AssetsBalances"
V2VolumesWithBalanceCursorResponse:
type: object
required:
@@ -1409,11 +1408,11 @@ components:
example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=
next:
type: string
- example: ''
+ example: ""
data:
type: array
items:
- $ref: '#/components/schemas/V2VolumesWithBalance'
+ $ref: "#/components/schemas/V2VolumesWithBalance"
V2VolumesWithBalance:
type: object
properties:
@@ -1441,7 +1440,7 @@ components:
additionalProperties:
type: string
example:
- admin: 'true'
+ admin: "true"
V2ConfigInfo:
type: object
properties:
@@ -1468,11 +1467,11 @@ components:
additionalProperties:
type: string
example:
- admin: 'true'
+ admin: "true"
volumes:
- $ref: '#/components/schemas/V2Volumes'
+ $ref: "#/components/schemas/V2Volumes"
effectiveVolumes:
- $ref: '#/components/schemas/V2Volumes'
+ $ref: "#/components/schemas/V2Volumes"
V2AssetsBalances:
type: object
additionalProperties:
@@ -1515,12 +1514,12 @@ components:
postings:
type: array
items:
- $ref: '#/components/schemas/V2Posting'
+ $ref: "#/components/schemas/V2Posting"
reference:
type: string
example: ref:001
metadata:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
id:
type: integer
format: bigint
@@ -1531,13 +1530,13 @@ components:
type: string
format: date-time
preCommitVolumes:
- $ref: '#/components/schemas/V2AggregatedVolumes'
+ $ref: "#/components/schemas/V2AggregatedVolumes"
postCommitVolumes:
- $ref: '#/components/schemas/V2AggregatedVolumes'
+ $ref: "#/components/schemas/V2AggregatedVolumes"
preCommitEffectiveVolumes:
- $ref: '#/components/schemas/V2AggregatedVolumes'
+ $ref: "#/components/schemas/V2AggregatedVolumes"
postCommitEffectiveVolumes:
- $ref: '#/components/schemas/V2AggregatedVolumes'
+ $ref: "#/components/schemas/V2AggregatedVolumes"
required:
- postings
- timestamp
@@ -1556,7 +1555,7 @@ components:
postings:
type: array
items:
- $ref: '#/components/schemas/V2Posting'
+ $ref: "#/components/schemas/V2Posting"
script:
type: object
properties:
@@ -1575,7 +1574,7 @@ components:
type: string
example: ref:001
metadata:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
V2Stats:
type: object
properties:
@@ -1623,28 +1622,28 @@ components:
V2CreateTransactionResponse:
properties:
data:
- $ref: '#/components/schemas/V2Transaction'
+ $ref: "#/components/schemas/V2Transaction"
type: object
required:
- data
V2RevertTransactionResponse:
- $ref: '#/components/schemas/V2CreateTransactionResponse'
+ $ref: "#/components/schemas/V2CreateTransactionResponse"
V2GetTransactionResponse:
properties:
data:
- $ref: '#/components/schemas/V2Transaction'
+ $ref: "#/components/schemas/V2Transaction"
type: object
required:
- data
V2StatsResponse:
properties:
data:
- $ref: '#/components/schemas/V2Stats'
+ $ref: "#/components/schemas/V2Stats"
type: object
required:
- data
V2ConfigInfoResponse:
- $ref: '#/components/schemas/V2ConfigInfo'
+ $ref: "#/components/schemas/V2ConfigInfo"
V2Volume:
type: object
properties:
@@ -1667,7 +1666,7 @@ components:
V2Volumes:
type: object
additionalProperties:
- $ref: '#/components/schemas/V2Volume'
+ $ref: "#/components/schemas/V2Volume"
example:
USD:
input: 100
@@ -1680,7 +1679,7 @@ components:
V2AggregatedVolumes:
type: object
additionalProperties:
- $ref: '#/components/schemas/V2Volumes'
+ $ref: "#/components/schemas/V2Volumes"
example:
orders:1:
USD:
@@ -1699,10 +1698,10 @@ components:
- errorMessage
properties:
errorCode:
- $ref: '#/components/schemas/V2ErrorsEnum'
+ $ref: "#/components/schemas/V2ErrorsEnum"
errorMessage:
type: string
- example: '[VALIDATION] invalid ''cursor'' query param'
+ example: "[VALIDATION] invalid 'cursor' query param"
details:
type: string
example: >-
@@ -1724,12 +1723,14 @@ components:
- IMPORT
- TIMEOUT
- BULK_SIZE_EXCEEDED
+ - INTERPRETER_PARSE
+ - INTERPRETER_RUNTIME
example: VALIDATION
V2LedgerInfoResponse:
type: object
properties:
data:
- $ref: '#/components/schemas/V2LedgerInfo'
+ $ref: "#/components/schemas/V2LedgerInfo"
V2LedgerInfo:
type: object
properties:
@@ -1742,7 +1743,7 @@ components:
migrations:
type: array
items:
- $ref: '#/components/schemas/V2MigrationInfo'
+ $ref: "#/components/schemas/V2MigrationInfo"
V2MigrationInfo:
type: object
properties:
@@ -1765,7 +1766,7 @@ components:
V2Bulk:
type: array
items:
- $ref: '#/components/schemas/V2BulkElement'
+ $ref: "#/components/schemas/V2BulkElement"
V2BaseBulkElement:
type: object
required:
@@ -1778,25 +1779,25 @@ components:
V2BulkElement:
type: object
oneOf:
- - $ref: '#/components/schemas/V2BulkElementCreateTransaction'
- - $ref: '#/components/schemas/V2BulkElementAddMetadata'
- - $ref: '#/components/schemas/V2BulkElementRevertTransaction'
- - $ref: '#/components/schemas/V2BulkElementDeleteMetadata'
+ - $ref: "#/components/schemas/V2BulkElementCreateTransaction"
+ - $ref: "#/components/schemas/V2BulkElementAddMetadata"
+ - $ref: "#/components/schemas/V2BulkElementRevertTransaction"
+ - $ref: "#/components/schemas/V2BulkElementDeleteMetadata"
discriminator:
propertyName: action
mapping:
- CREATE_TRANSACTION: '#/components/schemas/V2BulkElementCreateTransaction'
- ADD_METADATA: '#/components/schemas/V2BulkElementAddMetadata'
- REVERT_TRANSACTION: '#/components/schemas/V2BulkElementRevertTransaction'
- DELETE_METADATA: '#/components/schemas/V2BulkElementDeleteMetadata'
+ CREATE_TRANSACTION: "#/components/schemas/V2BulkElementCreateTransaction"
+ ADD_METADATA: "#/components/schemas/V2BulkElementAddMetadata"
+ REVERT_TRANSACTION: "#/components/schemas/V2BulkElementRevertTransaction"
+ DELETE_METADATA: "#/components/schemas/V2BulkElementDeleteMetadata"
V2BulkElementCreateTransaction:
type: object
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElement'
+ - $ref: "#/components/schemas/V2BaseBulkElement"
- type: object
properties:
data:
- $ref: '#/components/schemas/V2PostTransaction'
+ $ref: "#/components/schemas/V2PostTransaction"
V2TargetId:
oneOf:
- type: string
@@ -1810,16 +1811,16 @@ components:
V2BulkElementAddMetadata:
type: object
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElement'
+ - $ref: "#/components/schemas/V2BaseBulkElement"
- type: object
properties:
data:
type: object
properties:
targetId:
- $ref: '#/components/schemas/V2TargetId'
+ $ref: "#/components/schemas/V2TargetId"
targetType:
- $ref: '#/components/schemas/V2TargetType'
+ $ref: "#/components/schemas/V2TargetType"
metadata:
type: object
additionalProperties:
@@ -1831,7 +1832,7 @@ components:
V2BulkElementRevertTransaction:
type: object
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElement'
+ - $ref: "#/components/schemas/V2BaseBulkElement"
- type: object
properties:
data:
@@ -1849,16 +1850,16 @@ components:
V2BulkElementDeleteMetadata:
type: object
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElement'
+ - $ref: "#/components/schemas/V2BaseBulkElement"
- type: object
properties:
data:
type: object
properties:
targetId:
- $ref: '#/components/schemas/V2TargetId'
+ $ref: "#/components/schemas/V2TargetId"
targetType:
- $ref: '#/components/schemas/V2TargetType'
+ $ref: "#/components/schemas/V2TargetType"
key:
type: string
required:
@@ -1870,26 +1871,26 @@ components:
data:
type: array
items:
- $ref: '#/components/schemas/V2BulkElementResult'
+ $ref: "#/components/schemas/V2BulkElementResult"
type: object
required:
- data
V2BulkElementResult:
type: object
oneOf:
- - $ref: '#/components/schemas/V2BulkElementResultCreateTransaction'
- - $ref: '#/components/schemas/V2BulkElementResultAddMetadata'
- - $ref: '#/components/schemas/V2BulkElementResultRevertTransaction'
- - $ref: '#/components/schemas/V2BulkElementResultDeleteMetadata'
- - $ref: '#/components/schemas/V2BulkElementResultError'
+ - $ref: "#/components/schemas/V2BulkElementResultCreateTransaction"
+ - $ref: "#/components/schemas/V2BulkElementResultAddMetadata"
+ - $ref: "#/components/schemas/V2BulkElementResultRevertTransaction"
+ - $ref: "#/components/schemas/V2BulkElementResultDeleteMetadata"
+ - $ref: "#/components/schemas/V2BulkElementResultError"
discriminator:
propertyName: responseType
mapping:
- CREATE_TRANSACTION: '#/components/schemas/V2BulkElementResultCreateTransaction'
- ADD_METADATA: '#/components/schemas/V2BulkElementResultAddMetadata'
- REVERT_TRANSACTION: '#/components/schemas/V2BulkElementResultRevertTransaction'
- DELETE_METADATA: '#/components/schemas/V2BulkElementResultDeleteMetadata'
- ERROR: '#/components/schemas/V2BulkElementResultError'
+ CREATE_TRANSACTION: "#/components/schemas/V2BulkElementResultCreateTransaction"
+ ADD_METADATA: "#/components/schemas/V2BulkElementResultAddMetadata"
+ REVERT_TRANSACTION: "#/components/schemas/V2BulkElementResultRevertTransaction"
+ DELETE_METADATA: "#/components/schemas/V2BulkElementResultDeleteMetadata"
+ ERROR: "#/components/schemas/V2BulkElementResultError"
V2BaseBulkElementResult:
type: object
properties:
@@ -1899,31 +1900,31 @@ components:
- responseType
V2BulkElementResultCreateTransaction:
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElementResult'
+ - $ref: "#/components/schemas/V2BaseBulkElementResult"
- type: object
properties:
data:
- $ref: '#/components/schemas/V2Transaction'
+ $ref: "#/components/schemas/V2Transaction"
required:
- data
V2BulkElementResultAddMetadata:
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElementResult'
+ - $ref: "#/components/schemas/V2BaseBulkElementResult"
V2BulkElementResultRevertTransaction:
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElementResult'
+ - $ref: "#/components/schemas/V2BaseBulkElementResult"
- type: object
properties:
data:
- $ref: '#/components/schemas/V2Transaction'
+ $ref: "#/components/schemas/V2Transaction"
required:
- data
V2BulkElementResultDeleteMetadata:
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElementResult'
+ - $ref: "#/components/schemas/V2BaseBulkElementResult"
V2BulkElementResultError:
allOf:
- - $ref: '#/components/schemas/V2BaseBulkElementResult'
+ - $ref: "#/components/schemas/V2BaseBulkElementResult"
- type: object
properties:
errorCode:
@@ -1941,7 +1942,7 @@ components:
bucket:
type: string
metadata:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
features:
type: object
additionalProperties:
@@ -1957,7 +1958,7 @@ components:
bucket:
type: string
metadata:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
required:
- name
- addedAt
@@ -1988,17 +1989,17 @@ components:
example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=
next:
type: string
- example: ''
+ example: ""
data:
type: array
items:
- $ref: '#/components/schemas/V2Ledger'
+ $ref: "#/components/schemas/V2Ledger"
V2UpdateLedgerMetadataRequest:
- $ref: '#/components/schemas/V2Metadata'
+ $ref: "#/components/schemas/V2Metadata"
V2GetLedgerResponse:
type: object
required:
- data
properties:
data:
- $ref: '#/components/schemas/V2Ledger'
+ $ref: "#/components/schemas/V2Ledger"
diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock
index 7db1bbd33..3ac7e688a 100644
--- a/pkg/client/.speakeasy/gen.lock
+++ b/pkg/client/.speakeasy/gen.lock
@@ -1,12 +1,12 @@
lockVersion: 2.0.0
id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3
management:
- docChecksum: f87e5e30078da93c83bec4115056a7a9
+ docChecksum: 043615f02d6da6be8aa8e5c006587e66
docVersion: v1
speakeasyVersion: 1.351.0
generationVersion: 2.384.1
- releaseVersion: 0.4.15
- configChecksum: 82b92b8a70bc4a520560afe9e887834c
+ releaseVersion: 0.4.16
+ configChecksum: 87ddc5f3dd8be9290d0bf5cabd849a48
features:
go:
additionalDependencies: 0.1.0
diff --git a/pkg/client/.speakeasy/gen.yaml b/pkg/client/.speakeasy/gen.yaml
index ad5852e79..50198e28b 100644
--- a/pkg/client/.speakeasy/gen.yaml
+++ b/pkg/client/.speakeasy/gen.yaml
@@ -15,7 +15,7 @@ generation:
auth:
oAuth2ClientCredentialsEnabled: true
go:
- version: 0.4.15
+ version: 0.4.16
additionalDependencies: {}
allowUnknownFieldsInWeakUnions: false
clientServerStatusCodesAsErrors: true
diff --git a/pkg/client/docs/models/components/v2errorsenum.md b/pkg/client/docs/models/components/v2errorsenum.md
index a292606a7..35dc2f95a 100644
--- a/pkg/client/docs/models/components/v2errorsenum.md
+++ b/pkg/client/docs/models/components/v2errorsenum.md
@@ -3,19 +3,21 @@
## Values
-| Name | Value |
-| ------------------------------- | ------------------------------- |
-| `V2ErrorsEnumInternal` | INTERNAL |
-| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND |
-| `V2ErrorsEnumValidation` | VALIDATION |
-| `V2ErrorsEnumConflict` | CONFLICT |
-| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED |
-| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE |
-| `V2ErrorsEnumNotFound` | NOT_FOUND |
-| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING |
-| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT |
-| `V2ErrorsEnumNoPostings` | NO_POSTINGS |
-| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND |
-| `V2ErrorsEnumImport` | IMPORT |
-| `V2ErrorsEnumTimeout` | TIMEOUT |
-| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED |
\ No newline at end of file
+| Name | Value |
+| -------------------------------- | -------------------------------- |
+| `V2ErrorsEnumInternal` | INTERNAL |
+| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND |
+| `V2ErrorsEnumValidation` | VALIDATION |
+| `V2ErrorsEnumConflict` | CONFLICT |
+| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED |
+| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE |
+| `V2ErrorsEnumNotFound` | NOT_FOUND |
+| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING |
+| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT |
+| `V2ErrorsEnumNoPostings` | NO_POSTINGS |
+| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND |
+| `V2ErrorsEnumImport` | IMPORT |
+| `V2ErrorsEnumTimeout` | TIMEOUT |
+| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED |
+| `V2ErrorsEnumInterpreterParse` | INTERPRETER_PARSE |
+| `V2ErrorsEnumInterpreterRuntime` | INTERPRETER_RUNTIME |
\ No newline at end of file
diff --git a/pkg/client/formance.go b/pkg/client/formance.go
index e1a4b7c0c..949d62ce8 100644
--- a/pkg/client/formance.go
+++ b/pkg/client/formance.go
@@ -143,9 +143,9 @@ func New(opts ...SDKOption) *Formance {
sdkConfiguration: sdkConfiguration{
Language: "go",
OpenAPIDocVersion: "v1",
- SDKVersion: "0.4.15",
+ SDKVersion: "0.4.16",
GenVersion: "2.384.1",
- UserAgent: "speakeasy-sdk/go 0.4.15 2.384.1 v1 github.com/formancehq/stack/ledger/client",
+ UserAgent: "speakeasy-sdk/go 0.4.16 2.384.1 v1 github.com/formancehq/stack/ledger/client",
Hooks: hooks.New(),
},
}
diff --git a/pkg/client/models/components/v2errorsenum.go b/pkg/client/models/components/v2errorsenum.go
index 598f3248c..862ca3c52 100644
--- a/pkg/client/models/components/v2errorsenum.go
+++ b/pkg/client/models/components/v2errorsenum.go
@@ -10,20 +10,22 @@ import (
type V2ErrorsEnum string
const (
- V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL"
- V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND"
- V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION"
- V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT"
- V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED"
- V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE"
- V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND"
- V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING"
- V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT"
- V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS"
- V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND"
- V2ErrorsEnumImport V2ErrorsEnum = "IMPORT"
- V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT"
- V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED"
+ V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL"
+ V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND"
+ V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION"
+ V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT"
+ V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED"
+ V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE"
+ V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND"
+ V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING"
+ V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT"
+ V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS"
+ V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND"
+ V2ErrorsEnumImport V2ErrorsEnum = "IMPORT"
+ V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT"
+ V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED"
+ V2ErrorsEnumInterpreterParse V2ErrorsEnum = "INTERPRETER_PARSE"
+ V2ErrorsEnumInterpreterRuntime V2ErrorsEnum = "INTERPRETER_RUNTIME"
)
func (e V2ErrorsEnum) ToPointer() *V2ErrorsEnum {
@@ -62,6 +64,10 @@ func (e *V2ErrorsEnum) UnmarshalJSON(data []byte) error {
case "TIMEOUT":
fallthrough
case "BULK_SIZE_EXCEEDED":
+ fallthrough
+ case "INTERPRETER_PARSE":
+ fallthrough
+ case "INTERPRETER_RUNTIME":
*e = V2ErrorsEnum(v)
return nil
default:
diff --git a/pkg/testserver/server.go b/pkg/testserver/server.go
index 2939de5b1..c70670e81 100644
--- a/pkg/testserver/server.go
+++ b/pkg/testserver/server.go
@@ -3,16 +3,17 @@ package testserver
import (
"context"
"fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+
"github.com/formancehq/go-libs/v2/otlp"
"github.com/formancehq/go-libs/v2/otlp/otlpmetrics"
"github.com/formancehq/go-libs/v2/publish"
"github.com/google/uuid"
"github.com/nats-io/nats.go"
"github.com/uptrace/bun"
- "io"
- "net/http"
- "strings"
- "time"
"github.com/formancehq/go-libs/v2/bun/bunconnect"
"github.com/formancehq/go-libs/v2/httpclient"
@@ -37,13 +38,14 @@ type OTLPConfig struct {
}
type Configuration struct {
- PostgresConfiguration bunconnect.ConnectionOptions
- NatsURL string
- Output io.Writer
- Debug bool
- OTLPConfig *OTLPConfig
- ExperimentalFeatures bool
- BulkMaxSize int
+ PostgresConfiguration bunconnect.ConnectionOptions
+ NatsURL string
+ Output io.Writer
+ Debug bool
+ OTLPConfig *OTLPConfig
+ ExperimentalFeatures bool
+ BulkMaxSize int
+ ExperimentalNumscriptRewrite bool
}
type Server struct {
@@ -81,6 +83,12 @@ func (s *Server) Start() {
fmt.Sprint(s.configuration.BulkMaxSize),
)
}
+ if s.configuration.ExperimentalNumscriptRewrite {
+ args = append(
+ args,
+ "--"+cmd.NumscriptInterpreterFlag,
+ )
+ }
if s.configuration.PostgresConfiguration.MaxIdleConns != 0 {
args = append(
args,
diff --git a/test/e2e/api_bulk.go b/test/e2e/api_bulk.go
index 7c6570312..4dfe1c918 100644
--- a/test/e2e/api_bulk.go
+++ b/test/e2e/api_bulk.go
@@ -3,13 +3,14 @@
package test_suite
import (
+ "math/big"
+ "time"
+
"github.com/formancehq/go-libs/v2/logging"
. "github.com/formancehq/go-libs/v2/testing/api"
. "github.com/formancehq/ledger/pkg/testserver"
"github.com/formancehq/stack/ledger/client/models/components"
"github.com/formancehq/stack/ledger/client/models/operations"
- "math/big"
- "time"
"github.com/formancehq/go-libs/v2/metadata"
. "github.com/onsi/ginkgo/v2"
@@ -17,173 +18,192 @@ import (
)
var _ = Context("Ledger engine tests", func() {
- var (
- db = UseTemplatedDatabase()
- ctx = logging.TestingContext()
- bulkMaxSize = 5
- )
- testServer := NewTestServer(func() Configuration {
- return Configuration{
- PostgresConfiguration: db.GetValue().ConnectionOptions(),
- Output: GinkgoWriter,
- Debug: debug,
- NatsURL: natsServer.GetValue().ClientURL(),
- BulkMaxSize: bulkMaxSize,
- }
- })
- BeforeEach(func() {
- err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
- Ledger: "default",
- })
- Expect(err).To(BeNil())
- })
- When("creating a bulk on a ledger", func() {
- var (
- now = time.Now().Round(time.Microsecond).UTC()
- items []components.V2BulkElement
- err error
- )
- BeforeEach(func() {
- items = []components.V2BulkElement{
- components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
- Data: &components.V2PostTransaction{
- Metadata: map[string]string{},
+ for _, data := range []struct {
+ description string
+ numscriptRewrite bool
+ }{
+ {"default", false},
+ {"numscript rewrite", true},
+ } {
+
+ Context(data.description, func() {
+ var (
+ db = UseTemplatedDatabase()
+ ctx = logging.TestingContext()
+ bulkMaxSize = 5
+ )
+
+ testServer := NewTestServer(func() Configuration {
+ return Configuration{
+ PostgresConfiguration: db.GetValue().ConnectionOptions(),
+ Output: GinkgoWriter,
+ Debug: debug,
+ NatsURL: natsServer.GetValue().ClientURL(),
+ BulkMaxSize: bulkMaxSize,
+ ExperimentalNumscriptRewrite: data.numscriptRewrite,
+ }
+ })
+ BeforeEach(func() {
+ err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
+ Ledger: "default",
+ })
+ Expect(err).To(BeNil())
+ })
+ When("creating a bulk on a ledger", func() {
+ var (
+ now = time.Now().Round(time.Microsecond).UTC()
+ items []components.V2BulkElement
+ err error
+ )
+ BeforeEach(func() {
+ items = []components.V2BulkElement{
+ components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
+ Data: &components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{{
+ Amount: big.NewInt(100),
+ Asset: "USD/2",
+ Destination: "bank",
+ Source: "world",
+ }},
+ Timestamp: &now,
+ },
+ }),
+ components.CreateV2BulkElementAddMetadata(components.V2BulkElementAddMetadata{
+ Data: &components.Data{
+ Metadata: metadata.Metadata{
+ "foo": "bar",
+ "role": "admin",
+ },
+ TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)),
+ TargetType: components.V2TargetTypeTransaction,
+ },
+ }),
+ components.CreateV2BulkElementDeleteMetadata(components.V2BulkElementDeleteMetadata{
+ Data: &components.V2BulkElementDeleteMetadataData{
+ Key: "foo",
+ TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)),
+ TargetType: components.V2TargetTypeTransaction,
+ },
+ }),
+ components.CreateV2BulkElementRevertTransaction(components.V2BulkElementRevertTransaction{
+ Data: &components.V2BulkElementRevertTransactionData{
+ ID: big.NewInt(1),
+ },
+ }),
+ }
+ })
+ JustBeforeEach(func() {
+ _, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{
+ RequestBody: items,
+ Ledger: "default",
+ })
+ })
+ It("should be ok", func() {
+ Expect(err).To(Succeed())
+
+ tx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{
+ ID: big.NewInt(1),
+ Ledger: "default",
+ })
+ Expect(err).To(Succeed())
+ reversedTx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{
+ ID: big.NewInt(2),
+ Ledger: "default",
+ })
+ Expect(err).To(Succeed())
+
+ Expect(*tx).To(Equal(components.V2Transaction{
+ ID: big.NewInt(1),
+ Metadata: metadata.Metadata{
+ "role": "admin",
+ },
Postings: []components.V2Posting{{
Amount: big.NewInt(100),
Asset: "USD/2",
Destination: "bank",
Source: "world",
}},
- Timestamp: &now,
- },
- }),
- components.CreateV2BulkElementAddMetadata(components.V2BulkElementAddMetadata{
- Data: &components.Data{
- Metadata: metadata.Metadata{
- "foo": "bar",
- "role": "admin",
- },
- TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)),
- TargetType: components.V2TargetTypeTransaction,
- },
- }),
- components.CreateV2BulkElementDeleteMetadata(components.V2BulkElementDeleteMetadata{
- Data: &components.V2BulkElementDeleteMetadataData{
- Key: "foo",
- TargetID: components.CreateV2TargetIDBigint(big.NewInt(1)),
- TargetType: components.V2TargetTypeTransaction,
- },
- }),
- components.CreateV2BulkElementRevertTransaction(components.V2BulkElementRevertTransaction{
- Data: &components.V2BulkElementRevertTransactionData{
- ID: big.NewInt(1),
- },
- }),
- }
- })
- JustBeforeEach(func() {
- _, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{
- RequestBody: items,
- Ledger: "default",
- })
- })
- It("should be ok", func() {
- Expect(err).To(Succeed())
-
- tx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{
- ID: big.NewInt(1),
- Ledger: "default",
- })
- Expect(err).To(Succeed())
-
- reversedTx, err := GetTransaction(ctx, testServer.GetValue(), operations.V2GetTransactionRequest{
- ID: big.NewInt(2),
- Ledger: "default",
- })
- Expect(err).To(Succeed())
-
- Expect(*tx).To(Equal(components.V2Transaction{
- ID: big.NewInt(1),
- Metadata: metadata.Metadata{
- "role": "admin",
- },
- Postings: []components.V2Posting{{
- Amount: big.NewInt(100),
- Asset: "USD/2",
- Destination: "bank",
- Source: "world",
- }},
- Reverted: true,
- RevertedAt: &reversedTx.Timestamp,
- Timestamp: now,
- InsertedAt: tx.InsertedAt,
- }))
- })
- Context("with exceeded batch size", func() {
- BeforeEach(func() {
- items = make([]components.V2BulkElement, 0)
- for i := 0; i < bulkMaxSize+1; i++ {
- items = append(items, components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
- Data: &components.V2PostTransaction{
- Metadata: map[string]string{},
- Postings: []components.V2Posting{{
- Amount: big.NewInt(100),
- Asset: "USD/2",
- Destination: "bank",
- Source: "world",
- }},
- Timestamp: &now,
- },
+ Reverted: true,
+ RevertedAt: &reversedTx.Timestamp,
+ Timestamp: now,
+ InsertedAt: tx.InsertedAt,
}))
- }
- })
- It("should respond with an error", func() {
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumBulkSizeExceeded)))
+ })
+ Context("with exceeded batch size", func() {
+ BeforeEach(func() {
+ items = make([]components.V2BulkElement, 0)
+ for i := 0; i < bulkMaxSize+1; i++ {
+ items = append(items, components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
+ Data: &components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{{
+ Amount: big.NewInt(100),
+ Asset: "USD/2",
+ Destination: "bank",
+ Source: "world",
+ }},
+ Timestamp: &now,
+ },
+ }))
+ }
+ })
+ It("should respond with an error", func() {
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumBulkSizeExceeded)))
+ })
+ })
})
- })
- })
- When("creating a bulk with an error on a ledger", func() {
- var (
- now = time.Now().Round(time.Microsecond).UTC()
- err error
- bulkResponse []components.V2BulkElementResult
- )
- BeforeEach(func() {
- bulkResponse, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{
- RequestBody: []components.V2BulkElement{
- components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
- Data: &components.V2PostTransaction{
- Metadata: map[string]string{},
- Postings: []components.V2Posting{{
- Amount: big.NewInt(100),
- Asset: "USD/2",
- Destination: "bank",
- Source: "world",
- }},
- Timestamp: &now,
- },
- }),
- components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
- Data: &components.V2PostTransaction{
- Metadata: map[string]string{},
- Postings: []components.V2Posting{{
- Amount: big.NewInt(200), // Insufficient fund
- Asset: "USD/2",
- Destination: "user",
- Source: "bank",
- }},
- Timestamp: &now,
+ When("creating a bulk with an error on a ledger", func() {
+ var (
+ now = time.Now().Round(time.Microsecond).UTC()
+ err error
+ bulkResponse []components.V2BulkElementResult
+ )
+ BeforeEach(func() {
+ bulkResponse, err = CreateBulk(ctx, testServer.GetValue(), operations.V2CreateBulkRequest{
+ RequestBody: []components.V2BulkElement{
+ components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
+ Data: &components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{{
+ Amount: big.NewInt(100),
+ Asset: "USD/2",
+ Destination: "bank",
+ Source: "world",
+ }},
+ Timestamp: &now,
+ },
+ }),
+ components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{
+ Data: &components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{{
+ Amount: big.NewInt(200), // Insufficient fund
+ Asset: "USD/2",
+ Destination: "user",
+ Source: "bank",
+ }},
+ Timestamp: &now,
+ },
+ }),
},
- }),
- },
- Ledger: "default",
+ Ledger: "default",
+ })
+ Expect(err).To(Succeed())
+ })
+ It("should respond with an error", func() {
+ var expectedErr string
+ if data.numscriptRewrite {
+ expectedErr = "INTERPRETER_RUNTIME"
+ } else {
+ expectedErr = "INSUFFICIENT_FUND"
+ }
+ Expect(bulkResponse[1].Type).To(Equal(components.V2BulkElementResultType("ERROR")))
+ Expect(bulkResponse[1].V2BulkElementResultError.ErrorCode).To(Equal(expectedErr))
+ })
})
- Expect(err).To(Succeed())
})
- It("should respond with an error", func() {
- Expect(bulkResponse[1].Type).To(Equal(components.V2BulkElementResultType("ERROR")))
- Expect(bulkResponse[1].V2BulkElementResultError.ErrorCode).To(Equal("INSUFFICIENT_FUND"))
- })
- })
+ }
+
})
diff --git a/test/e2e/api_transactions_create.go b/test/e2e/api_transactions_create.go
index 0da493620..7512dcffb 100644
--- a/test/e2e/api_transactions_create.go
+++ b/test/e2e/api_transactions_create.go
@@ -3,6 +3,9 @@
package test_suite
import (
+ "math/big"
+ "time"
+
"github.com/formancehq/go-libs/v2/logging"
. "github.com/formancehq/go-libs/v2/testing/api"
ledger "github.com/formancehq/ledger/internal"
@@ -10,8 +13,6 @@ import (
. "github.com/formancehq/ledger/pkg/testserver"
"github.com/formancehq/stack/ledger/client/models/components"
"github.com/formancehq/stack/ledger/client/models/operations"
- "math/big"
- "time"
"github.com/formancehq/go-libs/v2/metadata"
"github.com/formancehq/go-libs/v2/pointer"
@@ -22,466 +23,504 @@ import (
)
var _ = Context("Ledger accounts list API tests", func() {
- var (
- db = UseTemplatedDatabase()
- ctx = logging.TestingContext()
- )
-
- testServer := NewTestServer(func() Configuration {
- return Configuration{
- PostgresConfiguration: db.GetValue().ConnectionOptions(),
- Output: GinkgoWriter,
- Debug: debug,
- NatsURL: natsServer.GetValue().ClientURL(),
- }
- })
-
- BeforeEach(func() {
- err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
- Ledger: "default",
- })
- Expect(err).To(BeNil())
-
- err = CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
- Ledger: "test",
- })
- Expect(err).To(BeNil())
- })
+ for _, data := range []struct {
+ description string
+ numscriptRewrite bool
+ }{
+ {"default", false},
+ {"numscript rewrite", true},
+ } {
+ Context(data.description, func() {
+ var (
+ db = UseTemplatedDatabase()
+ ctx = logging.TestingContext()
+ )
+ testServer := NewTestServer(func() Configuration {
+ return Configuration{
+ PostgresConfiguration: db.GetValue().ConnectionOptions(),
+ Output: GinkgoWriter,
+ Debug: debug,
+ NatsURL: natsServer.GetValue().ClientURL(),
+ ExperimentalNumscriptRewrite: data.numscriptRewrite,
+ }
+ })
- When("creating a transaction", func() {
- var (
- events chan *nats.Msg
- timestamp = time.Now().Round(time.Second).UTC()
- rsp *components.V2Transaction
- req operations.V2CreateTransactionRequest
- err error
- )
- BeforeEach(func() {
- events = testServer.GetValue().Subscribe()
- req = operations.V2CreateTransactionRequest{
- V2PostTransaction: components.V2PostTransaction{
- Timestamp: ×tamp,
- },
- Ledger: "default",
- }
- })
- JustBeforeEach(func() {
- // Create a transaction
- rsp, err = CreateTransaction(ctx, testServer.GetValue(), req)
- })
- Context("with valid data", func() {
BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Postings: []components.V2Posting{
- {
- Amount: big.NewInt(100),
- Asset: "USD",
- Source: "world",
- Destination: "alice",
- },
- },
- Timestamp: ×tamp,
- Reference: pointer.For("foo"),
- },
+ err := CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
Ledger: "default",
- }
+ })
+ Expect(err).To(BeNil())
+
+ err = CreateLedger(ctx, testServer.GetValue(), operations.V2CreateLedgerRequest{
+ Ledger: "test",
+ })
+ Expect(err).To(BeNil())
})
- It("should be ok", func() {
- response, err := GetTransaction(
- ctx,
- testServer.GetValue(),
- operations.V2GetTransactionRequest{
- Ledger: "default",
- ID: rsp.ID,
- Expand: pointer.For("volumes"),
- },
- )
- Expect(err).ToNot(HaveOccurred())
- Expect(*response).To(Equal(components.V2Transaction{
- Timestamp: rsp.Timestamp,
- InsertedAt: rsp.InsertedAt,
- Postings: rsp.Postings,
- Reference: rsp.Reference,
- Metadata: rsp.Metadata,
- ID: rsp.ID,
- PreCommitVolumes: map[string]map[string]components.V2Volume{
- "world": {
- "USD": {
- Input: big.NewInt(0),
- Output: big.NewInt(0),
- Balance: big.NewInt(0),
- },
+ When("creating a transaction", func() {
+ var (
+ events chan *nats.Msg
+ timestamp = time.Now().Round(time.Second).UTC()
+ rsp *components.V2Transaction
+ req operations.V2CreateTransactionRequest
+ err error
+ )
+ BeforeEach(func() {
+ events = testServer.GetValue().Subscribe()
+ req = operations.V2CreateTransactionRequest{
+ V2PostTransaction: components.V2PostTransaction{
+ Timestamp: ×tamp,
},
- "alice": {
- "USD": {
- Input: big.NewInt(0),
- Output: big.NewInt(0),
- Balance: big.NewInt(0),
+ Ledger: "default",
+ }
+ })
+ JustBeforeEach(func() {
+ // Create a transaction
+ rsp, err = CreateTransaction(ctx, testServer.GetValue(), req)
+ })
+ Context("with valid data", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{
+ {
+ Amount: big.NewInt(100),
+ Asset: "USD",
+ Source: "world",
+ Destination: "alice",
+ },
+ },
+ Timestamp: ×tamp,
+ Reference: pointer.For("foo"),
},
- },
- },
- PostCommitVolumes: map[string]map[string]components.V2Volume{
- "world": {
- "USD": {
- Input: big.NewInt(0),
- Output: big.NewInt(100),
- Balance: big.NewInt(-100),
+ Ledger: "default",
+ }
+ })
+ It("should be ok", func() {
+ response, err := GetTransaction(
+ ctx,
+ testServer.GetValue(),
+ operations.V2GetTransactionRequest{
+ Ledger: "default",
+ ID: rsp.ID,
+ Expand: pointer.For("volumes"),
},
- },
- "alice": {
- "USD": {
- Input: big.NewInt(100),
- Output: big.NewInt(0),
- Balance: big.NewInt(100),
+ )
+ Expect(err).ToNot(HaveOccurred())
+
+ Expect(*response).To(Equal(components.V2Transaction{
+ Timestamp: rsp.Timestamp,
+ InsertedAt: rsp.InsertedAt,
+ Postings: rsp.Postings,
+ Reference: rsp.Reference,
+ Metadata: rsp.Metadata,
+ ID: rsp.ID,
+ PreCommitVolumes: map[string]map[string]components.V2Volume{
+ "world": {
+ "USD": {
+ Input: big.NewInt(0),
+ Output: big.NewInt(0),
+ Balance: big.NewInt(0),
+ },
+ },
+ "alice": {
+ "USD": {
+ Input: big.NewInt(0),
+ Output: big.NewInt(0),
+ Balance: big.NewInt(0),
+ },
+ },
},
- },
- },
- }))
+ PostCommitVolumes: map[string]map[string]components.V2Volume{
+ "world": {
+ "USD": {
+ Input: big.NewInt(0),
+ Output: big.NewInt(100),
+ Balance: big.NewInt(-100),
+ },
+ },
+ "alice": {
+ "USD": {
+ Input: big.NewInt(100),
+ Output: big.NewInt(0),
+ Balance: big.NewInt(100),
+ },
+ },
+ },
+ }))
- account, err := GetAccount(
- ctx,
- testServer.GetValue(),
- operations.V2GetAccountRequest{
- Address: "alice",
- Ledger: "default",
- Expand: pointer.For("volumes"),
- },
- )
- Expect(err).ToNot(HaveOccurred())
+ account, err := GetAccount(
+ ctx,
+ testServer.GetValue(),
+ operations.V2GetAccountRequest{
+ Address: "alice",
+ Ledger: "default",
+ Expand: pointer.For("volumes"),
+ },
+ )
+ Expect(err).ToNot(HaveOccurred())
- Expect(*account).Should(Equal(components.V2Account{
- Address: "alice",
- Metadata: metadata.Metadata{},
- Volumes: map[string]components.V2Volume{
- "USD": {
- Input: big.NewInt(100),
- Output: big.NewInt(0),
- Balance: big.NewInt(100),
- },
- },
- }))
- By("should trigger a new event", func() {
- Eventually(events).Should(Receive(Event(ledgerevents.EventTypeCommittedTransactions, WithPayload(bus.CommittedTransactions{
- Ledger: "default",
- Transactions: []ledger.Transaction{ConvertSDKTxToCoreTX(rsp)},
- AccountMetadata: ledger.AccountMetadata{},
- }))))
+ Expect(*account).Should(Equal(components.V2Account{
+ Address: "alice",
+ Metadata: metadata.Metadata{},
+ Volumes: map[string]components.V2Volume{
+ "USD": {
+ Input: big.NewInt(100),
+ Output: big.NewInt(0),
+ Balance: big.NewInt(100),
+ },
+ },
+ }))
+ By("should trigger a new event", func() {
+ Eventually(events).Should(Receive(Event(ledgerevents.EventTypeCommittedTransactions, WithPayload(bus.CommittedTransactions{
+ Ledger: "default",
+ Transactions: []ledger.Transaction{ConvertSDKTxToCoreTX(rsp)},
+ AccountMetadata: ledger.AccountMetadata{},
+ }))))
+ })
+ })
+ When("using a reference", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Reference = pointer.For("foo")
+ })
+ It("should be ok", func() {
+ Expect(err).To(BeNil())
+ })
+ When("trying to commit a new transaction with the same reference", func() {
+ JustBeforeEach(func() {
+ _, err = CreateTransaction(ctx, testServer.GetValue(), req)
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumConflict)))
+ })
+ It("Should fail with "+string(components.V2ErrorsEnumConflict)+" error code", func() {})
+ })
+ })
})
- })
- When("using a reference", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Reference = pointer.For("foo")
+ When("with insufficient funds", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Amount: big.NewInt(100),
+ Asset: "USD",
+ Source: "bob",
+ Destination: "alice",
+ }}
+ })
+ It("should fail", func() {
+ var expectedErr string
+ if data.numscriptRewrite {
+ expectedErr = string(components.V2ErrorsEnumInterpreterRuntime)
+ } else {
+ expectedErr = string(components.V2ErrorsEnumInsufficientFund)
+ }
+
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(expectedErr))
+ })
})
- It("should be ok", func() {
- Expect(err).To(BeNil())
+ When("with nil amount", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Asset: "USD",
+ Source: "bob",
+ Destination: "alice",
+ }}
+ })
+ It("should fail", func() {
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
+ })
})
- When("trying to commit a new transaction with the same reference", func() {
- JustBeforeEach(func() {
- _, err = CreateTransaction(ctx, testServer.GetValue(), req)
+ When("with negative amount", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Amount: big.NewInt(-100),
+ Asset: "USD",
+ Source: "bob",
+ Destination: "alice",
+ }}
+ })
+ It("should fail", func() {
Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumConflict)))
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
})
- It("Should fail with "+string(components.V2ErrorsEnumConflict)+" error code", func() {})
})
- })
- })
- When("with insufficient funds", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Amount: big.NewInt(100),
- Asset: "USD",
- Source: "bob",
- Destination: "alice",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumInsufficientFund)))
- })
- })
- When("with nil amount", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Asset: "USD",
- Source: "bob",
- Destination: "alice",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
- })
- })
- When("with negative amount", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Amount: big.NewInt(-100),
- Asset: "USD",
- Source: "bob",
- Destination: "alice",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
- })
- })
- When("with invalid source address", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Amount: big.NewInt(-100),
- Asset: "USD",
- Source: "bob;test",
- Destination: "alice",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
- })
- })
- When("with invalid destination address", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Amount: big.NewInt(-100),
- Asset: "USD",
- Source: "bob",
- Destination: "alice;test",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
- })
- })
- When("with invalid asset", func() {
- BeforeEach(func() {
- req.V2PostTransaction.Postings = []components.V2Posting{{
- Amount: big.NewInt(-100),
- Asset: "USD//2",
- Source: "bob",
- Destination: "alice",
- }}
- })
- It("should fail", func() {
- Expect(err).To(HaveOccurred())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
- })
- })
- When("using an idempotency key and a specific ledger", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("foo"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Postings: []components.V2Posting{
- {
- Amount: big.NewInt(100),
- Asset: "USD",
- Source: "world",
- Destination: "alice",
- },
- },
- Timestamp: ×tamp,
- Reference: pointer.For("foo"),
- },
- Ledger: "default",
- }
- })
- It("should be ok", func() {
- Expect(err).To(Succeed())
- Expect(rsp.ID).To(Equal(big.NewInt(1)))
- })
- When("creating a ledger transaction with same ik and different ledger", func() {
- JustBeforeEach(func() {
- rsp, err = CreateTransaction(ctx, testServer.GetValue(), req)
+ When("with invalid source address", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Amount: big.NewInt(-100),
+ Asset: "USD",
+ Source: "bob;test",
+ Destination: "alice",
+ }}
+ })
+ It("should fail", func() {
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
+ })
})
- It("should not have an error", func() {
- Expect(err).To(Succeed())
- Expect(rsp.ID).To(Equal(big.NewInt(1)))
+ When("with invalid destination address", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Amount: big.NewInt(-100),
+ Asset: "USD",
+ Source: "bob",
+ Destination: "alice;test",
+ }}
+ })
+ It("should fail", func() {
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
+ })
})
- })
- })
- When("using a negative amount in a script", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Script: &components.V2PostTransactionScript{
- Plain: `send [COIN -100] (
+ When("with invalid asset", func() {
+ BeforeEach(func() {
+ req.V2PostTransaction.Postings = []components.V2Posting{{
+ Amount: big.NewInt(-100),
+ Asset: "USD//2",
+ Source: "bob",
+ Destination: "alice",
+ }}
+ })
+ It("should fail", func() {
+ Expect(err).To(HaveOccurred())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation)))
+ })
+ })
+ When("using an idempotency key and a specific ledger", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("foo"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Postings: []components.V2Posting{
+ {
+ Amount: big.NewInt(100),
+ Asset: "USD",
+ Source: "world",
+ Destination: "alice",
+ },
+ },
+ Timestamp: ×tamp,
+ Reference: pointer.For("foo"),
+ },
+ Ledger: "default",
+ }
+ })
+ It("should be ok", func() {
+ Expect(err).To(Succeed())
+ Expect(rsp.ID).To(Equal(big.NewInt(1)))
+ })
+ When("creating a ledger transaction with same ik and different ledger", func() {
+ JustBeforeEach(func() {
+ rsp, err = CreateTransaction(ctx, testServer.GetValue(), req)
+ })
+ It("should not have an error", func() {
+ Expect(err).To(Succeed())
+ Expect(rsp.ID).To(Equal(big.NewInt(1)))
+ })
+ })
+ })
+ When("using a negative amount in a script", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Script: &components.V2PostTransactionScript{
+ Plain: `send [COIN -100] (
source = @world
destination = @bob
)`,
- Vars: map[string]interface{}{},
- },
- },
- Ledger: "default",
- }
- })
- It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() {
- Expect(err).NotTo(Succeed())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed)))
- })
- })
- When("using a negative amount in the script with a variable", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Script: &components.V2PostTransactionScript{
- Plain: `vars {
+ Vars: map[string]interface{}{},
+ },
+ },
+ Ledger: "default",
+ }
+ })
+
+ var expectedErr string
+ if data.numscriptRewrite {
+ expectedErr = string(components.V2ErrorsEnumInterpreterRuntime)
+ } else {
+ expectedErr = string(components.V2ErrorsEnumCompilationFailed)
+ }
+
+ It("should fail with "+expectedErr+" code", func() {
+ Expect(err).NotTo(Succeed())
+ Expect(err).To(HaveErrorCode(expectedErr))
+ })
+ })
+ When("using a negative amount in the script with a variable", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Script: &components.V2PostTransactionScript{
+ Plain: `vars {
monetary $amount
}
send $amount (
source = @world
destination = @bob
)`,
- Vars: map[string]interface{}{
- "amount": "USD -100",
+ Vars: map[string]interface{}{
+ "amount": "USD -100",
+ },
+ },
},
- },
- },
- Ledger: "default",
- }
- })
- It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() {
- Expect(err).NotTo(Succeed())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed)))
- })
- })
- Context("with error on script", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Script: &components.V2PostTransactionScript{
- Plain: `XXX`,
- Vars: map[string]interface{}{},
- },
- },
- Ledger: "default",
- }
- })
- It("should fail with "+string(components.V2ErrorsEnumCompilationFailed)+" code", func() {
- Expect(err).NotTo(Succeed())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumCompilationFailed)))
- })
- })
- Context("with no postings", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Script: &components.V2PostTransactionScript{
- Plain: `vars {
+ Ledger: "default",
+ }
+ })
+
+ var expectedErr string
+ if data.numscriptRewrite {
+ expectedErr = string(components.V2ErrorsEnumInterpreterRuntime)
+ } else {
+ expectedErr = string(components.V2ErrorsEnumCompilationFailed)
+ }
+ It("should fail with "+expectedErr+" code", func() {
+ Expect(err).NotTo(Succeed())
+ Expect(err).To(HaveErrorCode(expectedErr))
+ })
+ })
+ Context("with error on script", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Script: &components.V2PostTransactionScript{
+ Plain: `XXX`,
+ Vars: map[string]interface{}{},
+ },
+ },
+ Ledger: "default",
+ }
+ })
+ var expectedErr string
+ if data.numscriptRewrite {
+ expectedErr = string(components.V2ErrorsEnumInterpreterParse)
+ } else {
+ expectedErr = string(components.V2ErrorsEnumCompilationFailed)
+ }
+ It("should fail with "+expectedErr+" code", func() {
+ Expect(err).NotTo(Succeed())
+ Expect(err).To(HaveErrorCode(expectedErr))
+ })
+ })
+ Context("with no postings", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Script: &components.V2PostTransactionScript{
+ Plain: `vars {
monetary $amount
}
set_tx_meta("foo", "bar")
`,
- Vars: map[string]interface{}{
- "amount": "USD 100",
+ Vars: map[string]interface{}{
+ "amount": "USD 100",
+ },
+ },
},
- },
- },
- Ledger: "default",
- }
- })
- It("should fail with "+string(components.V2ErrorsEnumNoPostings)+" code", func() {
- Expect(err).NotTo(Succeed())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumNoPostings)))
- })
- })
- When("with metadata override", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{
- "foo": "baz",
- },
- Script: &components.V2PostTransactionScript{
- Plain: `send [COIN 100] (
+ Ledger: "default",
+ }
+ })
+ It("should fail with "+string(components.V2ErrorsEnumNoPostings)+" code", func() {
+ Expect(err).NotTo(Succeed())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumNoPostings)))
+ })
+ })
+ When("with metadata override", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{
+ "foo": "baz",
+ },
+ Script: &components.V2PostTransactionScript{
+ Plain: `send [COIN 100] (
source = @world
destination = @bob
)
set_tx_meta("foo", "bar")`,
- Vars: map[string]interface{}{},
- },
- },
- Ledger: "default",
- }
- })
- It("should fail with "+string(components.V2ErrorsEnumMetadataOverride)+" code", func() {
- Expect(err).NotTo(Succeed())
- Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumMetadataOverride)))
- })
- })
- When("with dry run mode", func() {
- BeforeEach(func() {
- req = operations.V2CreateTransactionRequest{
- IdempotencyKey: pointer.For("testing"),
- V2PostTransaction: components.V2PostTransaction{
- Metadata: map[string]string{},
- Script: &components.V2PostTransactionScript{
- Plain: `send [COIN 100] (
+ Vars: map[string]interface{}{},
+ },
+ },
+ Ledger: "default",
+ }
+ })
+ It("should fail with "+string(components.V2ErrorsEnumMetadataOverride)+" code", func() {
+ Expect(err).NotTo(Succeed())
+ Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumMetadataOverride)))
+ })
+ })
+ When("with dry run mode", func() {
+ BeforeEach(func() {
+ req = operations.V2CreateTransactionRequest{
+ IdempotencyKey: pointer.For("testing"),
+ V2PostTransaction: components.V2PostTransaction{
+ Metadata: map[string]string{},
+ Script: &components.V2PostTransactionScript{
+ Plain: `send [COIN 100] (
source = @world
destination = @bob
)`,
- Vars: map[string]interface{}{},
- },
- },
- DryRun: pointer.For(true),
- Ledger: "default",
- }
- })
- It("should be ok", func() {
- Expect(err).To(BeNil())
+ Vars: map[string]interface{}{},
+ },
+ },
+ DryRun: pointer.For(true),
+ Ledger: "default",
+ }
+ })
+ It("should be ok", func() {
+ Expect(err).To(BeNil())
+ })
+ })
})
- })
- })
- When("creating a transaction on the ledger v1 with old variable format", func() {
- var (
- err error
- response *operations.CreateTransactionResponse
- )
- BeforeEach(func() {
- v, _ := big.NewInt(0).SetString("1320000000000000000000000000000000000000000000000001", 10)
- response, err = testServer.GetValue().Client().Ledger.V1.CreateTransaction(
- ctx,
- operations.CreateTransactionRequest{
- PostTransaction: components.PostTransaction{
- Metadata: map[string]any{},
- Script: &components.PostTransactionScript{
- Plain: `vars {
+ When("creating a transaction on the ledger v1 with old variable format", func() {
+ var (
+ err error
+ response *operations.CreateTransactionResponse
+ )
+ BeforeEach(func() {
+ v, _ := big.NewInt(0).SetString("1320000000000000000000000000000000000000000000000001", 10)
+ response, err = testServer.GetValue().Client().Ledger.V1.CreateTransaction(
+ ctx,
+ operations.CreateTransactionRequest{
+ PostTransaction: components.PostTransaction{
+ Metadata: map[string]any{},
+ Script: &components.PostTransactionScript{
+ Plain: `vars {
monetary $amount
}
send $amount (
source = @world
destination = @bob
)`,
- Vars: map[string]interface{}{
- "amount": map[string]any{
- "asset": "EUR/12",
- "amount": v,
+ Vars: map[string]interface{}{
+ "amount": map[string]any{
+ "asset": "EUR/12",
+ "amount": v,
+ },
+ },
},
},
+ Ledger: "default",
},
- },
- Ledger: "default",
- },
- )
- })
- It("should be ok", func() {
- Expect(err).To(Succeed())
- Expect(response.TransactionsResponse.Data[0].Txid).To(Equal(big.NewInt(1)))
+ )
+ })
+ It("should be ok", func() {
+ Expect(err).To(Succeed())
+ Expect(response.TransactionsResponse.Data[0].Txid).To(Equal(big.NewInt(1)))
+ })
+ })
})
- })
+ }
})