Skip to content

Conversation

@louisgthier
Copy link
Contributor

Exported fields of Int to enable encoding and decoding with the encoding/gob, encoding/json... packages.

type Int struct {
abs *uint256.Int
neg bool
Abs *uint256.Int
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we put it public?
We don't want to leak and access these components from outside or other places that will use this package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing the Int struct fields to be exported enables serialization with packages such as Gob, JSON, XML, etc. If the fields aren’t exported, serialization will result in an empty struct because these packages cannot access unexported fields.

I believe that having exported fields isn't an issue since their behavior is straightforward. In the uint256 library, the four uint64 fields that make up the type are also exported and can be modified outside of the package.

// Int is represented as an array of 4 uint64, in little-endian order,
// so that Int[3] is the most significant, and Int[0] is the least significant
type Int [4]uint64

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @louisgthier
Can you give me an example for this case?
Allowing the Int struct fields to be exported enables serialization with packages such as Gob, JSON, XML, etc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing the Int struct fields to be exported enables serialization with packages such as Gob, JSON, XML, etc. If the fields aren’t exported, serialization will result in an empty struct because these packages cannot access unexported fields.

I believe that having exported fields isn't an issue since their behavior is straightforward. In the uint256 library, the four uint64 fields that make up the type are also exported and can be modified outside of the package.

// Int is represented as an array of 4 uint64, in little-endian order,
// so that Int[3] is the most significant, and Int[0] is the least significant
type Int [4]uint64

Regarding the Json serialization:

  • To allow a struct to be serialized to JSON or unserialized from JSON in Go, you generally need to create methods that implement the Marshaler and Unmarshaler interfaces defined in the encoding/json package. So no need to expose those fields, the implemention of those interfaces is enough.

Almost sure something simillar exit for other XML, Gob etc format but I'm not familiar with those.

@louisgthier
Copy link
Contributor Author

Here is an example of JSON serialization with and without exporting fields of int256.Int for some UniswapV3Pool struct:

pool := samplePools[0]
data, err := json.MarshalIndent(pool, "", "  ")
if err != nil {
t.Errorf("Error marshalling pool to JSON: %v", err)
}

fmt.Println(string(data))

When exporting fields:

{
  "PoolAddress": "0x0ce541eac2adf14c8eeeb36d588a5db21df9e6c6",
  "FactoryAddress": "0x0000000000000000000000000000000000000000",
  "ChainID": null,
  "DEX": "",
  "Token0": "0x088cd8f5ef3652623c22d48b1605dcfe860cd704",
  "Token1": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
  "Liquidity": 4600564526560966222516,
  "CreationBlock": null,
  "Fee": 3000,
  "TickSpacing": 60,
  "SqrtPriceX96": 1270572484863045909758718483,
  "Tick": -82662,
  "Ticks": {
    "-82620": {
      "LiquidityGross": null,
      "LiquidityNet": {
        "Abs": "0",
        "Neg": false
      },
      "Initialized": true
    },
    "-82680": {
      "LiquidityGross": null,
      "LiquidityNet": {
        "Abs": "0",
        "Neg": false
      },
      "Initialized": true
    },
    "-82740": {
      "LiquidityGross": null,
      "LiquidityNet": {
        "Abs": "0",
        "Neg": false
      },
      "Initialized": true
    }
  },
  "TickBitmap": {
    "-3": "166153499473114484112975882535043072",
    "-4": "2961817721679888005506645162940264410513424",
    "-5": "339234636759968684613029315482494289238586323288673951314139903554586943488",
    "-6": "579533279354731026327679709428062086880823104002250405372500346022729351170",
    "-7": "73786976294838206464",
    "4": "46768052394588893382517914646921056628989841375232",
    "6": "1"
  }
}

When keeping them private:

{
  "PoolAddress": "0x0ce541eac2adf14c8eeeb36d588a5db21df9e6c6",
  "FactoryAddress": "0x0000000000000000000000000000000000000000",
  "ChainID": null,
  "DEX": "",
  "Token0": "0x088cd8f5ef3652623c22d48b1605dcfe860cd704",
  "Token1": "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
  "Liquidity": 4600564526560966222516,
  "CreationBlock": null,
  "Fee": 3000,
  "TickSpacing": 60,
  "SqrtPriceX96": 1270572484863045909758718483,
  "Tick": -82662,
  "Ticks": {
    "-82620": {
      "LiquidityGross": null,
      "LiquidityNet": {},
      "Initialized": true
    },
    "-82680": {
      "LiquidityGross": null,
      "LiquidityNet": {},
      "Initialized": true
    },
    "-82740": {
      "LiquidityGross": null,
      "LiquidityNet": {},
      "Initialized": true
    }
  },
  "TickBitmap": {
    "-3": "166153499473114484112975882535043072",
    "-4": "2961817721679888005506645162940264410513424",
    "-5": "339234636759968684613029315482494289238586323288673951314139903554586943488",
    "-6": "579533279354731026327679709428062086880823104002250405372500346022729351170",
    "-7": "73786976294838206464",
    "4": "46768052394588893382517914646921056628989841375232",
    "6": "1"
  }
}

The only *int256.Int here is LiquidityNet in the Ticks, and as you can see they are lost when fields are not exported.

Same thing happens with Gob:

x := int256.NewInt(5)
gob.Register(int256.Int{})
w := new(bytes.Buffer)
encoder := gob.NewEncoder(w)
encoder.Encode(x)

r := bytes.NewBuffer(w.Bytes())
decoder := gob.NewDecoder(r)
var y int256.Int
decoder.Decode(&y)

fmt.Println(y)

With exported fields:

{5 false}

Without exported fields:

{<nil> false}

@0xsimulacra
Copy link
Contributor

0xsimulacra commented Jun 4, 2024

I've already worked on UniswapV2 data and faced the same problem:

  • For encoding Json, it can be solved by implementing the MarshalJSON() interface.
  • From decoding Json, it can be solved by implementing the UnmarshalJSON(input []byte) interface, as long as the json fields are tagged that they are of type int256.Int.

I'm wsorking on a pull request that will add those two interfaces, in addition to MarshalText() and UnmarshalText(input []byte) interface.

Edit: I just push the pull request, not sure if you can pull it and see if it solve the json encoding/decoding problem for you.

@linhbkhn95
Copy link
Owner

@louisgthier As I understand, we need to implement JSON marshaller/unmarshaller functions.

@0xsimulacra
Copy link
Contributor

0xsimulacra commented Jun 5, 2024

@louisgthier As I understand, we need to implement JSON marshaller/unmarshaller functions.

I've added those functions in my last pull request #14. And included an example of Json marshalling/unmarshalling working correctly as intended without exposing those fields.

```go
type TickInfo struct {
    Initialized  bool
    LiquidityNet *int256.Int
}

type Pool struct {
    PoolAddress *common.Address
    Ticks       map[int]*TickInfo
}

func main() {
    tick1Info := &TickInfo{
        Initialized:  true,
        LiquidityNet: int256.MustFromDecimal("-111000000000000000000000000000000000099990"),
    }

    tick2Info := &TickInfo{
        Initialized:  true,
        LiquidityNet: int256.MustFromDecimal("1110000000000440000000000000000000000099990"),
    }
    ticks := make(map[int]*TickInfo)
    ticks[100] = tick2Info
    ticks[-100] = tick1Info
    poolAddr := common.HexToAddress("0x0000000000000000000000000000000000000000")
    pool := &Pool{
        PoolAddress: &poolAddr,
        Ticks:       ticks,
    }
    data, err := json.Marshal(pool)
    if err != nil {
        fmt.Println(err)
    } else {
        // {"PoolAddress":"0x0000000000000000000000000000000000000000","Ticks":{"-100":{"Initialized":true,"LiquidityNet":"-111000000000000000000000000000000000099990"},"100":{"Initialized":true,"LiquidityNet":"1110000000000440000000000000000000000099990"}}}
        fmt.Println(string(data))
    }
    newPool := &Pool{}
    poolData := "{\"PoolAddress\":\"0x0000000000000000000000000000000000000000\",\"Ticks\":{\"-100\":{\"Initialized\":true,\"LiquidityNet\":\"-111000000000000000000000000000000000099990\"},\"100\":{\"Initialized\":true,\"LiquidityNet\":\"1110000000000440000000000000000000000099990\"}}}"
    err = json.Unmarshal([]byte(poolData), &newPool)
    if err != nil {
        fmt.Println(err)
    } else {
		// -111000000000000000000000000000000000099990
        fmt.Println(newPool.Ticks[-100].LiquidityNet.String())
		// 1110000000000440000000000000000000000099990
        fmt.Println(newPool.Ticks[100].LiquidityNet.String())
    }
}

Not Familiar with Gob tho to try and implement the methods needed for encoding/decoding that format.

@linhbkhn95
Copy link
Owner

@louisgthier can u tell me the progress for this PR?

@louisgthier
Copy link
Contributor Author

@louisgthier can u tell me the progress for this PR?

I have been using exported fields in my fork for simplicity but I just took a look at how decimal.Decimal handles Gob encoding/decoding and I'll try to do the same.

It will probably be in another PR though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants