diff --git a/sdk-clients/orderbook/api.go b/sdk-clients/orderbook/api.go index 5cf09bb..81bbe95 100644 --- a/sdk-clients/orderbook/api.go +++ b/sdk-clients/orderbook/api.go @@ -8,17 +8,10 @@ import ( "github.com/1inch/1inch-sdk-go/common" ) -// Empty Extensions for Orderbook API are represented as 0x instead of a blank string -const emptyExtension = "0x" - // CreateOrder creates an order in the Limit Order Protocol func (api *api) CreateOrder(ctx context.Context, params CreateOrderParams) (*CreateOrderResponse, error) { u := fmt.Sprintf("/orderbook/v4.0/%d", api.chainId) - if params.Extension == "" { - params.Extension = emptyExtension - } - err := params.Validate() if err != nil { return nil, err diff --git a/sdk-clients/orderbook/bitmask.go b/sdk-clients/orderbook/bitmask.go new file mode 100644 index 0000000..ee6ff02 --- /dev/null +++ b/sdk-clients/orderbook/bitmask.go @@ -0,0 +1,49 @@ +package orderbook + +import ( + "fmt" + "math/big" +) + +type BitMask struct { + Offset *big.Int + Mask *big.Int +} + +// NewBitMask creates a new BitMask with the given start and end bit positions. +func NewBitMask(startBit, endBit *big.Int) *BitMask { + if startBit.Cmp(endBit) >= 0 { + panic("BitMask: startBit must be less than endBit") + } + + bitCount := new(big.Int).Sub(endBit, startBit) // endBit - startBit + mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), uint(bitCount.Uint64())), big.NewInt(1)) // (1 << bitCount) - 1 + + return &BitMask{ + Offset: startBit, + Mask: mask, + } +} + +func (b *BitMask) SetBits(value, bits *big.Int) *big.Int { + // Create the shifted mask + shiftedMask := new(big.Int).Set(b.Mask) + shiftedMask.Lsh(shiftedMask, uint(b.Offset.Uint64())) + // Clear the bits at the mask location + value.And(value, new(big.Int).Not(shiftedMask)) + // Shift the bits to the correct location + shiftedBits := new(big.Int).Lsh(bits, uint(b.Offset.Uint64())) + value.Or(value, shiftedBits) + return value +} + +// ToString returns the string representation of the mask shifted by the offset. +func (b *BitMask) ToString() string { + shiftedMask := new(big.Int).Lsh(b.Mask, uint(b.Offset.Uint64())) + return fmt.Sprintf("0x%x", shiftedMask) +} + +// ToBigInt returns the mask value as a big.Int, shifted by the offset. +func (b *BitMask) ToBigInt() *big.Int { + return new(big.Int).Lsh(b.Mask, uint(b.Offset.Uint64())) +} diff --git a/sdk-clients/orderbook/bitmask_test.go b/sdk-clients/orderbook/bitmask_test.go new file mode 100644 index 0000000..6277470 --- /dev/null +++ b/sdk-clients/orderbook/bitmask_test.go @@ -0,0 +1,142 @@ +package orderbook + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBitMask(t *testing.T) { + tests := []struct { + name string + startBit int64 + endBit int64 + valueToUpdate string + inputBits string + expectedOutputBits string + }{ + { + name: "Simple single bit mask", + startBit: 0, + endBit: 1, + valueToUpdate: "0", + inputBits: "1", + expectedOutputBits: "1", + }, + { + name: "Set middle bits", + startBit: 4, + endBit: 8, + valueToUpdate: "110000000000", + inputBits: "1111", + expectedOutputBits: "110011110000", + }, + { + name: "Clear bits", + startBit: 4, + endBit: 8, + valueToUpdate: "11111111", + inputBits: "0", + expectedOutputBits: "00001111", + }, + { + name: "Set bits in an existing value", + startBit: 4, + endBit: 6, + valueToUpdate: "11110000", + inputBits: "11", + expectedOutputBits: "11110000", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + startBit := big.NewInt(tc.startBit) + endBit := big.NewInt(tc.endBit) + valueToUpdate := bitStringToBigInt(tc.valueToUpdate) + inputBits := bitStringToBigInt(tc.inputBits) + expectedOutputBits := bitStringToBigInt(tc.expectedOutputBits) + + bitmask := NewBitMask(startBit, endBit) + result := bitmask.SetBits(valueToUpdate, inputBits) + assert.Equal(t, expectedOutputBits, result) + }) + } +} + +func TestBitMaskToString(t *testing.T) { + tests := []struct { + name string + startBit int64 + endBit int64 + expectedString string + }{ + { + name: "Simple mask", + startBit: 4, + endBit: 8, + expectedString: "0xf0", + }, + { + name: "Single bit mask", + startBit: 0, + endBit: 1, + expectedString: "0x1", + }, + { + name: "Full byte mask", + startBit: 0, + endBit: 8, + expectedString: "0xff", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bitmask := NewBitMask(big.NewInt(tc.startBit), big.NewInt(tc.endBit)) + assert.Equal(t, tc.expectedString, bitmask.ToString()) + }) + } +} + +func TestBitMaskToBigInt(t *testing.T) { + tests := []struct { + name string + startBit int64 + endBit int64 + expectedBigInt *big.Int + }{ + { + name: "Simple mask", + startBit: 4, + endBit: 8, + expectedBigInt: bitStringToBigInt("11110000"), + }, + { + name: "Single bit mask", + startBit: 0, + endBit: 1, + expectedBigInt: bitStringToBigInt("00000001"), + }, + { + name: "Full byte mask", + startBit: 0, + endBit: 8, + expectedBigInt: bitStringToBigInt("11111111"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + bitmask := NewBitMask(big.NewInt(tc.startBit), big.NewInt(tc.endBit)) + assert.Equal(t, tc.expectedBigInt, bitmask.ToBigInt()) + }) + } +} + +func bitStringToBigInt(bitStr string) *big.Int { + i := new(big.Int) + i.SetString(bitStr, 2) + return i +} diff --git a/sdk-clients/orderbook/examples/create_and_fill_order/main.go b/sdk-clients/orderbook/examples/create_and_fill_order/main.go index 7e81de4..83cf0ad 100644 --- a/sdk-clients/orderbook/examples/create_and_fill_order/main.go +++ b/sdk-clients/orderbook/examples/create_and_fill_order/main.go @@ -59,7 +59,7 @@ func main() { log.Fatal(fmt.Errorf("failed to get series nonce: %v", err)) } - buildMakerTraitsParams := orderbook.BuildMakerTraitsParams{ + makerTraits := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ AllowedSender: zeroAddress, ShouldCheckEpoch: false, UsePermit2: false, @@ -70,8 +70,7 @@ func main() { Expiry: expireAfter, Nonce: seriesNonce.Int64(), Series: 0, // TODO: Series 0 always? - } - makerTraits := orderbook.BuildMakerTraits(buildMakerTraitsParams) + }) createOrderResponse, err := client.CreateOrder(ctx, orderbook.CreateOrderParams{ SeriesNonce: seriesNonce, @@ -110,7 +109,7 @@ func main() { OrderHash: getOrderResponse[0].OrderHash, }) - fillOrderData, err := client.GetFillOrderCalldata(getOrderRresponse) + fillOrderData, err := client.GetFillOrderCalldata(getOrderRresponse, nil) aggregationRouter, err := constants.Get1inchRouterFromChainId(chainId) if err != nil { diff --git a/sdk-clients/orderbook/examples/create_order/main.go b/sdk-clients/orderbook/examples/create_order/main.go index 71f95a1..77f4163 100644 --- a/sdk-clients/orderbook/examples/create_order/main.go +++ b/sdk-clients/orderbook/examples/create_order/main.go @@ -58,7 +58,7 @@ func main() { log.Fatal(fmt.Errorf("failed to get series nonce: %v", err)) } - buildMakerTraitsParams := orderbook.BuildMakerTraitsParams{ + makerTraits := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ AllowedSender: zeroAddress, ShouldCheckEpoch: false, UsePermit2: false, @@ -69,8 +69,7 @@ func main() { Expiry: expireAfter, Nonce: seriesNonce.Int64(), Series: 0, // TODO: Series 0 always? - } - makerTraits := orderbook.BuildMakerTraits(buildMakerTraitsParams) + }) createOrderResponse, err := client.CreateOrder(ctx, orderbook.CreateOrderParams{ SeriesNonce: seriesNonce, diff --git a/sdk-clients/orderbook/examples/create_order_permit/main.go b/sdk-clients/orderbook/examples/create_order_permit/main.go index 0ae0bd6..478f59f 100644 --- a/sdk-clients/orderbook/examples/create_order_permit/main.go +++ b/sdk-clients/orderbook/examples/create_order_permit/main.go @@ -78,22 +78,20 @@ func main() { if err != nil { panic(err) } - permit, err := client.Wallet.TokenPermit(*permitData) if err != nil { log.Fatal(fmt.Errorf("Failed to get permit: %v\n", err)) } - interactions, err := orderbook.GetInteractions(PolygonFRAX, orderbook.Trim0x(permit)) + extension, err := orderbook.NewExtension(orderbook.ExtensionParams{ + MakerAsset: PolygonFRAX, + Permit: permit, + }) if err != nil { - log.Fatal(fmt.Errorf("Failed to get interactions: %v\n", err)) + log.Fatalf("Failed to create extension: %v\n", err) } - interactionsConcatenated := orderbook.ConcatenateInteractions(interactions) - interactionsOffsets := orderbook.GetOffsets(interactions) - extension := orderbook.BuildExtension(interactionsConcatenated, interactionsOffsets) - - makerTraits := orderbook.BuildMakerTraits(orderbook.BuildMakerTraitsParams{ + makerTraits := orderbook.NewMakerTraits(orderbook.MakerTraitsParams{ AllowedSender: zeroAddress, ShouldCheckEpoch: false, UsePermit2: false, diff --git a/sdk-clients/orderbook/examples/fill_order/main.go b/sdk-clients/orderbook/examples/fill_order/main.go index dc4401f..e1264b1 100644 --- a/sdk-clients/orderbook/examples/fill_order/main.go +++ b/sdk-clients/orderbook/examples/fill_order/main.go @@ -26,7 +26,7 @@ var ( ) const ( - limitOrderHash = "0x073797847405119e8de253d97b281853748e690065b4ae04e5eda9d282f1015b" + limitOrderHash = "0xddf0907b11b3dba4591372e201117f09f9cdc7ccfff175397bc57eeb7043a59f" chainId = 137 ) @@ -39,11 +39,15 @@ func main() { } client, err := orderbook.NewClient(config) - getOrderRresponse, err := client.GetOrder(ctx, orderbook.GetOrderParams{ + getOrderResponse, err := client.GetOrder(ctx, orderbook.GetOrderParams{ OrderHash: limitOrderHash, }) - fillOrderData, err := client.GetFillOrderCalldata(getOrderRresponse) + takerTraits := orderbook.NewTakerTraits(orderbook.TakerTraitsParams{ + Extension: getOrderResponse.Data.Extension, + }) + + fillOrderData, err := client.GetFillOrderCalldata(getOrderResponse, takerTraits) if err != nil { log.Fatalf("Failed to get fill order calldata: %v", err) } diff --git a/sdk-clients/orderbook/extension.go b/sdk-clients/orderbook/extension.go new file mode 100644 index 0000000..463c389 --- /dev/null +++ b/sdk-clients/orderbook/extension.go @@ -0,0 +1,93 @@ +package orderbook + +import ( + "fmt" + "math/big" + "strings" +) + +type Extension struct { + InteractionsArray []string +} + +type ExtensionParams struct { + MakerAsset string + MakerAssetData string + TakerAssetData string + GetMakingAmount string + GetTakingAmount string + Predicate string + Permit string + PreInteraction string + PostInteraction string +} + +func NewExtension(params ExtensionParams) (Extension, error) { + + if params.Permit != "" { + if params.MakerAsset == "" { + return Extension{}, fmt.Errorf("when Permit is present, a maker asset must also be defined requires MakerAsset") + } + } + + if params.MakerAsset != "" { + if params.Permit == "" { + return Extension{}, fmt.Errorf("when MakerAsset is present, a maker asset must also be defined requires Permit") + } + } + + makerAssetData := params.MakerAssetData + takerAssetData := params.TakerAssetData + getMakingAmount := params.GetMakingAmount + getTakingAmount := params.GetTakingAmount + predicate := params.Predicate + permit := params.MakerAsset + strings.TrimPrefix(params.Permit, "0x") + preInteraction := params.PreInteraction + postInteraction := params.PostInteraction + + interactions := []string{makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preInteraction, postInteraction} + + return Extension{ + InteractionsArray: interactions, + }, nil +} + +func (i *Extension) Encode() string { + interactionsConcatednated := i.getConcatenatedInteractions() + if interactionsConcatednated == "" { + return "0x" + } + offsetsBytes := i.getOffsets().Bytes() + paddedOffsetHex := fmt.Sprintf("%064x", offsetsBytes) + return "0x" + paddedOffsetHex + interactionsConcatednated +} + +func (i *Extension) getConcatenatedInteractions() string { + var builder strings.Builder + for _, interaction := range i.InteractionsArray { + interaction = strings.TrimPrefix(interaction, "0x") + builder.WriteString(interaction) + } + return builder.String() +} + +func (i *Extension) getOffsets() *big.Int { + var lengthMap []int + for _, interaction := range i.InteractionsArray { + lengthMap = append(lengthMap, len(strings.TrimPrefix(interaction, "0x"))/2) + } + + cumulativeSum := 0 + bytesAccumulator := big.NewInt(0) + var index uint64 + + for _, length := range lengthMap { + cumulativeSum += length + shiftVal := big.NewInt(int64(cumulativeSum)) + shiftVal.Lsh(shiftVal, uint(32*index)) // Shift left + bytesAccumulator.Add(bytesAccumulator, shiftVal) // Add to accumulator + index++ + } + + return bytesAccumulator +} diff --git a/sdk-clients/orderbook/extension_test.go b/sdk-clients/orderbook/extension_test.go new file mode 100644 index 0000000..56a199e --- /dev/null +++ b/sdk-clients/orderbook/extension_test.go @@ -0,0 +1,109 @@ +package orderbook + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetConcatenatedInteractions(t *testing.T) { + + tests := []struct { + name string + extension Extension + expectedConcatenatedInteractions string + }{ + { + name: "Single hex value", + extension: Extension{ + InteractionsArray: []string{ + "", + "", + "", + "", + "", + "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + "", + "", + }, + }, + expectedConcatenatedInteractions: "45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + }, + { + name: "Multiple hex value", + extension: Extension{ + InteractionsArray: []string{ + "", + "", + "0x12345", + "", + "", + "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + "", + "", + }, + }, + expectedConcatenatedInteractions: "1234545c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + }, + { + name: "Hex and non-hex values", + extension: Extension{ + InteractionsArray: []string{ + "", + "", + "0x12345", + "nonhex", + "", + "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + "", + "", + }, + }, + expectedConcatenatedInteractions: "12345nonhex45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expectedConcatenatedInteractions, tc.extension.getConcatenatedInteractions()) + }) + } +} + +func TestGetOffsets(t *testing.T) { + + tests := []struct { + name string + extension Extension + expectedOffsets string + }{ + { + name: "Single hex value", + extension: Extension{ + InteractionsArray: []string{ + "", + "", + "", + "", + "", + "0x45c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654B5EFBdD0c54a062dfa6012933deFe000000000000000000000000111111125421cA6dc452d289314280a0f8842A65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663d7724000000000000000000000000000000000000000000000000000000000000001bd104dfa3f550a95a28d404f74c84514a39ba3b20023cf04863ee1b541e952e2649c45c8f394c68e90f38700d9951b4c1b0dc4e7bd2ae2f6fc793db846de75ee3", + "", + "", + }, + }, + expectedOffsets: "6578226988316368933689708187117462815671832943811647470634552862441472", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + expectedOffsetsBig, ok := new(big.Int).SetString(tc.expectedOffsets, 10) // base 10 for decimal + require.True(t, ok) + + offsets := tc.extension.getOffsets() + assert.Equal(t, offsets.Cmp(expectedOffsetsBig), 0) + }) + } +} diff --git a/sdk-clients/orderbook/limitorder.go b/sdk-clients/orderbook/limitorder.go index 243d785..83ef5d8 100644 --- a/sdk-clients/orderbook/limitorder.go +++ b/sdk-clients/orderbook/limitorder.go @@ -14,55 +14,6 @@ import ( "github.com/1inch/1inch-sdk-go/constants" ) -const ( - unwrapWethFlag = 247 - allowMultipleFillsFlag = 254 - needEpochCheckFlag = 250 - usePermit2Flag = 248 - hasExtensionFlag = 249 - needPreinteractionFlag = 252 - needPostinteractionFlag = 251 -) - -func BuildMakerTraits(params BuildMakerTraitsParams) string { - // Convert allowedSender from hex string to big.Int - allowedSenderInt := new(big.Int) - allowedSenderInt.SetString(params.AllowedSender, 16) - - // Initialize tempPredicate as big.Int - tempPredicate := new(big.Int) - tempPredicate.Lsh(big.NewInt(params.Series), 160) - tempPredicate.Or(tempPredicate, new(big.Int).Lsh(big.NewInt(params.Nonce), 120)) - tempPredicate.Or(tempPredicate, new(big.Int).Lsh(big.NewInt(params.Expiry), 80)) - tempPredicate.Or(tempPredicate, new(big.Int).And(allowedSenderInt, new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 80), big.NewInt(1)))) - - if params.UnwrapWeth { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), unwrapWethFlag)) - } - // This flag must be set - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), allowMultipleFillsFlag)) - - if params.ShouldCheckEpoch { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), needEpochCheckFlag)) - } - if params.UsePermit2 { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), usePermit2Flag)) - } - if params.HasExtension { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), hasExtensionFlag)) - } - if params.HasPreInteraction { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), needPreinteractionFlag)) - } - if params.HasPostInteraction { - tempPredicate.Or(tempPredicate, big.NewInt(1).Lsh(big.NewInt(1), needPostinteractionFlag)) - } - - // Pad the predicate to 32 bytes with 0's on the left and convert to hex string - paddedPredicate := fmt.Sprintf("%032x", tempPredicate) - return "0x" + paddedPredicate -} - func CreateLimitOrderMessage(orderRequest CreateOrderParams, chainId int) (*Order, error) { orderData := OrderData{ @@ -70,12 +21,12 @@ func CreateLimitOrderMessage(orderRequest CreateOrderParams, chainId int) (*Orde TakerAsset: orderRequest.TakerAsset, MakingAmount: orderRequest.MakingAmount, TakingAmount: orderRequest.TakingAmount, - Salt: GenerateSalt(orderRequest.Extension), + Salt: GenerateSalt(orderRequest.Extension.Encode()), Maker: orderRequest.Maker, AllowedSender: "0x0000000000000000000000000000000000000000", Receiver: orderRequest.Taker, - MakerTraits: orderRequest.MakerTraits, - Extension: orderRequest.Extension, + MakerTraits: orderRequest.MakerTraits.Encode(), + Extension: orderRequest.Extension.Encode(), } aggregationRouter, err := constants.Get1inchRouterFromChainId(chainId) @@ -205,72 +156,3 @@ func stringToHexBytes(hexStr string) ([]byte, error) { return bytes, nil } - -func GetInteractions(makerAsset string, permit string) ([]string, error) { - - makerAssetData := `0x` - takerAssetData := `0x` - getMakingAmount := `0x` - getTakingAmount := `0x` - predicate := `0x` - preInteraction := `0x` - postInteraction := `0x` - - // The maker token must be prepended to permit data for limit orders - if permit != "0x" { - permit = makerAsset + permit - } - - return []string{makerAssetData, takerAssetData, getMakingAmount, getTakingAmount, predicate, permit, preInteraction, postInteraction}, nil -} - -func GetOffsets(interactions []string) *big.Int { - var lengthMap []int - for _, interaction := range interactions { - if interaction[:2] == "0x" { - lengthMap = append(lengthMap, len(interaction)/2-1) - } else { - lengthMap = append(lengthMap, len(interaction)/2) - } - } - - cumulativeSum := 0 - bytesAccumulator := big.NewInt(0) - var index uint64 - - for _, length := range lengthMap { - cumulativeSum += length - shiftVal := big.NewInt(int64(cumulativeSum)) - shiftVal.Lsh(shiftVal, uint(32*index)) // Shift left - bytesAccumulator.Add(bytesAccumulator, shiftVal) // Add to accumulator - index++ - } - - return bytesAccumulator -} - -func BuildExtension(interactionsConcatednated string, offsets *big.Int) string { - if interactionsConcatednated == "0x" { - return "0x" - } - offsetsBytes := offsets.Bytes() - paddedOffsetHex := fmt.Sprintf("%064x", offsetsBytes) - return "0x" + paddedOffsetHex + strings.TrimPrefix(interactionsConcatednated, "0x") -} - -func ConcatenateInteractions(interactions []string) string { - var builder strings.Builder - - for _, interaction := range interactions { - // Remove "0x" prefix if present - interaction = strings.TrimPrefix(interaction, "0x") - builder.WriteString(interaction) - } - - // Add "0x" prefix to the final result - return builder.String() -} - -func Trim0x(input string) string { - return strings.TrimPrefix(input, "0x") -} diff --git a/sdk-clients/orderbook/limitorder_test.go b/sdk-clients/orderbook/limitorder_test.go deleted file mode 100644 index 7361e0e..0000000 --- a/sdk-clients/orderbook/limitorder_test.go +++ /dev/null @@ -1,355 +0,0 @@ -package orderbook - -// -//import ( -// "bytes" -// "math/big" -// "os" -// "testing" -// "time" -// -// "github.com/1inch/1inch-sdk-go/internal/helpers/constants/addresses" -// "github.com/1inch/1inch-sdk-go/internal/helpers/constants/amounts" -// "github.com/1inch/1inch-sdk-go/internal/helpers/constants/chains" -// "github.com/1inch/1inch-sdk-go/internal/helpers/constants/tokens" -// -// "github.com/ethereum/go-ethereum/ethclient" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// -// "github.com/1inch/1inch-sdk-go/client/models" -// -// "github.com/1inch/1inch-sdk-go/helpers" -//) -// -//func TestTrim0x(t *testing.T) { -// testcases := []struct { -// description string -// input string -// expected string -// }{ -// { -// description: "String starts with 0x", -// input: "0xabcdef", -// expected: "abcdef", -// }, -// { -// description: "String does not start with 0x", -// input: "abcdef", -// expected: "abcdef", -// }, -// { -// description: "Empty string", -// input: "", -// expected: "", -// }, -// { -// description: "String is just 0x", -// input: "0x", -// expected: "", -// }, -// { -// description: "String starts with 0X (uppercase)", -// input: "0Xabcdef", -// expected: "0Xabcdef", // note: the function only trims lowercase "0x" -// }, -// } -// -// for _, tc := range testcases { -// t.Run(tc.description, func(t *testing.T) { -// result := Trim0x(tc.input) -// require.Equal(t, tc.expected, result) -// }) -// } -//} -// -//func TestCumulativeSum(t *testing.T) { -// testcases := []struct { -// description string -// initial int -// values []int -// expectedSums []int -// }{ -// { -// description: "Initial value is 0", -// initial: 0, -// values: []int{5, 10, 15}, -// expectedSums: []int{5, 15, 30}, -// }, -// { -// description: "Initial value is 5", -// initial: 5, -// values: []int{5, 10, 15}, -// expectedSums: []int{10, 20, 35}, -// }, -// { -// description: "No values passed", -// initial: 5, -// values: []int{}, -// expectedSums: []int{}, -// }, -// { -// description: "Negative values", -// initial: -5, -// values: []int{5, -10, 15}, -// expectedSums: []int{0, -10, 5}, -// }, -// } -// -// for _, tc := range testcases { -// t.Run(tc.description, func(t *testing.T) { -// sumFunc := CumulativeSum(tc.initial) -// -// for i, value := range tc.values { -// result := sumFunc(value) -// require.Equal(t, tc.expectedSums[i], result) -// } -// }) -// } -//} -// -//func TestGenerateSalt(t *testing.T) { -// t.Run("Generated salt is unique", func(t *testing.T) { -// salt1 := GenerateSalt() -// time.Sleep(1 * time.Millisecond) // Sleep for a millisecond to ensure a different time -// salt2 := GenerateSalt() -// require.NotEqual(t, salt1, salt2) -// }) -// -// t.Run("Generated salt has expected length", func(t *testing.T) { -// salt := GenerateSalt() -// // Since we're using UnixNano / Millisecond, it should be a long string but not as long as nano time. -// require.True(t, len(salt) > 5 && len(salt) < 20) -// }) -//} -// -//func TestCreateLimitOrder(t *testing.T) { -// -// staticSalt := "100000000" -// mockGenerateSaltFunction := func() string { -// return staticSalt -// } -// -// tests := []struct { -// name string -// orderRequest models.CreateOrderParams -// interactions []string // TODO Revisit this to make it more encapsulated -// mockBigInt func(string) (*big.Int, error) -// expectedOrder *models.Order -// expectError bool -// expectedError string -// }{ -// { -// name: "happy path", -// orderRequest: models.CreateOrderParams{ -// chainId: chains.Polygon, -// PrivateKey: os.Getenv("WALLET_KEY"), -// MakerAsset: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", -// TakerAsset: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", -// MakingAmount: "1000000", -// TakingAmount: "1000000000", -// Maker: "0x2c9b2DBdbA8A9c969Ac24153f5C1c23CB0e63914", -// Taker: "0x0000000000000000000000000000000000000000", -// }, -// interactions: []string{"0x", "0x", "0x", "0x", "0xbf15fcd8000000000000000000000000a5eb255ef45dfb48b5d133d08833def69871691d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0071150dff0000000000000050c5df26654b5efbdd0c54a062dfa6012933defe00000000000000000000000000000000000000000000000000000000", "0x", "0x", "0x"}, -// expectedOrder: &models.Order{ -// OrderHash: "0xdc9344cfa6d3b4da5a2ad3283e02826d3f569b4472443390d3e1cfe86cacd13f", -// Signature: "0x317ed3e021851542deeafb4897ef091b010317772b7299477121d0f46cdd32cf1403429b13d2337b459c7a982ac71144ceaad88dd08d5b7c7b8abbe1618070ab1b", -// Data: models.OrderData{ -// MakerAsset: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", -// TakerAsset: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", -// MakingAmount: "1000000", -// TakingAmount: "1000000000", -// Salt: "100000000", -// Maker: "0x2c9b2DBdbA8A9c969Ac24153f5C1c23CB0e63914", -// AllowedSender: "0x0000000000000000000000000000000000000000", -// Receiver: "0x0000000000000000000000000000000000000000", -// Offsets: "4421431254442149611168492388118363282642987198110904030635476664713216", -// Interactions: "0xbf15fcd8000000000000000000000000a5eb255ef45dfb48b5d133d08833def69871691d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0071150dff0000000000000050c5df26654b5efbdd0c54a062dfa6012933defe00000000000000000000000000000000000000000000000000000000", -// }, -// }, -// expectError: false, -// }, -// { -// name: "empty maker asset", -// orderRequest: models.CreateOrderParams{ -// chainId: chains.Polygon, -// PrivateKey: os.Getenv("WALLET_KEY"), -// MakerAsset: "", -// TakerAsset: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", -// MakingAmount: "1000000", -// TakingAmount: "1000000000", -// Maker: "0x2c9b2DBdbA8A9c969Ac24153f5C1c23CB0e63914", -// Taker: "0x0000000000000000000000000000000000000000", -// }, -// interactions: []string{"0x", "0x", "0x", "0x", "0xbf15fcd8000000000000000000000000a5eb255ef45dfb48b5d133d08833def69871691d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0071150dff0000000000000050c5df26654b5efbdd0c54a062dfa6012933defe00000000000000000000000000000000000000000000000000000000", "0x", "0x", "0x"}, -// expectError: true, -// expectedError: "error hashing typed data", -// }, -// { -// name: "empty taker asset", -// orderRequest: models.CreateOrderParams{ -// chainId: chains.Polygon, -// PrivateKey: os.Getenv("WALLET_KEY"), -// MakerAsset: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", -// TakerAsset: "", -// MakingAmount: "1000000", -// TakingAmount: "1000000000", -// Maker: "0x2c9b2DBdbA8A9c969Ac24153f5C1c23CB0e63914", -// Taker: "0x0000000000000000000000000000000000000000", -// }, -// interactions: []string{"0x", "0x", "0x", "0x", "0xbf15fcd8000000000000000000000000a5eb255ef45dfb48b5d133d08833def69871691d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0071150dff0000000000000050c5df26654b5efbdd0c54a062dfa6012933defe00000000000000000000000000000000000000000000000000000000", "0x", "0x", "0x"}, -// expectError: true, -// expectedError: "error hashing typed data", -// }, -// { -// name: "invalid private key", -// orderRequest: models.CreateOrderParams{ -// chainId: chains.Polygon, -// PrivateKey: "invalid_private_key", // non-hex or short length key -// MakerAsset: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", -// TakerAsset: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", -// MakingAmount: "1000000", -// TakingAmount: "1000000000", -// Maker: "0x2c9b2DBdbA8A9c969Ac24153f5C1c23CB0e63914", -// Taker: "0x0000000000000000000000000000000000000000", -// }, -// interactions: []string{"0x", "0x", "0x", "0x", "0xbf15fcd8000000000000000000000000a5eb255ef45dfb48b5d133d08833def69871691d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000242cc2878d0071150dff0000000000000050c5df26654b5efbdd0c54a062dfa6012933defe00000000000000000000000000000000000000000000000000000000", "0x", "0x", "0x"}, -// expectError: true, -// expectedError: "error converting private key to ECDSA", -// }, -// } -// -// // Save the original salt generation function and return it later -// originalGenerateSalt := GenerateSalt -// GenerateSalt = mockGenerateSaltFunction -// defer func() { -// GenerateSalt = originalGenerateSalt -// }() -// -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// result, err := CreateLimitOrderMessage(tc.orderRequest, tc.interactions) -// -// if tc.expectError { -// assert.Error(t, err) -// if tc.expectedError != "" { -// assert.Contains(t, err.Error(), tc.expectedError) -// } -// } else { -// require.NoError(t, err) -// // Validate all the fields in the order data to be as expected -// assert.Equal(t, tc.expectedOrder.OrderHash, result.OrderHash, "Order hash does not match expected value") -// assert.Equal(t, tc.expectedOrder.Signature, result.Signature, "Signature does not match expected value") -// // Compare the data fields individually or as a whole -// assert.Equal(t, tc.expectedOrder.Data, result.Data, "Order data does not match expected value") -// } -// }) -// } -//} -// -//func TestConfirmTradeWithUser(t *testing.T) { -// -// order := &models.Order{ -// Data: models.OrderData{ -// MakerAsset: tokens.EthereumUsdc, -// TakerAsset: tokens.EthereumDai, -// MakingAmount: amounts.Ten6 + "1", -// TakingAmount: amounts.Ten18, -// Maker: addresses.Vitalik, -// }, -// } -// -// tests := []struct { -// name string -// userInput string -// expectedResult bool -// expectedOutput string -// }{ -// { -// name: "User inputs 'y'", -// userInput: "y\n", -// expectedResult: true, -// }, -// { -// name: "User inputs 'Y'", -// userInput: "Y\n", -// expectedResult: true, -// }, -// { -// name: "User inputs 'n'", -// userInput: "n\n", -// expectedResult: false, -// }, -// { -// name: "User inputs nothing", -// userInput: "\n", -// expectedResult: false, -// }, -// { -// name: "User inputs other text", -// userInput: "other\n", -// expectedResult: false, -// }, -// } -// -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// ethClient, err := ethclient.Dial(os.Getenv("WEB_3_HTTP_PROVIDER_URL_WITH_KEY")) -// require.NoError(t, err) -// reader := bytes.NewBufferString(tc.userInput) -// writer := helpers.NoOpPrinter{} -// result, err := confirmLimitOrderWithUser(order, ethClient, reader, writer) -// -// assert.NoError(t, err) -// assert.Equal(t, tc.expectedResult, result) -// }) -// } -//} -// -//func TestConcatenateInteractions(t *testing.T) { -// -// tests := []struct { -// name string -// interactions []string -// expectedResult string -// }{ -// { -// name: "Empty slice", -// interactions: []string{}, -// expectedResult: "0x", -// }, -// { -// name: "Single element without prefix", -// interactions: []string{"abcdef"}, -// expectedResult: "0xabcdef", -// }, -// { -// name: "Single element with prefix", -// interactions: []string{"0x123456"}, -// expectedResult: "0x123456", -// }, -// { -// name: "Multiple elements mixed prefixes", -// interactions: []string{"0xabcdef", "123456", "0x7890"}, -// expectedResult: "0xabcdef1234567890", -// }, -// { -// name: "Multiple elements all with prefix", -// interactions: []string{"0xabcdef", "0x123456", "0x7890"}, -// expectedResult: "0xabcdef1234567890", -// }, -// { -// name: "Multiple elements none with prefix", -// interactions: []string{"abcdef", "123456", "7890"}, -// expectedResult: "0xabcdef1234567890", -// }, -// } -// -// for _, tc := range tests { -// t.Run(tc.name, func(t *testing.T) { -// result := concatenateInteractions(tc.interactions) -// assert.Equal(t, tc.expectedResult, result) -// }) -// } -//} diff --git a/sdk-clients/orderbook/makerTraits.go b/sdk-clients/orderbook/makerTraits.go new file mode 100644 index 0000000..2124ec7 --- /dev/null +++ b/sdk-clients/orderbook/makerTraits.go @@ -0,0 +1,100 @@ +package orderbook + +import ( + "fmt" + "math/big" +) + +var ( +// TODO currently unused masks carried over from the Typescript Limit Order SDK +// allowedSenderMask = NewBitMask(big.NewInt(0), big.NewInt(80)) +// expirationMask = NewBitMask(big.NewInt(80), big.NewInt(120)) +// nonceOrEpochMask = NewBitMask(big.NewInt(120), big.NewInt(160)) +// seriesMask = NewBitMask(big.NewInt(160), big.NewInt(200)) +) + +const ( + noPartialFillsFlag = 255 + allowMultipleFillsFlag = 254 + needPreinteractionFlag = 252 + needPostinteractionFlag = 251 + needEpochCheckFlag = 250 + hasExtensionFlag = 249 + usePermit2Flag = 248 + unwrapWethFlag = 247 +) + +type MakerTraits struct { + AllowedSender string + Expiry int64 + Nonce int64 + Series int64 + + NoPartialFills bool + NeedPostinteraction bool + NeedPreinteraction bool + NeedEpochCheck bool + HasExtension bool + ShouldUsePermit2 bool + ShouldUnwrapWeth bool +} + +func NewMakerTraits(params MakerTraitsParams) *MakerTraits { + return &MakerTraits{ + AllowedSender: params.AllowedSender, + Expiry: params.Expiry, + Nonce: params.Nonce, + Series: params.Series, + + NeedPostinteraction: params.HasPostInteraction, + NeedPreinteraction: params.HasPreInteraction, + NeedEpochCheck: params.ShouldCheckEpoch, + HasExtension: params.HasExtension, + ShouldUsePermit2: params.UsePermit2, + ShouldUnwrapWeth: params.UnwrapWeth, + } +} + +func (m *MakerTraits) Encode() string { + encodedCalldata := new(big.Int) + + tmp := new(big.Int) + + if m.NoPartialFills { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), noPartialFillsFlag)) + } + // Limit Orders require this flag to always be present + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), allowMultipleFillsFlag)) + if m.NeedPreinteraction { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), needPreinteractionFlag)) + } + if m.NeedPostinteraction { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), needPostinteractionFlag)) + } + if m.NeedEpochCheck { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), needEpochCheckFlag)) + } + if m.HasExtension { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), hasExtensionFlag)) + } + if m.ShouldUsePermit2 { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), usePermit2Flag)) + } + if m.ShouldUnwrapWeth { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), unwrapWethFlag)) + } + + // Convert AllowedSender from hex string to big.Int + allowedSenderInt := new(big.Int) + allowedSenderInt.SetString(m.AllowedSender[len(m.AllowedSender)-20:], 16) // We only care about the last 20 characters of the ethereum address + + // TODO These values originally used masks to write. Needs more testing to verify the simpler approach works. See https://github.com/1inch/limit-order-sdk/blob/0724227f6dab1649c4a4abcb1df30c2b43126eab/src/limit-order/maker-traits.ts#L74-L84 for how this looks in the Typescript Limit Order SDK + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(m.Series), 160)) + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(m.Nonce), 120)) + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(m.Expiry), 80)) + encodedCalldata.Or(encodedCalldata, tmp.And(allowedSenderInt, new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 80), big.NewInt(1)))) + + // Pad the predicate to 32 bytes with 0's on the left and convert to hex string + paddedPredicate := fmt.Sprintf("%032x", encodedCalldata) + return "0x" + paddedPredicate +} diff --git a/sdk-clients/orderbook/makerTraits_test.go b/sdk-clients/orderbook/makerTraits_test.go new file mode 100644 index 0000000..a9be59b --- /dev/null +++ b/sdk-clients/orderbook/makerTraits_test.go @@ -0,0 +1,40 @@ +package orderbook + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMakerTraitsEncode(t *testing.T) { + + tests := []struct { + name string + makerTraitParams MakerTraitsParams + expectedMakerTraits string + }{ + { + name: "Extension, expiration", + makerTraitParams: MakerTraitsParams{ + AllowedSender: "0x0000000000000000000000000000000000000000", + ShouldCheckEpoch: false, + UsePermit2: false, + UnwrapWeth: false, + HasExtension: true, + HasPreInteraction: false, + HasPostInteraction: false, + Expiry: 1715201499, + Nonce: 0, + Series: 0, + }, + expectedMakerTraits: "0x420000000000000000000000000000000000663be5db00000000000000000000", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + makerTraits := NewMakerTraits(tc.makerTraitParams) + assert.Equal(t, tc.expectedMakerTraits, makerTraits.Encode()) + }) + } +} diff --git a/sdk-clients/orderbook/orderbook_types_manual.go b/sdk-clients/orderbook/orderbook_types_manual.go index d6beacb..5824de0 100644 --- a/sdk-clients/orderbook/orderbook_types_manual.go +++ b/sdk-clients/orderbook/orderbook_types_manual.go @@ -3,12 +3,14 @@ package orderbook import ( "math/big" "time" + + "github.com/ethereum/go-ethereum/common" ) type CreateOrderParams struct { SeriesNonce *big.Int - MakerTraits string - Extension string + MakerTraits *MakerTraits + Extension Extension PrivateKey string ExpireAfter int64 Maker string @@ -147,15 +149,30 @@ type NormalizedLimitOrderData struct { MakerTraits *big.Int } -type BuildMakerTraitsParams struct { +type MakerTraitsParams struct { AllowedSender string + Expiry int64 + Nonce int64 + Series int64 ShouldCheckEpoch bool UsePermit2 bool UnwrapWeth bool HasExtension bool HasPreInteraction bool HasPostInteraction bool - Expiry int64 - Nonce int64 - Series int64 +} + +type TakerTraitsParams struct { + Receiver *common.Address + Extension string + MakerAmount bool + UnwrapWETH bool + SkipOrderPermit bool + UsePermit2 bool + ArgsHasReceiver bool +} + +type TakerTraitsCalldata struct { + Trait *big.Int + Args string } diff --git a/sdk-clients/orderbook/takerTraits.go b/sdk-clients/orderbook/takerTraits.go new file mode 100644 index 0000000..4e883e8 --- /dev/null +++ b/sdk-clients/orderbook/takerTraits.go @@ -0,0 +1,76 @@ +package orderbook + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + // TODO currently unused masks carried over from the Typescript Limit Order SDK + //thresholdMask = NewBitMask(big.NewInt(0), big.NewInt(185)) + //argsInteractionLengthMask = NewBitMask(big.NewInt(220), big.NewInt(224)) + argsExtensionLengthMask = NewBitMask(big.NewInt(224), big.NewInt(248)) +) + +const ( + MakerAmountFlag = 255 + UnwrapWETHFlag = 254 + SkipOrderPermitFlag = 253 + UsePermit2Flag = 252 + ArgsHasReceiverFlag = 251 +) + +type TakerTraitsEncoded struct { + TraitFlags *big.Int + Args []byte +} + +type TakerTraits struct { + Receiver *common.Address + Extension string // Assuming extension related functions are defined elsewhere + + MakerAmount bool + UnwrapWETH bool + SkipOrderPermit bool + UsePermit2 bool + ArgsHasReceiver bool +} + +func NewTakerTraits(params TakerTraitsParams) *TakerTraits { + return &TakerTraits{ + Receiver: params.Receiver, + Extension: params.Extension, + MakerAmount: params.MakerAmount, + UnwrapWETH: params.UnwrapWETH, + SkipOrderPermit: params.SkipOrderPermit, + UsePermit2: params.UsePermit2, + ArgsHasReceiver: params.ArgsHasReceiver, + } +} + +func (t *TakerTraits) Encode() *TakerTraitsEncoded { + encodedCalldata := new(big.Int) + tmp := new(big.Int) + + if t.ArgsHasReceiver && t.Receiver != nil { + encodedCalldata.Or(encodedCalldata, tmp.Lsh(big.NewInt(1), ArgsHasReceiverFlag)) + } + + if t.Extension != "0x" { + extensionBytesLen := big.NewInt(int64(len(t.Extension))/2 - 1) + argsExtensionLengthMask.SetBits(encodedCalldata, extensionBytesLen) + } + + traits := fmt.Sprintf("%032x", encodedCalldata) + traitsBig, err := hexutil.DecodeBig("0x" + traits) + if err != nil { + panic(err) + } + return &TakerTraitsEncoded{ + TraitFlags: traitsBig, + Args: common.FromHex(t.Extension), + } +} diff --git a/sdk-clients/orderbook/takerTraits_test.go b/sdk-clients/orderbook/takerTraits_test.go new file mode 100644 index 0000000..b805532 --- /dev/null +++ b/sdk-clients/orderbook/takerTraits_test.go @@ -0,0 +1,47 @@ +package orderbook + +import ( + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/1inch/1inch-sdk-go/internal/validate" +) + +func TestTakerTraitsEncode(t *testing.T) { + + tests := []struct { + name string + takerTraitParams TakerTraitsParams + expectedTakerTraits string + expectedTakerArgs string + }{ + { + name: "Extension", + takerTraitParams: TakerTraitsParams{ + Extension: "0x000000f4000000f4000000f4000000000000000000000000000000000000000045c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654b5efbdd0c54a062dfa6012933defe000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663a478b000000000000000000000000000000000000000000000000000000000000001bdf138a0d223e2ef8635075f5fe68efa8a2da1d890fdc3825b754c7ba2083ca0464494f534829f576cd67b966059657c51aaf53edbd6498d51cbd07da8bdb256b", + }, + expectedTakerTraits: "7440945280133576583328096164017418065923851860621198004784596428783616", + expectedTakerArgs: "0x000000f4000000f4000000f4000000000000000000000000000000000000000045c32fa6df82ead1e2ef74d17b76547eddfaff8900000000000000000000000050c5df26654b5efbdd0c54a062dfa6012933defe000000000000000000000000111111125421ca6dc452d289314280a0f8842a65000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000663a478b000000000000000000000000000000000000000000000000000000000000001bdf138a0d223e2ef8635075f5fe68efa8a2da1d890fdc3825b754c7ba2083ca0464494f534829f576cd67b966059657c51aaf53edbd6498d51cbd07da8bdb256b", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + expectedTakerTraitsBig, err := validate.BigIntFromString(tc.expectedTakerTraits) + require.NoError(t, err) + + takerTraits := NewTakerTraits(tc.takerTraitParams) + takerTraitsEnocded := takerTraits.Encode() + assert.True(t, expectedTakerTraitsBig.Cmp(takerTraitsEnocded.TraitFlags) == 0, fmt.Sprintf("Expected %x, got %x", expectedTakerTraitsBig, takerTraitsEnocded.TraitFlags)) + + expectedTakerArgs := common.FromHex(tc.expectedTakerArgs) + require.NoError(t, err) + assert.Equal(t, expectedTakerArgs, takerTraitsEnocded.Args) + }) + } +} diff --git a/sdk-clients/orderbook/web3data.go b/sdk-clients/orderbook/web3data.go index d3d1481..8c0099d 100644 --- a/sdk-clients/orderbook/web3data.go +++ b/sdk-clients/orderbook/web3data.go @@ -39,14 +39,16 @@ func (c *Client) GetSeriesNonce(ctx context.Context, publicAddress gethCommon.Ad return nonce, nil } -const makerTraitsPermitOnly = "7440945280133576583328096164017418065923851860621198004784596428783616" - -func (c *Client) GetFillOrderCalldata(getOrderResponse *GetOrderByHashResponseExtended) ([]byte, error) { +func (c *Client) GetFillOrderCalldata(getOrderResponse *GetOrderByHashResponseExtended, takerTraits *TakerTraits) ([]byte, error) { var function string if getOrderResponse.Data.Extension == "0x" { function = "fillOrder" } else { + if takerTraits == nil { + return nil, fmt.Errorf("this order has extension data, but no taker traits were provided") + } + function = "fillOrderArgs" } @@ -65,11 +67,6 @@ func (c *Client) GetFillOrderCalldata(getOrderResponse *GetOrderByHashResponseEx return nil, err } - //takerTraitsBigInt, ok := new(big.Int).SetString("0", 16) - //if !ok { - // return nil, fmt.Errorf("invalid taking amount value") - //} - var fillOrderData []byte switch function { @@ -79,14 +76,8 @@ func (c *Client) GetFillOrderCalldata(getOrderResponse *GetOrderByHashResponseEx return nil, err } case "fillOrderArgs": - takerTraitsBigInt, ok := new(big.Int).SetString(makerTraitsPermitOnly, 10) - if !ok { - return nil, fmt.Errorf("invalid taking amount value") - } - - extensionData := gethCommon.FromHex(getOrderResponse.Data.Extension) - - fillOrderData, err = c.AggregationRouterV6.Pack(function, getOrderResponse.LimitOrderDataNormalized, rCompressed, vsCompressed, getOrderResponse.LimitOrderDataNormalized.TakingAmount, takerTraitsBigInt, extensionData) + takerTraitsEncoded := takerTraits.Encode() + fillOrderData, err = c.AggregationRouterV6.Pack(function, getOrderResponse.LimitOrderDataNormalized, rCompressed, vsCompressed, getOrderResponse.LimitOrderDataNormalized.TakingAmount, takerTraitsEncoded.TraitFlags, takerTraitsEncoded.Args) if err != nil { return nil, err }