Skip to content

Commit 45431a7

Browse files
test(integration): add integration test for amm transactions (#250)
1 parent 4d49c81 commit 45431a7

File tree

24 files changed

+1750
-12
lines changed

24 files changed

+1750
-12
lines changed

CHANGELOG.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [v0.1.18]
99

1010
### Added
1111

@@ -38,13 +38,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3838
- Added integration test for NFT transaction `NFTModify`
3939
- Added integration tests for MPT transactions `MPTokenAuthorize`, `MPTokenIssuanceCreate`, `MPTokenIssuanceDestroy` and `MPTokenIssuanceSet`
4040
- Added `RippleTimeToUnixSeconds` function
41+
- Added `GetAMMInfo` query for both RPC and WebSocket clients
42+
- Added unit tests for `amm_info` request and response serialization
43+
- Added integration test for amm transactions
44+
- Added `pkg/decodehook` package with shared `JSON()` decode hook for `mapstructure`
45+
- Added `hash.PaymentChannel()` function to compute payment channel ID from source, destination, and sequence
46+
- Added `hash.MPTID()` function to compute MPT ID from sequence and issuer
47+
- Added `ObjectType` constants for `DID`, `MPToken`, `MPTIssuance`, `Oracle`, `PermissionedDomain`, and `Vault` in account objects query
4148

4249
### Changed
4350

4451
#### Makefile
4552

4653
- Changed localnet rippled image to `develop`
4754
- Exposed RPC port in localnet command
55+
- Use `gotest` (colorized output) with fallback to `go test`
4856

4957
### Fixed
5058

@@ -54,6 +62,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5462
- Validate `MPTokenMetadata` length (max 1024 bytes) in `MPTokenIssuanceCreate` (previously only checked hex format).
5563
- Reject `MPTokenIssuanceSet` when `Holder` equals `Account` (`temMALFORMED` per rippled spec).
5664
- Validate `MPTokenIssuanceID` is valid hexadecimal in `MPTokenIssuanceSet`, `MPTokenIssuanceDestroy`, and `MPTokenAuthorize` (previously only checked non-empty).
65+
- `PaymentChannelCreate.Flatten()` and `PaymentChannelFund.Flatten()` now set `TransactionType` in flattened output.
66+
67+
#### binary-codec
68+
69+
- `UInt64` type now validates hex string length (max 16 chars) before padding, preventing silent overflow.
70+
71+
#### xrpl/websocket
72+
73+
- `GetResult` now composes a `jsonUnmarshalerHookFunc` alongside the existing `TextUnmarshallerHookFunc`, so any target type implementing `json.Unmarshaler` is decoded via its own `UnmarshalJSON` rather than by mapstructure directly.
74+
75+
#### xrpl/queries/server/types
76+
77+
- `State.ValidatorListExpires` remains a `string`; a custom `UnmarshalJSON` on `State` now accepts both a JSON string and a JSON number for that field, converting the number to its string representation. This fixes a crash when rippled returns `0` for `validator_list_expires` over WebSocket.
78+
79+
#### xrpl/ledger-entry-types
80+
81+
- Fixed `AuthAccount.Flatten()` storing `types.Address` without converting to `string`, causing binary codec serialization to fail.
82+
83+
#### Makefile
84+
85+
- Corrected localnet setup to automatically create ledgers periodically
5786

5887
### Removed
5988

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ test-ci:
6868

6969
run-localnet-linux/arm64:
7070
@echo "Running localnet..."
71-
@docker run -p 6006:6006 -p 5005:5005 --rm -it -d --platform linux/arm64 --name rippled_standalone --volume $(PWD)/.ci-config:/etc/opt/ripple/ --entrypoint bash $(RIPPLED_IMAGE) -c 'rippled -a'
71+
@docker run -p 6006:6006 -p 5005:5005 --rm -it -d --platform linux/arm64 --name rippled_standalone --volume $(PWD)/.ci-config:/etc/opt/ripple/ --entrypoint bash $(RIPPLED_IMAGE) -c 'mkdir -p /var/lib/rippled/db/ && rippled -a --start & sleep 5 && while true; do rippled ledger_accept; sleep 1; done'
7272
@echo "Localnet running!"
7373

7474
run-localnet-linux/amd64:
7575
@echo "Running localnet..."
76-
@docker run -p 6006:6006 -p 5005:5005 --rm -it -d --platform linux/amd64 --name rippled_standalone --volume $(PWD)/.ci-config:/etc/opt/ripple/ --entrypoint bash $(RIPPLED_IMAGE) -c 'rippled -a'
76+
@docker run -p 6006:6006 -p 5005:5005 --rm -it -d --platform linux/amd64 --name rippled_standalone --volume $(PWD)/.ci-config:/etc/opt/ripple/ --entrypoint bash $(RIPPLED_IMAGE) -c 'mkdir -p /var/lib/rippled/db/ && rippled -a --start & sleep 5 && while true; do rippled ledger_accept; sleep 1; done'
7777
@echo "Localnet running!"
7878

7979
test-integration-localnet:
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
title: v0.1.18
3+
---
4+
5+
### Added
6+
7+
#### xrpl
8+
9+
- Added method `GetAccountObjects` and `GetAccountLines` to testutil `client` interface
10+
- Added integration tests for `TrustSet` transaction
11+
- Added Dynamic MPT support for `MPTokenIssuanceCreate`:
12+
- `MutableFlags` field to declare which properties can be mutated after creation.
13+
- `DomainID` field to associate a permissioned domain (requires `TfMPTRequireAuth` flag).
14+
- MutableFlags constants: `TmfMPTCanMutateCanLock`, `TmfMPTCanMutateRequireAuth`, `TmfMPTCanMutateCanEscrow`, `TmfMPTCanMutateCanTrade`, `TmfMPTCanMutateCanTransfer`, `TmfMPTCanMutateCanClawback`, `TmfMPTCanMutateMetadata`, `TmfMPTCanMutateTransferFee`.
15+
- Flag setter methods for all mutable flags.
16+
- Added Dynamic MPT support for `MPTokenIssuanceSet`:
17+
- `MutableFlags`, `MPTokenMetadata`, `TransferFee`, and `DomainID` fields for post-creation mutation.
18+
- MutableFlags set/clear constant pairs: `TmfMPTSetCanLock`/`TmfMPTClearCanLock`, `TmfMPTSetRequireAuth`/`TmfMPTClearRequireAuth`, `TmfMPTSetCanEscrow`/`TmfMPTClearCanEscrow`, `TmfMPTSetCanTrade`/`TmfMPTClearCanTrade`, `TmfMPTSetCanTransfer`/`TmfMPTClearCanTransfer`, `TmfMPTSetCanClawback`/`TmfMPTClearCanClawback`.
19+
- Flag setter methods for all set/clear mutable flags.
20+
- Validation: mutual exclusivity between `Holder`/`Flags` and DynamicMPT fields, set/clear conflict detection, `TransferFee` + `ClearCanTransfer` conflict, `DomainID` format validation, no-op transaction detection.
21+
- Added `MutableFlags` and `DomainID` fields to `MPTokenIssuance` ledger entry type with ledger-state mutable flags constants (`Lsmf` prefix) and flag setter methods.
22+
- Added `MutableFlags` helper function in `types` package.
23+
- Added integration tests for account transactions `AccountSet` and `AccountDelete`
24+
- Added integration test for permissioned domain transactions
25+
- Added integration test for check transactions `CheckCreate`, `CheckCash` and `CheckCancel`
26+
- Added integration tests for did transactions `DIDSet` and `DIDDelete`
27+
- Added integration test for credential transactions `CredentialAccept` and `CredentialDelete`
28+
- Added integration test for `DepositPreauth` transaction
29+
- Added integration test for escrow transactions.
30+
- Added integration test for payment and payment channels transactions.
31+
- Added integration test for vault transactions
32+
- Added integration test for oracle transactions `OracleSet` and `OracleDelete`
33+
- Added integration test for NFT transaction `NFTModify`
34+
- Added integration tests for MPT transactions `MPTokenAuthorize`, `MPTokenIssuanceCreate`, `MPTokenIssuanceDestroy` and `MPTokenIssuanceSet`
35+
- Added `RippleTimeToUnixSeconds` function
36+
- Added `GetAMMInfo` query for both RPC and WebSocket clients
37+
- Added unit tests for `amm_info` request and response serialization
38+
- Added integration test for amm transactions
39+
- Added `pkg/decodehook` package with shared `JSON()` decode hook for `mapstructure`
40+
- Added `hash.PaymentChannel()` function to compute payment channel ID from source, destination, and sequence
41+
- Added `hash.MPTID()` function to compute MPT ID from sequence and issuer
42+
- Added `ObjectType` constants for `DID`, `MPToken`, `MPTIssuance`, `Oracle`, `PermissionedDomain`, and `Vault` in account objects query
43+
44+
### Changed
45+
46+
#### Makefile
47+
48+
- Changed localnet rippled image to `develop`
49+
- Exposed RPC port in localnet command
50+
- Use `gotest` (colorized output) with fallback to `go test`
51+
52+
### Fixed
53+
54+
#### xrpl
55+
56+
- Validate `DomainID` is valid hexadecimal in `IsDomainID` check (previously only checked length).
57+
- Validate `MPTokenMetadata` length (max 1024 bytes) in `MPTokenIssuanceCreate` (previously only checked hex format).
58+
- Reject `MPTokenIssuanceSet` when `Holder` equals `Account` (`temMALFORMED` per rippled spec).
59+
- Validate `MPTokenIssuanceID` is valid hexadecimal in `MPTokenIssuanceSet`, `MPTokenIssuanceDestroy`, and `MPTokenAuthorize` (previously only checked non-empty).
60+
- `PaymentChannelCreate.Flatten()` and `PaymentChannelFund.Flatten()` now set `TransactionType` in flattened output.
61+
62+
#### binary-codec
63+
64+
- `UInt64` type now validates hex string length (max 16 chars) before padding, preventing silent overflow.
65+
66+
#### xrpl/websocket
67+
68+
- `GetResult` now composes a `jsonUnmarshalerHookFunc` alongside the existing `TextUnmarshallerHookFunc`, so any target type implementing `json.Unmarshaler` is decoded via its own `UnmarshalJSON` rather than by mapstructure directly.
69+
70+
#### xrpl/queries/server/types
71+
72+
- `State.ValidatorListExpires` remains a `string`; a custom `UnmarshalJSON` on `State` now accepts both a JSON string and a JSON number for that field, converting the number to its string representation. This fixes a crash when rippled returns `0` for `validator_list_expires` over WebSocket.
73+
74+
#### xrpl/ledger-entry-types
75+
76+
- Fixed `AuthAccount.Flatten()` storing `types.Address` without converting to `string`, causing binary codec serialization to fail.
77+
78+
#### Makefile
79+
80+
- Corrected localnet setup to automatically create ledgers periodically
81+
82+
### Removed
83+
84+
#### xrpl/transaction
85+
86+
- Removed integration tests for obsolete transactions `Batch` and `DelegateSet`

pkg/decodehook/hook.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Package decodehook provides shared decode hooks for mapstructure.
2+
package decodehook
3+
4+
import (
5+
"encoding/json"
6+
"reflect"
7+
8+
"github.com/mitchellh/mapstructure"
9+
)
10+
11+
var jsonUnmarshalerType = reflect.TypeFor[json.Unmarshaler]()
12+
13+
// JSON returns a mapstructure decode hook that delegates to json.Unmarshaler
14+
// for any target type that implements it. The source value is re-encoded to JSON
15+
// and then passed to the target's UnmarshalJSON method, which lets individual
16+
// types define their own flexible decoding logic (e.g. accepting both strings
17+
// and numbers for a field that is modelled as a string in Go).
18+
func JSON() mapstructure.DecodeHookFuncValue {
19+
return func(from reflect.Value, to reflect.Value) (any, error) {
20+
toType := to.Type()
21+
if toType.Kind() != reflect.Pointer {
22+
toType = reflect.PointerTo(toType)
23+
}
24+
if !toType.Implements(jsonUnmarshalerType) {
25+
return from.Interface(), nil
26+
}
27+
28+
// Re-encode the source (a map[string]any or similar) to JSON bytes.
29+
jsonBytes, err := json.Marshal(from.Interface())
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
// Allocate a new value of the concrete (non-pointer) target type and
35+
// unmarshal into it via the custom UnmarshalJSON.
36+
target := reflect.New(to.Type())
37+
if err := json.Unmarshal(jsonBytes, target.Interface()); err != nil {
38+
return nil, err
39+
}
40+
return target.Elem().Interface(), nil
41+
}
42+
}

pkg/decodehook/hook_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package decodehook
2+
3+
import (
4+
"encoding/json"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/mitchellh/mapstructure"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
// customField is a test type that implements json.Unmarshaler.
14+
// It accepts both JSON strings and numbers, always storing the value as a string.
15+
type customField struct {
16+
Value string
17+
}
18+
19+
func (c *customField) UnmarshalJSON(data []byte) error {
20+
if data[0] == '"' {
21+
return json.Unmarshal(data, &c.Value)
22+
}
23+
c.Value = string(data)
24+
return nil
25+
}
26+
27+
func TestJSON(t *testing.T) {
28+
type target struct {
29+
Field customField `json:"field"`
30+
}
31+
32+
tests := []struct {
33+
name string
34+
input map[string]any
35+
expected string
36+
expectErr bool
37+
}{
38+
{
39+
name: "delegates to UnmarshalJSON with string value",
40+
input: map[string]any{"field": "hello"},
41+
expected: "hello",
42+
},
43+
{
44+
name: "delegates to UnmarshalJSON with numeric value",
45+
input: map[string]any{"field": json.Number("42")},
46+
expected: "42",
47+
},
48+
}
49+
50+
for _, tc := range tests {
51+
t.Run(tc.name, func(t *testing.T) {
52+
var result target
53+
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
54+
TagName: "json",
55+
Result: &result,
56+
DecodeHook: JSON(),
57+
})
58+
require.NoError(t, err)
59+
err = dec.Decode(tc.input)
60+
require.NoError(t, err)
61+
assert.Equal(t, tc.expected, result.Field.Value)
62+
})
63+
}
64+
65+
t.Run("skips types that do not implement json.Unmarshaler", func(t *testing.T) {
66+
hook := JSON()
67+
from := reflect.ValueOf("plain")
68+
to := reflect.New(reflect.TypeFor[string]()).Elem()
69+
out, err := hook(from, to)
70+
require.NoError(t, err)
71+
assert.Equal(t, "plain", out)
72+
})
73+
74+
t.Run("returns error when source cannot be marshaled", func(t *testing.T) {
75+
hook := JSON()
76+
from := reflect.ValueOf(make(chan int))
77+
to := reflect.New(reflect.TypeFor[customField]()).Elem()
78+
_, err := hook(from, to)
79+
require.Error(t, err)
80+
})
81+
}

xrpl/ledger-entry-types/amm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ type AuthAccount struct {
126126
// Flatten returns the flattened representation of AuthAccount.
127127
func (a *AuthAccount) Flatten() map[string]any {
128128
flattened := make(map[string]any)
129-
flattened["Account"] = a.Account
129+
flattened["Account"] = a.Account.String()
130130
return flattened
131131
}
132132

0 commit comments

Comments
 (0)