Skip to content

Commit

Permalink
Patch txpool core/list for managing multi currencies (#43) (#55)
Browse files Browse the repository at this point in the history
Modifies the legacypool, list, and priceheap to allow for multi currency txs.
  • Loading branch information
hbandura authored Feb 22, 2024
1 parent 6c346a9 commit 4161e67
Show file tree
Hide file tree
Showing 23 changed files with 1,057 additions and 229 deletions.
29 changes: 29 additions & 0 deletions common/celo_types.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
package common

import (
"math/big"
)

var (
ZeroAddress = BytesToAddress([]byte{})
)

type ExchangeRates = map[Address]*big.Rat

func IsCurrencyWhitelisted(exchangeRates ExchangeRates, feeCurrency *Address) bool {
if feeCurrency == nil {
return true
}

// Check if fee currency is registered
_, ok := exchangeRates[*feeCurrency]
return ok
}

func AreSameAddress(a, b *Address) bool {
// both are nil or point to the same address
if a == b {
return true
}
// if only one is nil
if a == nil || b == nil {
return false
}
// if they point to the same
return *a == *b
}
47 changes: 47 additions & 0 deletions common/celo_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package common

import (
"math/big"
"testing"
)

var (
currA = HexToAddress("0xA")
currB = HexToAddress("0xB")
currX = HexToAddress("0xF")
exchangeRates = ExchangeRates{
currA: big.NewRat(47, 100),
currB: big.NewRat(45, 100),
}
)

func TestIsWhitelisted(t *testing.T) {
tests := []struct {
name string
feeCurrency *Address
want bool
}{
{
name: "no fee currency",
feeCurrency: nil,
want: true,
},
{
name: "valid fee currency",
feeCurrency: &currA,
want: true,
},
{
name: "invalid fee currency",
feeCurrency: &currX,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsCurrencyWhitelisted(exchangeRates, tt.feeCurrency); got != tt.want {
t.Errorf("IsWhitelisted() = %v, want %v", got, tt.want)
}
})
}
}
102 changes: 102 additions & 0 deletions common/exchange/rates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package exchange

import (
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)

var (
unitRate = big.NewRat(1, 1)
// ErrNonWhitelistedFeeCurrency is returned if the currency specified to use for the fees
// isn't one of the currencies whitelisted for that purpose.
ErrNonWhitelistedFeeCurrency = errors.New("non-whitelisted fee currency address")
)

// ConvertCurrency does an exchange conversion from currencyFrom to currencyTo of the value given.
func ConvertCurrency(exchangeRates common.ExchangeRates, val1 *big.Int, currencyFrom *common.Address, currencyTo *common.Address) *big.Int {
goldAmount, err := ConvertCurrencyToGold(exchangeRates, val1, currencyFrom)
if err != nil {
log.Error("Error trying to convert from currency to gold.", "value", val1, "fromCurrency", currencyFrom.Hex())
}
toAmount, err := ConvertGoldToCurrency(exchangeRates, currencyTo, goldAmount)
if err != nil {
log.Error("Error trying to convert from gold to currency.", "value", goldAmount, "toCurrency", currencyTo.Hex())
}
return toAmount
}

func ConvertCurrencyToGold(exchangeRates common.ExchangeRates, currencyAmount *big.Int, feeCurrency *common.Address) (*big.Int, error) {
if feeCurrency == nil {
return currencyAmount, nil
}
exchangeRate, ok := exchangeRates[*feeCurrency]
if !ok {
return nil, ErrNonWhitelistedFeeCurrency
}
return new(big.Int).Div(new(big.Int).Mul(currencyAmount, exchangeRate.Denom()), exchangeRate.Num()), nil
}

func ConvertGoldToCurrency(exchangeRates common.ExchangeRates, feeCurrency *common.Address, goldAmount *big.Int) (*big.Int, error) {
if feeCurrency == nil {
return goldAmount, nil
}
exchangeRate, ok := exchangeRates[*feeCurrency]
if !ok {
return nil, ErrNonWhitelistedFeeCurrency
}
return new(big.Int).Div(new(big.Int).Mul(goldAmount, exchangeRate.Num()), exchangeRate.Denom()), nil
}

func getRate(exchangeRates common.ExchangeRates, feeCurrency *common.Address) (*big.Rat, error) {
if feeCurrency == nil {
return unitRate, nil
}
rate, ok := exchangeRates[*feeCurrency]
if !ok {
return nil, fmt.Errorf("fee currency not registered: %s", feeCurrency.Hex())
}
return rate, nil
}

// CompareValue compares values in different currencies (nil currency is native currency)
// returns -1 0 or 1 depending if val1 < val2, val1 == val2, or val1 > val2 respectively.
func CompareValue(exchangeRates common.ExchangeRates, val1 *big.Int, feeCurrency1 *common.Address, val2 *big.Int, feeCurrency2 *common.Address) (int, error) {
// Short circuit if the fee currency is the same.
if feeCurrency1 == feeCurrency2 {
return val1.Cmp(val2), nil
}

exchangeRate1, err := getRate(exchangeRates, feeCurrency1)
if err != nil {
return 0, err
}
exchangeRate2, err := getRate(exchangeRates, feeCurrency2)
if err != nil {
return 0, err
}

// Below code block is basically evaluating this comparison:
// val1 * exchangeRate1.denominator / exchangeRate1.numerator < val2 * exchangeRate2.denominator / exchangeRate2.numerator
// It will transform that comparison to this, to remove having to deal with fractional values.
// val1 * exchangeRate1.denominator * exchangeRate2.numerator < val2 * exchangeRate2.denominator * exchangeRate1.numerator
leftSide := new(big.Int).Mul(
val1,
new(big.Int).Mul(
exchangeRate1.Denom(),
exchangeRate2.Num(),
),
)
rightSide := new(big.Int).Mul(
val2,
new(big.Int).Mul(
exchangeRate2.Denom(),
exchangeRate1.Num(),
),
)

return leftSide.Cmp(rightSide), nil
}
171 changes: 171 additions & 0 deletions common/exchange/rates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package exchange

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
)

var (
currA = common.HexToAddress("0xA")
currB = common.HexToAddress("0xB")
currX = common.HexToAddress("0xF")
exchangeRates = common.ExchangeRates{
currA: big.NewRat(47, 100),
currB: big.NewRat(45, 100),
}
)

func TestCompareFees(t *testing.T) {
type args struct {
val1 *big.Int
feeCurrency1 *common.Address
val2 *big.Int
feeCurrency2 *common.Address
}
tests := []struct {
name string
args args
wantResult int
wantErr bool
}{
// Native currency
{
name: "Same amount of native currency",
args: args{
val1: big.NewInt(1),
feeCurrency1: nil,
val2: big.NewInt(1),
feeCurrency2: nil,
},
wantResult: 0,
}, {
name: "Different amounts of native currency 1",
args: args{
val1: big.NewInt(2),
feeCurrency1: nil,
val2: big.NewInt(1),
feeCurrency2: nil,
},
wantResult: 1,
}, {
name: "Different amounts of native currency 2",
args: args{
val1: big.NewInt(1),
feeCurrency1: nil,
val2: big.NewInt(5),
feeCurrency2: nil,
},
wantResult: -1,
},
// Mixed currency
{
name: "Same amount of mixed currency",
args: args{
val1: big.NewInt(1),
feeCurrency1: nil,
val2: big.NewInt(1),
feeCurrency2: &currA,
},
wantResult: -1,
}, {
name: "Different amounts of mixed currency 1",
args: args{
val1: big.NewInt(100),
feeCurrency1: nil,
val2: big.NewInt(47),
feeCurrency2: &currA,
},
wantResult: 0,
}, {
name: "Different amounts of mixed currency 2",
args: args{
val1: big.NewInt(45),
feeCurrency1: &currB,
val2: big.NewInt(100),
feeCurrency2: nil,
},
wantResult: 0,
},
// Two fee currencies
{
name: "Same amount of same currency",
args: args{
val1: big.NewInt(1),
feeCurrency1: &currA,
val2: big.NewInt(1),
feeCurrency2: &currA,
},
wantResult: 0,
}, {
name: "Different amounts of same currency 1",
args: args{
val1: big.NewInt(3),
feeCurrency1: &currA,
val2: big.NewInt(1),
feeCurrency2: &currA,
},
wantResult: 1,
}, {
name: "Different amounts of same currency 2",
args: args{
val1: big.NewInt(1),
feeCurrency1: &currA,
val2: big.NewInt(7),
feeCurrency2: &currA,
},
wantResult: -1,
}, {
name: "Different amounts of different currencies 1",
args: args{
val1: big.NewInt(47),
feeCurrency1: &currA,
val2: big.NewInt(45),
feeCurrency2: &currB,
},
wantResult: 0,
}, {
name: "Different amounts of different currencies 2",
args: args{
val1: big.NewInt(48),
feeCurrency1: &currA,
val2: big.NewInt(45),
feeCurrency2: &currB,
},
wantResult: 1,
}, {
name: "Different amounts of different currencies 3",
args: args{
val1: big.NewInt(47),
feeCurrency1: &currA,
val2: big.NewInt(46),
feeCurrency2: &currB,
},
wantResult: -1,
},
// Unregistered fee currency
{
name: "Different amounts of different currencies",
args: args{
val1: big.NewInt(1),
feeCurrency1: &currA,
val2: big.NewInt(1),
feeCurrency2: &currX,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CompareValue(exchangeRates, tt.args.val1, tt.args.feeCurrency1, tt.args.val2, tt.args.feeCurrency2)

if tt.wantErr && err == nil {
t.Error("Expected error in CompareValue()")
}
if got != tt.wantResult {
t.Errorf("CompareValue() = %v, want %v", got, tt.wantResult)
}
})
}
}
Loading

0 comments on commit 4161e67

Please sign in to comment.