From 732bd9cd6c07903710e8296010ffdc29f7115267 Mon Sep 17 00:00:00 2001 From: Chien Nguyen Date: Thu, 15 Aug 2024 18:20:42 +0700 Subject: [PATCH] Implement encode/decode settlement post interaction data --- .../settlement_post_interaction_data.go | 23 ++- ...ent_post_interaction_data_encode_decode.go | 132 ++++++++++++ ...ost_interaction_data_encode_decode_test.go | 195 ++++++++++++++++++ 3 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode.go create mode 100644 pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode_test.go diff --git a/pkg/oneinch/fusionorder/settlement_post_interaction_data.go b/pkg/oneinch/fusionorder/settlement_post_interaction_data.go index 5a8ed07..c8ea825 100644 --- a/pkg/oneinch/fusionorder/settlement_post_interaction_data.go +++ b/pkg/oneinch/fusionorder/settlement_post_interaction_data.go @@ -97,7 +97,7 @@ func NewSettlementPostInteractionDataFromSettlementSuffixData( copy(addressHalf[:], item.Address.Bytes()[common.AddressLength-addressHalfLength:]) // take the last 10 bytes whitelist = append(whitelist, WhitelistItem{ AddressHalf: addressHalf, - Delay: delay, + Delay: uint16(delay.Uint64()), }) } @@ -112,7 +112,7 @@ func NewSettlementPostInteractionDataFromSettlementSuffixData( type WhitelistItem struct { AddressHalf AddressHalf - Delay *big.Int + Delay uint16 } type IntegratorFee struct { @@ -136,3 +136,22 @@ type SettlementSuffixData struct { ResolvingStartTime *big.Int CustomReceiver common.Address } + +func (s SettlementPostInteractionData) CanExecuteAt(executor common.Address, executionTime *big.Int) bool { + var addressHalf AddressHalf + copy(addressHalf[:], executor.Bytes()[common.AddressLength-addressHalfLength:]) // take the last 10 bytes + + allowedFrom := s.ResolvingStartTime + + for _, item := range s.Whitelist { + allowedFrom = new(big.Int).Add(allowedFrom, big.NewInt(int64(item.Delay))) + + if addressHalf == item.AddressHalf { + return executionTime.Cmp(allowedFrom) >= 0 // executionTime >= allowedFrom + } else if executionTime.Cmp(allowedFrom) < 0 { // executionTime < allowedFrom + return false + } + } + + return false +} diff --git a/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode.go b/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode.go new file mode 100644 index 0000000..1bb3e84 --- /dev/null +++ b/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode.go @@ -0,0 +1,132 @@ +package fusionorder + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math/big" + + "github.com/KyberNetwork/tradinglib/pkg/oneinch/utils" + "github.com/ethereum/go-ethereum/common" +) + +const ( + resolverFeeFlag = 0x01 + integratorFeeFlag = 0x02 + customReceiverFlag = 0x04 + whitelistShift = 3 +) + +func resolverFeeEnabled(flags byte) bool { + return flags&resolverFeeFlag == resolverFeeFlag +} + +func integratorFeeEnabled(flags byte) bool { + return flags&integratorFeeFlag == integratorFeeFlag +} + +func hasCustomReceiver(flags byte) bool { + return flags&customReceiverFlag == customReceiverFlag +} + +func resolversCount(flags byte) byte { + return flags >> whitelistShift +} + +func DecodeSettlementPostInteractionData(extraData string) (SettlementPostInteractionData, error) { + if !utils.IsHexString(extraData) { + return SettlementPostInteractionData{}, errors.New("invalid auction details data") + } + + data, err := hex.DecodeString(utils.Trim0x(extraData)) + if err != nil { + return SettlementPostInteractionData{}, fmt.Errorf("decode settlement post interaction data: %w", err) + } + + flags := data[len(data)-1] + + bankFee := big.NewInt(0) + var integratorFee IntegratorFee + var customReceiver common.Address + resolvingStartTime := big.NewInt(0) + + if resolverFeeEnabled(flags) { + bankFee.SetBytes(data[:4]) + data = data[4:] + } + + if integratorFeeEnabled(flags) { + integratorFeeRatio := new(big.Int).SetBytes(data[:2]) + integratorAddress := common.BytesToAddress(data[2:22]) + integratorFee = IntegratorFee{ + Ratio: integratorFeeRatio, + Receiver: integratorAddress, + } + + data = data[22:] + + if hasCustomReceiver(flags) { + customReceiver = common.BytesToAddress(data[:20]) + data = data[20:] + } + } + + resolvingStartTime = new(big.Int).SetBytes(data[:4]) + data = data[4:] + + whitelistCount := resolversCount(flags) + whitelist := make([]WhitelistItem, 0, whitelistCount) + + for i := byte(0); i < whitelistCount; i++ { + var address AddressHalf + copy(address[:], data[:addressHalfLength]) + data = data[addressHalfLength:] + + delay := new(big.Int).SetBytes(data[:2]) + data = data[2:] + + whitelist = append(whitelist, WhitelistItem{ + AddressHalf: address, + Delay: uint16(delay.Uint64()), + }) + } + + return SettlementPostInteractionData{ + Whitelist: whitelist, + IntegratorFee: integratorFee, + BankFee: bankFee, + ResolvingStartTime: resolvingStartTime, + CustomReceiver: customReceiver, + }, nil +} + +func (s SettlementPostInteractionData) Encode() string { + buf := new(bytes.Buffer) + var flags byte + if s.BankFee != nil && s.BankFee.Cmp(big.NewInt(0)) != 0 { + flags |= resolverFeeFlag + buf.Write(utils.PadOrTrim(s.BankFee.Bytes(), 4)) + } + if s.IntegratorFee.Ratio != nil && s.IntegratorFee.Ratio.Cmp(big.NewInt(0)) != 0 { + flags |= integratorFeeFlag + buf.Write(utils.PadOrTrim(s.IntegratorFee.Ratio.Bytes(), 2)) + buf.Write(s.IntegratorFee.Receiver.Bytes()) + if s.CustomReceiver != (common.Address{}) { + flags |= customReceiverFlag + buf.Write(s.CustomReceiver.Bytes()) + } + } + buf.Write(utils.PadOrTrim(s.ResolvingStartTime.Bytes(), 4)) + + for _, wl := range s.Whitelist { + buf.Write(wl.AddressHalf[:]) + buf.Write(utils.PadOrTrim(big.NewInt(int64(int(wl.Delay))).Bytes(), 2)) + } + + flags |= byte(len(s.Whitelist)) << whitelistShift + + buf.WriteByte(flags) + + return utils.Add0x(hex.EncodeToString(buf.Bytes())) +} diff --git a/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode_test.go b/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode_test.go new file mode 100644 index 0000000..f15d4d4 --- /dev/null +++ b/pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode_test.go @@ -0,0 +1,195 @@ +package fusionorder_test + +import ( + "math/big" + "testing" + + "github.com/KyberNetwork/tradinglib/pkg/oneinch/fusionorder" + "github.com/KyberNetwork/tradinglib/pkg/oneinch/fusionutils" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Those tests are copied from +// https://github.com/1inch/fusion-sdk/blob/8721c62612b08cc7c0e01423a1bdd62594e7b8d0/src/fusion-order/settlement-post-interaction-data/settlement-post-interaction-data.spec.ts#L6 +func TestSettlementPostInteractionData(t *testing.T) { + t.Run("Should encode/decode with bank fee and whitelist", func(t *testing.T) { + data, err := fusionorder.NewSettlementPostInteractionDataFromSettlementSuffixData( + fusionorder.SettlementSuffixData{ + BankFee: big.NewInt(1), + ResolvingStartTime: big.NewInt(1708117482), + Whitelist: []fusionorder.AuctionWhitelistItem{ + { + Address: common.BigToAddress(big.NewInt(0)), + AllowFrom: big.NewInt(0), + }, + }, + }, + ) + require.NoError(t, err) + + encoded := data.Encode() + + assert.Equal(t, 44, len(encoded)) + + decoded, err := fusionorder.DecodeSettlementPostInteractionData(encoded) + require.NoError(t, err) + + assertSettlementPostInteractionDataEqual(t, data, decoded) + }) + + t.Run("Should encode/decode with no fees and whitelist", func(t *testing.T) { + data, err := fusionorder.NewSettlementPostInteractionDataFromSettlementSuffixData( + fusionorder.SettlementSuffixData{ + ResolvingStartTime: big.NewInt(1708117482), + Whitelist: []fusionorder.AuctionWhitelistItem{ + { + Address: common.BigToAddress(big.NewInt(0)), + AllowFrom: big.NewInt(0), + }, + }, + }, + ) + require.NoError(t, err) + + encoded := data.Encode() + + assert.Equal(t, 36, len(encoded)) + + decoded, err := fusionorder.DecodeSettlementPostInteractionData(encoded) + require.NoError(t, err) + + assertSettlementPostInteractionDataEqual(t, data, decoded) + }) + + t.Run("Should encode/decode with fees and whitelist", func(t *testing.T) { + data, err := fusionorder.NewSettlementPostInteractionDataFromSettlementSuffixData( + fusionorder.SettlementSuffixData{ + BankFee: big.NewInt(0), + ResolvingStartTime: big.NewInt(1708117482), + Whitelist: []fusionorder.AuctionWhitelistItem{ + { + Address: common.BigToAddress(big.NewInt(0)), + AllowFrom: big.NewInt(0), + }, + }, + IntegratorFee: fusionorder.IntegratorFee{ + Ratio: fusionutils.BpsToRatioFormat(10), + Receiver: common.BigToAddress(big.NewInt(1)), + }, + }, + ) + require.NoError(t, err) + + decoded, err := fusionorder.DecodeSettlementPostInteractionData(data.Encode()) + require.NoError(t, err) + + assertSettlementPostInteractionDataEqual(t, data, decoded) + }) + t.Run("Should encode/decode with fees, custom receiver and whitelist", func(t *testing.T) { + data, err := fusionorder.NewSettlementPostInteractionDataFromSettlementSuffixData( + fusionorder.SettlementSuffixData{ + BankFee: big.NewInt(0), + ResolvingStartTime: big.NewInt(1708117482), + Whitelist: []fusionorder.AuctionWhitelistItem{ + { + Address: common.BigToAddress(big.NewInt(0)), + AllowFrom: big.NewInt(0), + }, + }, + IntegratorFee: fusionorder.IntegratorFee{ + Ratio: fusionutils.BpsToRatioFormat(10), + Receiver: common.BigToAddress(big.NewInt(1)), + }, + CustomReceiver: common.BigToAddress(big.NewInt(1337)), + }, + ) + require.NoError(t, err) + + decoded, err := fusionorder.DecodeSettlementPostInteractionData(data.Encode()) + require.NoError(t, err) + + assertSettlementPostInteractionDataEqual(t, data, decoded) + }) + + t.Run("Should generate correct whitelist", func(t *testing.T) { + start := big.NewInt(1708117482) + + data, err := fusionorder.NewSettlementPostInteractionDataFromSettlementSuffixData( + fusionorder.SettlementSuffixData{ + ResolvingStartTime: start, + Whitelist: []fusionorder.AuctionWhitelistItem{ + { + Address: common.BigToAddress(big.NewInt(2)), + AllowFrom: new(big.Int).Add(start, big.NewInt(1_000)), + }, + { + Address: common.BigToAddress(big.NewInt(0)), + AllowFrom: new(big.Int).Sub(start, big.NewInt(10)), // should be set to start + }, + { + Address: common.BigToAddress(big.NewInt(1)), + AllowFrom: new(big.Int).Add(start, big.NewInt(10)), + }, + { + Address: common.BigToAddress(big.NewInt(3)), + AllowFrom: new(big.Int).Add(start, big.NewInt(10)), + }, + }, + }, + ) + require.NoError(t, err) + + expectedWhitelist := []fusionorder.WhitelistItem{ + { + AddressHalf: fusionorder.AddressHalf{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Delay: 0, + }, + { + AddressHalf: fusionorder.AddressHalf{0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + Delay: 10, + }, + { + AddressHalf: fusionorder.AddressHalf{0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, + Delay: 0, + }, + { + AddressHalf: fusionorder.AddressHalf{0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, + Delay: 990, + }, + } + + assert.ElementsMatch(t, expectedWhitelist, data.Whitelist) + + assert.True(t, + data.CanExecuteAt(common.BigToAddress(big.NewInt(1)), + new(big.Int).Add(start, big.NewInt(10))), + ) + assert.False(t, + data.CanExecuteAt(common.BigToAddress(big.NewInt(1)), + new(big.Int).Add(start, big.NewInt(9))), + ) + assert.True(t, + data.CanExecuteAt(common.BigToAddress(big.NewInt(3)), + new(big.Int).Add(start, big.NewInt(10))), + ) + assert.False(t, + data.CanExecuteAt(common.BigToAddress(big.NewInt(3)), + new(big.Int).Add(start, big.NewInt(9))), + ) + assert.False(t, + data.CanExecuteAt(common.BigToAddress(big.NewInt(2)), + new(big.Int).Add(start, big.NewInt(50))), + ) + }) +} + +func assertSettlementPostInteractionDataEqual(t *testing.T, expected, actual fusionorder.SettlementPostInteractionData) { + assert.ElementsMatch(t, expected.Whitelist, actual.Whitelist) + assert.Equal(t, expected.IntegratorFee.Ratio, actual.IntegratorFee.Ratio) + assert.Equal(t, expected.IntegratorFee.Receiver, actual.IntegratorFee.Receiver) + assert.Equal(t, expected.BankFee, actual.BankFee) + assert.Equal(t, expected.ResolvingStartTime, actual.ResolvingStartTime) + assert.Equal(t, expected.CustomReceiver, actual.CustomReceiver) +}