Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch txpool core/list for managing multi currencies #43

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading