Last active
September 10, 2019 20:53
-
-
Save Emyrk/68f98b7aca65208269dded185682c972 to your computer and use it in GitHub Desktop.
Pegnet Conversion Math
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"fmt" | |
"math/big" | |
) | |
// OraclePriceRecordAssetList is used such that the marshaling of the assets | |
// is in the same order, and we still can use map access in the code | |
// Key: Asset | |
// Value: Exchange rate to USD * 1e8 | |
type OraclePriceRecordAssetList map[string]uint64 | |
// ExchangeTo tells us how much we need to spend given the amount we want is fixed. All inputs must be in their | |
// lowest divisible unit as whole numbers. | |
// ?? FROM -> X TO | |
// | |
// X TO to_usd 1 | |
// ------ * --------- = USD * -------- = FROM | |
// 1 1 from_usd | |
func (o OraclePriceRecordAssetList) ExchangeTo(from string, to string, want int64) (int64, error) { | |
return o.Exchange(to, want, from) | |
} | |
// ExchangeFrom tells us how much we need to spend given the amount we have is fixed. All inputs must be in their | |
// lowest divisible unit as whole numbers. | |
// X FROM -> ?? TO | |
// | |
// X FROM from_usd 1 | |
// ------ * --------- = USD * -------- = TO | |
// 1 1 to_usd | |
// | |
func (o OraclePriceRecordAssetList) ExchangeFrom(from string, have int64, to string) (int64, error) { | |
return o.Exchange(from, have, to) | |
} | |
// Exchange will use big ints to avoid overflows. All inputs must be in their | |
// lowest divisible unit as whole numbers. | |
// TODO: Will we ever overflow a int64? | |
func (o OraclePriceRecordAssetList) Exchange(input string, amount int64, output string) (int64, error) { | |
fromRate, toRate, err := o.ExchangeRates(input, output) | |
if err != nil { | |
return 0, err | |
} | |
// Convert the rates to integers. Because these rates are in USD, we will switch all our inputs to | |
// 1e-8 fixed point. The `want` should already be in this format. This should be the most amount of | |
// accuracy a miner reports. Anything beyond the 8th decimal point, we cannot account for. | |
fr := big.NewInt(int64(fromRate)) | |
tr := big.NewInt(int64(toRate )) | |
amt := big.NewInt(amount) | |
// Now we can run the conversion | |
// ALWAYS multiply first. If you do not adhere to the order of operations shown | |
// explicitly below, your answer will be incorrect. When doing a conversion, | |
// always multiply before you divide. | |
// (amt * fromrate) / torate | |
num := big.NewInt(0).Mul(amt, fr) | |
num = num.Div(num, tr) | |
return num.Int64(), nil | |
} | |
// ExchangeRates finds the exchange rates for FROM and TO in usd as the base pair. | |
func (o OraclePriceRecordAssetList) ExchangeRates(from, to string) (fromRate uint64, toRate uint64, err error) { | |
var ok bool | |
// First we need to ensure we have the pricing for each side of the exchange | |
fromRate, ok = o[from] | |
if !ok { | |
return 0, 0, fmt.Errorf("did not find a rate for %s", from) | |
} | |
toRate, ok = o[to] | |
if !ok { | |
return 0, 0, fmt.Errorf("did not find a rate for %s", to) | |
} | |
if toRate == 0 || fromRate == 0 { | |
return 0, 0, fmt.Errorf("one of the rates found is 0") | |
} | |
// fromRate / toRate | |
return fromRate, toRate, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
L39-L60 is the implementation of the math for a conversion for the tx
amount INPUT -> OUTPUT
Notes: We store price quotes as uint64, but a BigInt requires int64 as the input. We need to decide if we need to handle overflows, and keep balances as big ints vs int64s.