decred.org/dcrdex@v1.0.5/dex/asset.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package dex
     5  
     6  import (
     7  	"fmt"
     8  	"math"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  )
    13  
    14  const (
    15  	UnsupportedScriptError = ErrorKind("unsupported script type")
    16  
    17  	defaultLockTimeTaker = 8 * time.Hour
    18  	defaultlockTimeMaker = 20 * time.Hour
    19  
    20  	secondsPerMinute  int64 = 60
    21  	secondsPerDay           = 24 * 60 * secondsPerMinute
    22  	BondExpiryMainnet       = 30 * secondsPerDay     // 30 days
    23  	BondExpiryTestnet       = 180 * secondsPerMinute // 3 hours
    24  	BondExpirySimnet        = 4 * secondsPerMinute   // 4 minutes
    25  )
    26  
    27  var (
    28  	// These string variables are defined to enable setting custom locktime values
    29  	// that may be used instead of `DefaultLockTimeTaker` and `DefaultLockTimeMaker`
    30  	// IF running on a test network (testnet or regtest) AND both values are set to
    31  	// valid duration strings.
    32  	// Values for both variables may be set at build time using linker flags e.g:
    33  	// go build -ldflags "-X 'decred.org/dcrdex/dex.testLockTimeTaker=10m' \
    34  	// -X 'decred.org/dcrdex/dex.testLockTimeMaker=20m'"
    35  	// Same values should be set when building server and client binaries.
    36  	testLockTimeTaker string
    37  	testLockTimeMaker string
    38  
    39  	testLockTime struct {
    40  		taker time.Duration
    41  		maker time.Duration
    42  	}
    43  )
    44  
    45  // Set test locktime values on init. Panics if invalid or 0-second duration
    46  // strings are provided.
    47  func init() {
    48  	if testLockTimeTaker == "" && testLockTimeMaker == "" {
    49  		testLockTime.taker, testLockTime.maker = defaultLockTimeTaker, defaultlockTimeMaker
    50  		return
    51  	}
    52  	testLockTime.taker, _ = time.ParseDuration(testLockTimeTaker)
    53  	if testLockTime.taker.Seconds() == 0 {
    54  		panic(fmt.Sprintf("invalid value for testLockTimeTaker: %q", testLockTimeTaker))
    55  	}
    56  	testLockTime.maker, _ = time.ParseDuration(testLockTimeMaker)
    57  	if testLockTime.maker.Seconds() == 0 {
    58  		panic(fmt.Sprintf("invalid value for testLockTimeMaker: %q", testLockTimeMaker))
    59  	}
    60  }
    61  
    62  // LockTimeTaker returns the taker locktime value that should be used by both
    63  // client and server for the specified network. Mainnet uses a constant value
    64  // while test networks support setting a custom value during build.
    65  func LockTimeTaker(network Network) time.Duration {
    66  	if network == Mainnet {
    67  		return defaultLockTimeTaker
    68  	}
    69  	return testLockTime.taker
    70  }
    71  
    72  // LockTimeMaker returns the maker locktime value that should be used by both
    73  // client and server for the specified network. Mainnet uses a constant value
    74  // while test networks support setting a custom value during build.
    75  func LockTimeMaker(network Network) time.Duration {
    76  	if network == Mainnet {
    77  		return defaultlockTimeMaker
    78  	}
    79  	return testLockTime.maker
    80  }
    81  
    82  // BondExpiry returns the bond expiry duration in seconds for a given network.
    83  func BondExpiry(net Network) int64 {
    84  	switch net {
    85  	case Mainnet:
    86  		return BondExpiryMainnet
    87  	case Testnet:
    88  		return BondExpiryTestnet
    89  	default: // Regtest, Simnet, other
    90  		return BondExpirySimnet
    91  	}
    92  }
    93  
    94  // Network flags passed to asset backends to signify which network to use.
    95  type Network uint8
    96  
    97  const (
    98  	Mainnet Network = iota
    99  	Testnet
   100  	Regtest
   101  )
   102  
   103  // Simnet is an alias of Regtest.
   104  const Simnet = Regtest
   105  
   106  // String returns the string representation of a Network.
   107  func (n Network) String() string {
   108  	switch n {
   109  	case Mainnet:
   110  		return "mainnet"
   111  	case Testnet:
   112  		return "testnet"
   113  	case Simnet:
   114  		return "simnet"
   115  	}
   116  	return ""
   117  }
   118  
   119  // NetFromString returns the Network for the given network name.
   120  func NetFromString(net string) (Network, error) {
   121  	switch strings.ToLower(net) {
   122  	case "mainnet":
   123  		return Mainnet, nil
   124  	case "testnet":
   125  		return Testnet, nil
   126  	case "regtest", "regnet", "simnet":
   127  		return Simnet, nil
   128  	}
   129  	return 255, fmt.Errorf("unknown network %s", net)
   130  }
   131  
   132  // Asset is the configurable asset variables.
   133  type Asset struct {
   134  	ID         uint32   `json:"id"`
   135  	Symbol     string   `json:"symbol"`
   136  	Version    uint32   `json:"version"`
   137  	MaxFeeRate uint64   `json:"maxFeeRate"`
   138  	SwapConf   uint32   `json:"swapConf"`
   139  	UnitInfo   UnitInfo `json:"unitInfo"`
   140  }
   141  
   142  // Denomination is a unit and its conversion factor.
   143  type Denomination struct {
   144  	Unit             string `json:"unit"`
   145  	ConversionFactor uint64 `json:"conversionFactor"`
   146  }
   147  
   148  // UnitInfo conveys information about the units and available denominations for
   149  // an asset.
   150  type UnitInfo struct {
   151  	// AtomicUnit is the name associated with the asset's integral unit of
   152  	// measure, e.g. satoshis, atoms, gwei (for DEX purposes).
   153  	AtomicUnit string `json:"atomicUnit"`
   154  	// Conventional is the conventionally-used denomination.
   155  	Conventional Denomination `json:"conventional"`
   156  	// Alternatives lists additionally available Denominations, and can be
   157  	// empty.
   158  	Alternatives []Denomination `json:"denominations"`
   159  	// FeeRateDenom is the denominator used for fee rates.
   160  	FeeRateDenom string `json:"feeRateDenom"`
   161  }
   162  
   163  // ConventionalString converts the quantity to conventional units, and returns
   164  // the formatted float string.
   165  func (ui *UnitInfo) ConventionalString(v uint64) string {
   166  	c := ui.Conventional.ConversionFactor
   167  	prec := int(math.Round(math.Log10(float64(c)))) // Assumes integer powers of 10
   168  	return strconv.FormatFloat(float64(v)/float64(c), 'f', prec, 64)
   169  }
   170  
   171  // FormatAtoms formats the atomic value as a string with units. The number is
   172  // formatted with full precision. For small values, the value is formatted in
   173  // atomic units, while for larger values the value is formatted in conventional
   174  // units.
   175  func (ui *UnitInfo) FormatAtoms(v uint64) string {
   176  	const bestDisplayOrder = 1
   177  	conv := float64(v) / float64(ui.Conventional.ConversionFactor)
   178  	convDelta := uint(math.Abs(math.Floor(math.Log10(conv)) - bestDisplayOrder))
   179  	atomicDelta := uint(math.Abs(math.Floor(math.Log10(float64(v))) - bestDisplayOrder))
   180  	if convDelta <= atomicDelta {
   181  		prec := int(math.Round(math.Log10(float64(ui.Conventional.ConversionFactor))))
   182  		return fmt.Sprintf("%s %s", strconv.FormatFloat(conv, 'f', prec, 64), ui.Conventional.Unit)
   183  	}
   184  	return fmt.Sprintf("%d %s", v, ui.AtomicUnit)
   185  }
   186  
   187  func (ui *UnitInfo) FormatSignedAtoms(v int64, plusSign ...bool) string {
   188  	if v >= 0 {
   189  		if len(plusSign) > 0 && plusSign[0] {
   190  			return "+" + ui.FormatAtoms(uint64(v))
   191  		}
   192  		return ui.FormatAtoms(uint64(v))
   193  	}
   194  	return "-" + ui.FormatAtoms(uint64(-v))
   195  }
   196  
   197  // Token is a generic representation of a token-type asset.
   198  type Token struct {
   199  	// ParentID is the asset ID of the token's parent asset.
   200  	ParentID uint32 `json:"parentID"`
   201  	// Name is the display name of the token asset.
   202  	Name string `json:"name"`
   203  	// UnitInfo is the UnitInfo for the token.
   204  	UnitInfo UnitInfo `json:"unitInfo"`
   205  }
   206  
   207  // IntDivUp divides two integers, rounding up, without using floating point
   208  // conversion. This will panic if the denominator is zero, just like regular
   209  // integer division. This function should be used instead of ad hoc solutions or
   210  // being lazy with floating point math.
   211  func IntDivUp(val, div int64) int64 {
   212  	// https://github.com/rust-lang/rust/blob/343889b7234bf786e2bc673029467052f22fca08/library/core/src/num/uint_macros.rs#L2061
   213  	q, rem := val/div, val%div
   214  	if (rem > 0 && div > 0) || (rem < 0 && div < 0) {
   215  		q++
   216  	}
   217  	return q
   218  	// return (val + div - 1) / div
   219  }