Skip to content

Commit

Permalink
Implement encode/decode settlement post interaction data
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisngyn committed Aug 16, 2024
1 parent 1b107c9 commit 732bd9c
Show file tree
Hide file tree
Showing 3 changed files with 348 additions and 2 deletions.
23 changes: 21 additions & 2 deletions pkg/oneinch/fusionorder/settlement_post_interaction_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
})
}

Expand All @@ -112,7 +112,7 @@ func NewSettlementPostInteractionDataFromSettlementSuffixData(

type WhitelistItem struct {
AddressHalf AddressHalf
Delay *big.Int
Delay uint16
}

type IntegratorFee struct {
Expand All @@ -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
}
Original file line number Diff line number Diff line change
@@ -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()))
}
Original file line number Diff line number Diff line change
@@ -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) {

Check failure on line 16 in pkg/oneinch/fusionorder/settlement_post_interaction_data_encode_decode_test.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

Function 'TestSettlementPostInteractionData' is too long (169 > 80) (funlen)
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)
}

0 comments on commit 732bd9c

Please sign in to comment.