vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.9k stars 801 forks source link

VIP: allow generic decimal types #1807

Open charles-cooper opened 4 years ago

charles-cooper commented 4 years ago

Simple Summary

Allow generic decimal types with parametrizable precision and bits settings.

Motivation

Fixed point math is generally useful for smart contracts as one of the most common use cases for smart contracts is to do accurate accounting. This is manifest in the fact that numerous ERCs and applications depend on fixed point math (ex. ERC20, various AMMs, ERC4626). This VIP proposes general decimal types which are

Specification

Introduce new PEP484 style Decimals and UDecimals types which each accept two parameters (the latter of which is optional).

Examples:

The existing decimals type will be considered a deprecated alias of Decimals[10, 168].

In general, the ABI type of Decimals[N, B] will be int_<B>. The ABI type of UDecimals[N, B] will be uint_<B>.

Backwards Compatibility

Dependencies

Requires some refactoring of the internal type system (probably will be done as work for #2431).

References

https://github.com/vyperlang/vyper/issues/3039 Could be useful for use-site tuning of ERC20 or other token libraries.

Copyright

Copyright and related rights waived via CC0

fubuloubu commented 4 years ago

I don't think that you should have to specify the amount of bits the type takes up. We can calculate that.

fubuloubu commented 4 years ago

It would be really nice if we have the ability to create custom types (or type aliases) such that we could allow typing to be used by end users more directly, e.g. ray := decimal(places=27) for Maker codebase.

fubuloubu commented 4 years ago

Note: 38 decimal places is the most possible that can fit in 128 bits (corresponding to fixed255x38)

fubuloubu commented 4 years ago

List of decimals:

  1. fixed132x1
  2. fixed135x2
  3. fixed138x3
  4. fixed142x4
  5. fixed145x5
  6. fixed148x6
  7. fixed152x7
  8. fixed155x8
  9. fixed158x9
  10. fixed162x10
  11. fixed165x11
  12. fixed168x12
  13. fixed172x13
  14. fixed175x14
  15. fixed178x15
  16. fixed182x16
  17. fixed185x17
  18. fixed188x18
  19. fixed192x19
  20. fixed195x20
  21. fixed198x21
  22. fixed202x22
  23. fixed205x23
  24. fixed208x24
  25. fixed212x25
  26. fixed215x26
  27. fixed218x27
  28. fixed222x28
  29. fixed225x29
  30. fixed228x30
  31. fixed231x31 (NOTE: doesn't fit pattern)
  32. fixed235x32
  33. fixed238x33
  34. fixed241x34 (NOTE: doesn't fit pattern)
  35. fixed245x35
  36. fixed248x36
  37. fixed251x37 (NOTE: doesn't fit pattern)
  38. fixed255x38
fubuloubu commented 4 years ago

Meeting notes: Pamp it

fubuloubu commented 4 years ago

Was thinking about this some more today. It could be something like the following:

  1. Internally, Vyper users only see the Decimal type, which is a fixed point type that is allowed to be any width decimal (using fixed<M>x<N> notation) as long as the calculations align at "endpoints" (internal type conversions or ABI boundaries)
  2. For interface definitions, Decimal takes an argument (e.g. Decimal(fixed168x10)) which defines the "required" shape of the Decimal type at that interface and pins that boundary to that particular ABI type
  3. When producing ABIs, Vyper statically determines the ABI type of Decimal and uses fixed<M>x<N> notation to denote that interface type. This can be overriden using the same trick for interface definitions e.g. def myCall(val: Decimal(fixed168x10)) to pin to a particular type (useful for ERCs)
  4. All else is "undefined behavior", allowing the compiler to optimize towards the highest precision and lowest loss calculations based on all the mathematical interactions that are possible within an entire contract, given the constraints at the boundary points. This optimization should be statically determinable, so that it will always produce the same outcome given the same program (although different programs and constraint system can produce different results)

This design would be strictly better than doing uint256 math all over, with implicit assumptions about precision loss, and occasionally watching contract call failures from using SafeMath without due consideration to these scenarios. The compiler can also produce suggestions for the min/max size particular values can be at conversion boundary points in order to ensure correct operation of the algorithm.

charles-cooper commented 2 years ago

meeting notes: add an example, bring up for discussion again