Files
atomic-swap/coins/coins_test.go
2023-05-30 21:49:22 -05:00

209 lines
7.0 KiB
Go

// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only
package coins
import (
"encoding/json"
"math"
"math/big"
"testing"
"github.com/cockroachdb/apd/v3"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPiconeroAmount(t *testing.T) {
const preciseAmount = "666.666666666666666666" // 18 sixes after the Decimal
const moneroAmount = "666.666666666667" // 12 digits after Decimal saved
const piconeroAmount = "666666666666667" // 15 digits rounded
amount := StrToDecimal(preciseAmount)
piconero := MoneroToPiconero(amount)
assert.Equal(t, moneroAmount, piconero.AsMoneroString())
assert.Equal(t, piconeroAmount, piconero.String())
}
func TestMoneroToPiconero(t *testing.T) {
xrmAmount := StrToDecimal("2")
const expectedPiconeros = "2000000000000"
piconeroAmount := MoneroToPiconero(xrmAmount)
assert.Equal(t, expectedPiconeros, piconeroAmount.String())
}
func TestMoneroToPiconero_roundUp(t *testing.T) {
//
// This test is merely demonstrating the current behavior. It is not
// entirely clear if the ideal behavior is to round-half-up, truncate,
// or just store fractional piconeros.
//
xrmAmount := StrToDecimal("1.0000000000005") // 12 zeros, then "5"
const expectedPiconeros = "1000000000001"
piconeroAmount := MoneroToPiconero(xrmAmount)
assert.Equal(t, expectedPiconeros, piconeroAmount.String())
}
func TestMoneroToPiconero_roundDown(t *testing.T) {
xrmAmount := StrToDecimal("1.00000000000049") // 12 zeros, then "49"
const expectedPiconeros = "1000000000000"
piconeroAmount := MoneroToPiconero(xrmAmount)
assert.Equal(t, expectedPiconeros, piconeroAmount.String())
}
func TestNewPiconeroAmount(t *testing.T) {
onePn := NewPiconeroAmount(1)
oneU64, err := onePn.Uint64()
require.NoError(t, err)
assert.Equal(t, oneU64, uint64(1))
}
func TestPiconeroAmount_Uint64(t *testing.T) {
// MaxUint64 should work
piconeros := NewPiconeroAmount(math.MaxUint64)
piconerosU64, err := piconeros.Uint64()
require.NoError(t, err)
assert.Equal(t, uint64(math.MaxUint64), piconerosU64)
// MaxUint64+1 should return an error
one := apd.New(1, 0)
_, err = decimalCtx.Add(piconeros.Decimal(), piconeros.Decimal(), one)
require.NoError(t, err)
_, err = piconeros.Uint64()
assert.ErrorContains(t, err, "value out of range")
// Negative values, which we should never have, return an error
piconeros.Decimal().Set(apd.New(-1, 0))
_, err = piconeros.Uint64()
assert.ErrorContains(t, err, "cannot convert")
}
func TestWeiAmount(t *testing.T) {
amount := StrToDecimal("33.3")
wei := EtherToWei(amount)
assert.Equal(t, "33300000000000000000", wei.String())
assert.Equal(t, "33.3", wei.AsEther().String())
assert.Equal(t, "33.3", wei.AsStdString())
amountUint := int64(8181)
WeiAmount := IntToWei(amountUint)
assert.Equal(t, amountUint, WeiAmount.BigInt().Int64())
}
func TestBigInt2Wei(t *testing.T) {
bi := big.NewInt(4321)
wei := NewWeiAmount(bi)
assert.Equal(t, "4321", wei.String())
}
func TestWeiAmount_BigInt(t *testing.T) {
amount := StrToDecimal("0.12345678901234567890") // 20 decimal points, after 18 is partial wei
const expectedWei = "123456789012345679" // 8 at the end rounded to 9
wei := EtherToWei(amount)
require.Equal(t, expectedWei, wei.String())
// EtherToWei already rounded, reset the internal value so BigInt() needs to round
wei.Decimal().Set(amount) // reset to ether value
wei.Decimal().Exponent += NumEtherDecimals // turn the ether into wei
assert.Equal(t, expectedWei, wei.BigInt().String()) // BigInt() also rounds if needed
}
func TestERC20TokenAmount(t *testing.T) {
const numDecimals = 9
tokenInfo := NewERC20TokenInfo(ethcommon.Address{}, numDecimals, "", "")
amount := StrToDecimal("33.999999999")
tokenAmt := NewTokenAmountFromDecimals(amount, tokenInfo)
assert.Equal(t, amount.String(), tokenAmt.AsStdString())
amount = StrToDecimal("33.000000005")
tokenAmt = NewTokenAmountFromDecimals(amount, tokenInfo)
assert.Equal(t, "33.000000005", tokenAmt.AsStdString())
amount = StrToDecimal("33.0000000005")
tokenAmt = NewTokenAmountFromDecimals(amount, tokenInfo)
assert.Equal(t, "33.000000001", tokenAmt.AsStdString())
amount = StrToDecimal("999999999999999999.0000000005")
tokenAmt = NewTokenAmountFromDecimals(amount, tokenInfo)
assert.Equal(t, "999999999999999999.000000001", tokenAmt.AsStdString())
}
func TestNewERC20TokenAmountFromBigInt(t *testing.T) {
bi := big.NewInt(4321)
tokenAmt := NewERC20TokenAmountFromBigInt(bi, &ERC20TokenInfo{NumDecimals: 2})
assert.Equal(t, "43.21", tokenAmt.String())
assert.Equal(t, "43.21", tokenAmt.AsStdString())
assert.Equal(t, "4321", tokenAmt.BigInt().String())
}
func TestNewERC20TokenAmountFromDecimals(t *testing.T) {
stdAmount := StrToDecimal("0.19")
token := NewTokenAmountFromDecimals(stdAmount, &ERC20TokenInfo{NumDecimals: 1})
// There's only one decimal place, so this is getting rounded to 2
// under the current implementation
assert.Equal(t, "0.2", token.String())
}
func TestJSONMarshal(t *testing.T) {
// NOTE: At the current time, ERC20TokenAmount only has private members and
// is not serializable.
type TestTypes struct {
Piconeros *PiconeroAmount `json:"piconeros"`
Wei *WeiAmount `json:"wei"`
Rate *ExchangeRate `json:"rate"`
}
tt := &TestTypes{
Piconeros: NewPiconeroAmount(10),
Wei: IntToWei(20),
Rate: ToExchangeRate(StrToDecimal("0.4")),
}
const expectedJSON = `{
"piconeros": "10",
"wei": "20",
"rate": "0.4"
}`
data, err := json.Marshal(tt)
require.NoError(t, err)
assert.JSONEq(t, expectedJSON, string(data))
// Test Unmarshal
tt2 := new(TestTypes)
err = json.Unmarshal([]byte(expectedJSON), tt2)
require.NoError(t, err)
assert.Zero(t, tt.Piconeros.Cmp(tt2.Piconeros))
assert.Zero(t, tt.Piconeros.CmpU64(10))
assert.Zero(t, tt.Wei.BigInt().Cmp(tt2.Wei.BigInt()))
assert.Equal(t, tt.Rate.String(), tt2.Rate.String())
// Test Unmarshal missing fields produces nil
tt = new(TestTypes)
err = json.Unmarshal([]byte(`{}`), tt)
require.NoError(t, err)
assert.Nil(t, tt.Piconeros)
assert.Nil(t, tt.Wei)
assert.Nil(t, tt.Rate)
// Test Unmarshal empty strings produces error
tt = new(TestTypes)
err = json.Unmarshal([]byte(`{ "piconeros": "" }`), tt)
require.Error(t, err)
err = json.Unmarshal([]byte(`{ "wei": "" }`), tt)
require.Error(t, err)
err = json.Unmarshal([]byte(`{ "rate": "" }`), tt)
require.Error(t, err)
// Test that Unmarshalling negative values produces an error. Note: In most
// places we marshal/unmarshal apd.Decimal directly. In those cases, input
// validation in the receiving method is required to prevent negative values.
tt = new(TestTypes)
err = json.Unmarshal([]byte(`{ "piconeros": "-2" }`), tt)
require.ErrorIs(t, err, errNegativePiconeros)
err = json.Unmarshal([]byte(`{ "wei": "-3" }`), tt)
require.ErrorIs(t, err, errNegativeWei)
err = json.Unmarshal([]byte(`{ "rate": "-0.1" }`), tt)
require.ErrorContains(t, err, `"exchangeRate" cannot be negative`)
}