diff --git a/pkg/oneinch/limitorder/extension.go b/pkg/oneinch/limitorder/extension.go index 3da7c06..b871a7c 100644 --- a/pkg/oneinch/limitorder/extension.go +++ b/pkg/oneinch/limitorder/extension.go @@ -41,10 +41,10 @@ func (e Extension) IsEmpty() bool { return len(e.getConcatenatedInteractions()) == 0 } -func (e Extension) Encode() string { +func (e Extension) Encode() []byte { interactionsConcatenated := e.getConcatenatedInteractions() if len(interactionsConcatenated) == 0 { - return hexutil.Encode(interactionsConcatenated) + return interactionsConcatenated } offset := e.getOffsets() @@ -54,7 +54,7 @@ func (e Extension) Encode() string { b.Write(interactionsConcatenated) b.Write(e.CustomData) - return hexutil.Encode(b.Bytes()) + return b.Bytes() } func (e Extension) interactionsArray() [totalOffsetSlots][]byte { diff --git a/pkg/oneinch/limitorder/extension_test.go b/pkg/oneinch/limitorder/extension_test.go index 950e591..5f4b9a9 100644 --- a/pkg/oneinch/limitorder/extension_test.go +++ b/pkg/oneinch/limitorder/extension_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/KyberNetwork/tradinglib/pkg/oneinch/limitorder" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -24,7 +25,7 @@ func TestExtension(t *testing.T) { encodedExtension := extension.Encode() - decodedExtension, err := limitorder.DecodeExtension(encodedExtension) + decodedExtension, err := limitorder.DecodeExtension(hexutil.Encode(encodedExtension)) require.NoError(t, err) require.Equal(t, extension, decodedExtension) diff --git a/pkg/oneinch/limitorder/interaction.go b/pkg/oneinch/limitorder/interaction.go index 008f61f..fa8adf6 100644 --- a/pkg/oneinch/limitorder/interaction.go +++ b/pkg/oneinch/limitorder/interaction.go @@ -1,7 +1,6 @@ package limitorder import ( - "encoding/hex" "fmt" "github.com/KyberNetwork/tradinglib/pkg/oneinch/decode" @@ -17,8 +16,9 @@ func (i Interaction) IsZero() bool { return i.Target.String() == common.Address{}.String() && len(i.Data) == 0 } -func (i Interaction) Encode() string { - return i.Target.String() + hex.EncodeToString(i.Data) +func (i Interaction) Encode() []byte { + res := make([]byte, 0, len(i.Target)+len(i.Data)) + return append(append(res, i.Target.Bytes()...), i.Data...) } func DecodeInteraction(data []byte) (Interaction, error) { diff --git a/pkg/oneinch/limitorder/interaction_test.go b/pkg/oneinch/limitorder/interaction_test.go index fbe9c29..30d42e4 100644 --- a/pkg/oneinch/limitorder/interaction_test.go +++ b/pkg/oneinch/limitorder/interaction_test.go @@ -20,8 +20,7 @@ func TestInteraction(t *testing.T) { Data: data, } - encodedInteraction, err := hexutil.Decode(interaction.Encode()) - require.NoError(t, err) + encodedInteraction := interaction.Encode() decodedInteraction, err := limitorder.DecodeInteraction(encodedInteraction) require.NoError(t, err) diff --git a/pkg/oneinch/limitorder/taker_traits.go b/pkg/oneinch/limitorder/taker_traits.go new file mode 100644 index 0000000..216252e --- /dev/null +++ b/pkg/oneinch/limitorder/taker_traits.go @@ -0,0 +1,101 @@ +package limitorder + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +type AmountMode uint + +const ( + makerAmountFlag = 255 + argsHasReceiver = 251 + + AmountModeTaker AmountMode = 0 + AmountModeMaker AmountMode = 1 +) + +//nolint:gochecknoglobals,gomnd,mnd +var ( + // 224-247 bits `ARGS_EXTENSION_LENGTH` - The length of the extension calldata in the args. + argsExtensionLenMask = newBitMask(224, 248) + // 200-223 bits `ARGS_INTERACTION_LENGTH` - The length of the interaction calldata in the args. + argsInteractionLenMask = newBitMask(200, 224) + // 0-184 bits - The threshold amount. + amountThresholdMask = newBitMask(0, 185) +) + +type TakerTraits struct { + flags *big.Int + receiver *common.Address + extension *Extension + interaction *Interaction +} + +func NewTakerTraits( + flags *big.Int, receiver *common.Address, extension *Extension, interaction *Interaction, +) *TakerTraits { + return &TakerTraits{ + flags: flags, + receiver: receiver, + extension: extension, + interaction: interaction, + } +} + +func NewDefaultTakerTraits() *TakerTraits { + return &TakerTraits{ + flags: new(big.Int), + } +} + +func (t *TakerTraits) SetAmountMode(mode AmountMode) *TakerTraits { + t.flags.SetBit(t.flags, makerAmountFlag, uint(mode)) + return t +} + +// SetAmountThreshold sets threshold amount. +// +// In taker amount mode: the minimum amount a taker agrees to receive in exchange for a taking amount. +// In maker amount mode: the maximum amount a taker agrees to give in exchange for a making amount. +func (t *TakerTraits) SetAmountThreshold(threshold *big.Int) *TakerTraits { + setMask(t.flags, amountThresholdMask, threshold) + return t +} + +// SetExtension sets extension, it is required to provide same extension as in order creation (if any). +func (t *TakerTraits) SetExtension(ext Extension) *TakerTraits { + t.extension = &ext + return t +} + +func (t *TakerTraits) Encode() (common.Hash, []byte) { + var extension, interaction []byte + if t.extension != nil { + extension = t.extension.Encode() + } + if t.interaction != nil { + interaction = t.interaction.Encode() + } + + flags := new(big.Int).Set(t.flags) + if t.receiver != nil { + flags.SetBit(flags, argsHasReceiver, 1) + } + + // Set length for extension and interaction. + setMask(flags, argsExtensionLenMask, big.NewInt(int64(len(extension)))) + setMask(flags, argsInteractionLenMask, big.NewInt(int64(len(interaction)))) + + var args []byte + if t.receiver == nil { + args = make([]byte, 0, len(extension)+len(interaction)) + } else { + args = make([]byte, 0, len(t.receiver)+len(extension)+len(interaction)) + args = append(args, t.receiver.Bytes()...) + } + args = append(append(args, extension...), interaction...) + + return common.BigToHash(flags), args +} diff --git a/pkg/oneinch/limitorder/taker_traits_test.go b/pkg/oneinch/limitorder/taker_traits_test.go new file mode 100644 index 0000000..223826e --- /dev/null +++ b/pkg/oneinch/limitorder/taker_traits_test.go @@ -0,0 +1,33 @@ +//nolint:testpackage +package limitorder + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" +) + +//nolint:lll +func TestEncodeTakerTraits(t *testing.T) { + extension := Extension{ + MakerAssetSuffix: []byte{0x01}, + TakerAssetSuffix: []byte{0x02}, + MakingAmountData: []byte{0x03}, + TakingAmountData: []byte{0x04}, + Predicate: []byte{0x05}, + MakerPermit: []byte{0x06}, + PreInteraction: []byte{0x07}, + PostInteraction: []byte{0x08}, + CustomData: []byte{0xff}, + } + + takerTraits := NewDefaultTakerTraits() + takerTraits.SetExtension(extension).SetAmountMode(AmountModeMaker).SetAmountThreshold(big.NewInt(1)) + + encodedTakerTraits, args := takerTraits.Encode() + assert.Equal(t, common.HexToHash("0x8000002900000000000000000000000000000000000000000000000000000001"), encodedTakerTraits) + assert.Equal(t, hexutil.Encode(extension.Encode()), hexutil.Encode(args)) +} diff --git a/pkg/oneinch/limitorder/utils.go b/pkg/oneinch/limitorder/utils.go new file mode 100644 index 0000000..189cccf --- /dev/null +++ b/pkg/oneinch/limitorder/utils.go @@ -0,0 +1,30 @@ +package limitorder + +import "math/big" + +func newBitMask(start uint, end uint) *big.Int { + mask := big.NewInt(1) + mask.Lsh(mask, end) + mask.Sub(mask, big.NewInt(1)) + if start == 0 { + return mask + } + + notMask := newBitMask(0, start) + notMask.Not(notMask) + mask.And(mask, notMask) + + return mask +} + +func setMask(n *big.Int, mask *big.Int, value *big.Int) { + // Clear bits in range. + n.And(n, new(big.Int).Not(mask)) + + // Shift value to correct position and ensure value fits in mask. + value = new(big.Int).Lsh(value, mask.TrailingZeroBits()) + value.And(value, mask) + + // Set the bits in range. + n.Or(n, value) +} diff --git a/pkg/oneinch/limitorder/utils_test.go b/pkg/oneinch/limitorder/utils_test.go new file mode 100644 index 0000000..28af580 --- /dev/null +++ b/pkg/oneinch/limitorder/utils_test.go @@ -0,0 +1,71 @@ +//nolint:testpackage +package limitorder + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewBitMask(t *testing.T) { + tests := []struct { + start uint + end uint + expect *big.Int + }{ + { + start: 0, + end: 10, + expect: big.NewInt(0b1111111111), + }, + { + start: 5, + end: 17, + expect: big.NewInt(0b11111111111100000), + }, + } + + for _, test := range tests { + assert.Equal(t, test.expect, newBitMask(test.start, test.end)) + } +} + +func TestSetMask(t *testing.T) { + tests := []struct { + n *big.Int + mask *big.Int + value *big.Int + expect *big.Int + }{ + { + n: big.NewInt(0b1111111111111110101111111111100111), + mask: newBitMask(0, 10), + value: big.NewInt(0b0011110101), + expect: big.NewInt(0b1111111111111110101111110011110101), + }, + { + n: big.NewInt(0b1111111111111110101111111111100111), + mask: newBitMask(0, 10), + value: big.NewInt(0b11110011110101), + expect: big.NewInt(0b1111111111111110101111110011110101), + }, + { + n: big.NewInt(0b1111111111111110101111111111100111), + mask: newBitMask(5, 15), + value: big.NewInt(0b0011110101), + expect: big.NewInt(0b1111111111111110101001111010100111), + }, + { + n: big.NewInt(0b1111111111111110101111111111100111), + mask: newBitMask(5, 15), + value: big.NewInt(0b11110011110101), + expect: big.NewInt(0b1111111111111110101001111010100111), + }, + } + + for _, test := range tests { + setMask(test.n, test.mask, test.value) + assert.Equal(t, test.expect, test.n, "expect: %s, actual: %s", test.expect.Text(2), test.n.Text(2)) + } +}