Skip to content
Permalink
Browse files

Add asset decimals field (#601)

* Add V20.

* add asset decimals

* add asset decimals to api and goal

* display decimal info in goal

* fix goal asset create shell scripts

* don't require decimals flag

* update consensus version url

* print decimals for asset info, use decimals in expect test

* remove decimals from one goal asset test

* set base units -> units if asset params exist

* add missing space
  • Loading branch information
justicz authored and tsachiherman committed Dec 5, 2019
1 parent 4a1e0a7 commit affdd8431f649a70c9c55d0f4e65d2b143d4c8c2
@@ -477,17 +477,20 @@ var listCmd = &cobra.Command{
frozen = ", frozen"
}

unitName := "units"
unitName := "base units"
assetName := ""
assetURL := ""
assetMetadata := ""
creatorInfo, err := client.AccountInformation(bal.Creator)
assetDecimals := uint32(0)
decimalInfo := " (no decimal info) "
if err == nil {
params, ok := creatorInfo.AssetParams[aid]
if ok {
if params.UnitName != "" {
unitName = params.UnitName
}
unitName = "units"
if params.AssetName != "" {
assetName = fmt.Sprintf(", name %s", params.AssetName)
}
@@ -497,10 +500,12 @@ var listCmd = &cobra.Command{
if params.MetadataHash != nil {
assetMetadata = fmt.Sprintf(", metadata %x", params.MetadataHash)
}
assetDecimals = params.Decimals
decimalInfo = ""
}
}

fmt.Printf("\t%20d %-8s (creator %s, ID %d%s%s%s%s)\n", bal.Amount, unitName, bal.Creator, aid, assetName, assetURL, assetMetadata, frozen)
fmt.Printf("\t%20s %-8s%s (creator %s, ID %d%s%s%s%s)\n", assetDecimalsFmt(bal.Amount, assetDecimals), unitName, decimalInfo, bal.Creator, aid, assetName, assetURL, assetMetadata, frozen)
}
}
},
@@ -29,6 +29,7 @@ var (
assetID uint64
assetCreator string
assetTotal uint64
assetDecimals uint32
assetFrozen bool
assetUnitName string
assetMetadataHashBase64 string
@@ -56,6 +57,7 @@ func init() {

createAssetCmd.Flags().StringVar(&assetCreator, "creator", "", "Account address for creating an asset")
createAssetCmd.Flags().Uint64Var(&assetTotal, "total", 0, "Total amount of tokens for created asset")
createAssetCmd.Flags().Uint32Var(&assetDecimals, "decimals", 0, "The number of digits to use after the decimal point when displaying this asset. If set to 0, the asset is not divisible beyond its base unit. If set to 1, the base asset unit is tenths. If 2, the base asset unit is hundredths, and so on.")
createAssetCmd.Flags().BoolVar(&assetFrozen, "defaultfrozen", false, "Freeze or not freeze holdings by default")
createAssetCmd.Flags().StringVar(&assetUnitName, "unitname", "", "Name for the unit of asset")
createAssetCmd.Flags().StringVar(&assetName, "name", "", "Name for the entire asset")
@@ -112,7 +114,7 @@ func init() {
sendAssetCmd.Flags().StringVar(&assetUnitName, "asset", "", "Unit name of the asset being transferred")
sendAssetCmd.Flags().StringVarP(&account, "from", "f", "", "Account address to send the money from (if not specified, uses default account)")
sendAssetCmd.Flags().StringVarP(&toAddress, "to", "t", "", "Address to send to money to (required)")
sendAssetCmd.Flags().Uint64VarP(&amount, "amount", "a", 0, "The amount to be transferred (required), in microAlgos")
sendAssetCmd.Flags().Uint64VarP(&amount, "amount", "a", 0, "The amount to be transferred (required), in base units of the asset.")
sendAssetCmd.Flags().StringVarP(&closeToAddress, "close-to", "c", "", "Close asset account and send remainder to this address")
sendAssetCmd.Flags().Uint64Var(&fee, "fee", 0, "The transaction fee (automatically determined by default), in microAlgos")
sendAssetCmd.Flags().Uint64Var(&firstValid, "firstvalid", 0, "The first round where the transaction may be committed to the ledger")
@@ -222,7 +224,7 @@ var createAssetCmd = &cobra.Command{
}
}

tx, err := client.MakeUnsignedAssetCreateTx(assetTotal, assetFrozen, creator, creator, creator, creator, assetUnitName, assetName, assetURL, assetMetadataHash)
tx, err := client.MakeUnsignedAssetCreateTx(assetTotal, assetFrozen, creator, creator, creator, creator, assetUnitName, assetName, assetURL, assetMetadataHash, assetDecimals)
if err != nil {
reportErrorf("Cannot construct transaction: %s", err)
}
@@ -572,6 +574,20 @@ var freezeAssetCmd = &cobra.Command{
},
}

func assetDecimalsFmt(amount uint64, decimals uint32) string {
// Just return the raw amount with no decimal if decimals is 0
if decimals == 0 {
return fmt.Sprintf("%d", amount)
}

// Otherwise, ensure there are decimals digits to the right of the decimal point
pow := uint64(1)
for i := uint32(0); i < decimals; i++ {
pow *= 10
}
return fmt.Sprintf("%d.%0*d", amount/pow, decimals, amount%pow)
}

var infoAssetCmd = &cobra.Command{
Use: "info",
Short: "Look up current parameters for an asset",
@@ -598,13 +614,16 @@ var infoAssetCmd = &cobra.Command{
reportErrorf(errorRequestFail, err)
}

res := reserve.Assets[assetID]

fmt.Printf("Asset ID: %d\n", assetID)
fmt.Printf("Creator: %s\n", params.Creator)
fmt.Printf("Asset name: %s\n", params.AssetName)
fmt.Printf("Unit name: %s\n", params.UnitName)
fmt.Printf("Maximum issue: %d %s\n", params.Total, params.UnitName)
fmt.Printf("Reserve amount: %d %s\n", reserve.Assets[assetID].Amount, params.UnitName)
fmt.Printf("Issued: %d %s\n", params.Total-reserve.Assets[assetID].Amount, params.UnitName)
fmt.Printf("Maximum issue: %s %s\n", assetDecimalsFmt(params.Total, params.Decimals), params.UnitName)
fmt.Printf("Reserve amount: %s %s\n", assetDecimalsFmt(res.Amount, params.Decimals), params.UnitName)
fmt.Printf("Issued: %s %s\n", assetDecimalsFmt(params.Total-res.Amount, params.Decimals), params.UnitName)
fmt.Printf("Decimals: %d\n", params.Decimals)
fmt.Printf("Default frozen: %v\n", params.DefaultFrozen)
fmt.Printf("Manager address: %s\n", params.ManagerAddr)
fmt.Printf("Reserve address: %s\n", params.ReserveAddr)
@@ -221,6 +221,9 @@ type ConsensusParams struct {

// sum of estimated op cost must be less than this
LogicSigMaxCost uint64

// max decimal precision for assets
MaxAssetDecimals uint32
}

// Consensus tracks the protocol-level settings for different versions of the
@@ -445,9 +448,24 @@ func initConsensusProtocols() {
// v17 can be upgraded to v19.
v17.ApprovedUpgrades[protocol.ConsensusV19] = true

// v20 points to adding the precision to the assets.
v20 := v19
v20.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{}
v20.MaxAssetDecimals = 19
// we want to adjust the upgrade time to be roughly one week.
// one week, in term of rounds would be:
// 140651 = (7 * 24 * 60 * 60 / 4.3)
// for the sake of future manual calculations, we'll round that down
// a bit :
v20.UpgradeWaitRounds = 140000
Consensus[protocol.ConsensusV20] = v20

// v19 can be upgraded to v20.
v19.ApprovedUpgrades[protocol.ConsensusV20] = true

// ConsensusFuture is used to test features that are implemented
// but not yet released in a production protocol version.
vFuture := v19
vFuture := v20
vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{}
Consensus[protocol.ConsensusFuture] = vFuture
}
@@ -494,7 +512,7 @@ func initConsensusTestProtocols() {
Consensus[protocol.ConsensusTestRapidRewardRecalculation] = rapidRecalcParams

// Setting the testShorterLookback parameters derived from ConsensusCurrentVersion
// Will result in MaxBalLookback = 32
// Will result in MaxBalLookback = 32
// Used to run tests faster where past MaxBalLookback values are checked
testShorterLookback := Consensus[protocol.ConsensusCurrentVersion]
testShorterLookback.ApprovedUpgrades = map[protocol.ConsensusVersion]bool{}
@@ -503,7 +521,7 @@ func initConsensusTestProtocols() {
// ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md
testShorterLookback.SeedLookback = 2
testShorterLookback.SeedRefreshInterval = 8
testShorterLookback.MaxBalLookback = 2*testShorterLookback.SeedLookback*testShorterLookback.SeedRefreshInterval // 32
testShorterLookback.MaxBalLookback = 2 * testShorterLookback.SeedLookback * testShorterLookback.SeedRefreshInterval // 32
Consensus[protocol.ConsensusTestShorterLookback] = testShorterLookback
}

@@ -159,6 +159,7 @@ func assetParams(creator basics.Address, params basics.AssetParams) v1.AssetPara
paramsModel := v1.AssetParams{
Total: params.Total,
DefaultFrozen: params.DefaultFrozen,
Decimals: params.Decimals,
}

paramsModel.UnitName = strings.TrimRight(string(params.UnitName[:]), "\x00")
@@ -192,6 +192,14 @@ type AssetParams struct {
// required: true
Total uint64 `json:"total"`

// Decimals specifies the number of digits to use after the decimal
// point when displaying this asset. If 0, the asset is not divisible.
// If 1, the base unit of the asset is in tenths. If 2, the base unit
// of the asset is in hundredths, and so on.
//
// required: true
Decimals uint32 `json:"decimals"`

// DefaultFrozen specifies whether holdings in this asset
// are frozen by default.
//
@@ -186,6 +186,13 @@ type AssetParams struct {
// created.
Total uint64 `codec:"t"`

// Decimals specifies the number of digits to display after the decimal
// place when displaying this asset. A value of 0 represents an asset
// that is not divisible, a value of 1 represents an asset divisible
// into tenths, and so on. This value must be between 0 and 19
// (inclusive).
Decimals uint32 `codec:"dc"`

// DefaultFrozen specifies whether slots for this asset
// in user accounts are frozen by default or not.
DefaultFrozen bool `codec:"df"`
@@ -346,6 +346,9 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
if len(tx.AssetConfigTxnFields.AssetParams.URL) > proto.MaxAssetURLBytes {
return fmt.Errorf("transaction asset url too big: %d > %d", len(tx.AssetConfigTxnFields.AssetParams.URL), proto.MaxAssetURLBytes)
}
if tx.AssetConfigTxnFields.AssetParams.Decimals > proto.MaxAssetDecimals {
return fmt.Errorf("transaction asset decimals is too high (max is %d)", proto.MaxAssetDecimals)
}
if tx.Sender == spec.RewardsPool {
// this check is just to be safe, but reaching here seems impossible, since it requires computing a preimage of rwpool
return fmt.Errorf("transaction from incentive pool is invalid")
@@ -378,7 +378,7 @@ func (c *Client) FillUnsignedTxTemplate(sender string, firstValid, lastValid, fe
//
// Call FillUnsignedTxTemplate afterwards to fill out common fields in
// the resulting transaction template.
func (c *Client) MakeUnsignedAssetCreateTx(total uint64, defaultFrozen bool, manager string, reserve string, freeze string, clawback string, unitName string, assetName string, url string, metadataHash []byte) (transactions.Transaction, error) {
func (c *Client) MakeUnsignedAssetCreateTx(total uint64, defaultFrozen bool, manager string, reserve string, freeze string, clawback string, unitName string, assetName string, url string, metadataHash []byte, decimals uint32) (transactions.Transaction, error) {
var tx transactions.Transaction
var err error

@@ -447,6 +447,11 @@ func (c *Client) MakeUnsignedAssetCreateTx(total uint64, defaultFrozen bool, man
}
tx.AssetParams.AssetName = assetName

if decimals > cparams.MaxAssetDecimals {
return tx, fmt.Errorf("asset decimal precision too high (max %d)", cparams.MaxAssetDecimals)
}
tx.AssetParams.Decimals = decimals

return tx, nil
}

@@ -108,6 +108,11 @@ const ConsensusV19 = ConsensusVersion(
"https://github.com/algorandfoundation/specs/tree/0e196e82bfd6e327994bec373c4cc81bc878ef5c",
)

// ConsensusV20 points to adding the decimals field to assets
const ConsensusV20 = ConsensusVersion(
"https://github.com/algorandfoundation/specs/tree/4a9db6a25595c6fd097cf9cc137cc83027787eaa",
)

// ConsensusFuture is a protocol that should not appear in any production
// network, but is used to test features before they are released.
const ConsensusFuture = ConsensusVersion(
@@ -120,7 +125,7 @@ const ConsensusFuture = ConsensusVersion(

// ConsensusCurrentVersion is the latest version and should be used
// when a specific version is not provided.
const ConsensusCurrentVersion = ConsensusV19
const ConsensusCurrentVersion = ConsensusV20

// ConsensusTest0 is a version of ConsensusV0 used for testing
// (it has different approved upgrade paths).
@@ -128,7 +128,7 @@ func prepareAssets(accounts map[string]uint64, client libgoal.Client, cfg PpConf
fmt.Printf("Too many NumPartAccounts")
return
}
tx, createErr := client.MakeUnsignedAssetCreateTx(totalSupply, false, cfg.SrcAccount, cfg.SrcAccount, cfg.SrcAccount, cfg.SrcAccount, "ping", "pong", "", meta)
tx, createErr := client.MakeUnsignedAssetCreateTx(totalSupply, false, cfg.SrcAccount, cfg.SrcAccount, cfg.SrcAccount, cfg.SrcAccount, "ping", "pong", "", meta, 0)
if createErr != nil {
fmt.Printf("Cannot make asset create txn\n")
err = createErr
@@ -22,8 +22,8 @@ proc TestGoalCommandLineFlags { CMD EXPECTED_RE } {
if { [catch {
puts "Part 1: Check --validrounds and --lastvalid options combination for 'goal asset' and 'goal account"

TestGoalCommandLineFlags "goal asset create --validrounds 0 --creator ABC --total 100" ".*can not be zero.*"
TestGoalCommandLineFlags "goal asset create --validrounds 1 --lastvalid 1 --creator ABC --total 100" "Only one of .* can be specified"
TestGoalCommandLineFlags "goal asset create --decimals 0 --validrounds 0 --creator ABC --total 100" ".*can not be zero.*"
TestGoalCommandLineFlags "goal asset create --decimals 0 --validrounds 1 --lastvalid 1 --creator ABC --total 100" "Only one of .* can be specified"

TestGoalCommandLineFlags "goal account changeonlinestatus --validRounds 0 --online" ".*validRounds has been deprecated.*"
TestGoalCommandLineFlags "goal account changeonlinestatus --firstRound 0 --online" ".*firstRound has been deprecated.*"
@@ -58,13 +58,13 @@ if { [catch {
set TX_FILE $TEST_ROOT_DIR/tx-create-$FILE_COUNTER.tx
incr FILE_COUNTER
set LV_EXPECTED 2
TestLastValidInTx "goal asset create --validrounds 1 --firstvalid 2 --creator $PRIMARY_ACCOUNT_ADDRESS --total 100 -d $TEST_PRIMARY_NODE_DIR -o $TX_FILE" $TX_FILE $LV_EXPECTED
TestLastValidInTx "goal asset create --decimals 7 --validrounds 1 --firstvalid 2 --creator $PRIMARY_ACCOUNT_ADDRESS --total 100 -d $TEST_PRIMARY_NODE_DIR -o $TX_FILE" $TX_FILE $LV_EXPECTED

puts "Verify asset create with lastvalid=2 firstvalid=2"
set TX_FILE $TEST_ROOT_DIR/tx-create-$FILE_COUNTER.tx
incr FILE_COUNTER
set LV_EXPECTED 2
TestLastValidInTx "goal asset create --lastvalid 2 --firstvalid 2 --creator $PRIMARY_ACCOUNT_ADDRESS --total 100 -d $TEST_PRIMARY_NODE_DIR -o $TX_FILE" $TX_FILE $LV_EXPECTED
TestLastValidInTx "goal asset create --decimals 0 --lastvalid 2 --firstvalid 2 --creator $PRIMARY_ACCOUNT_ADDRESS --total 100 -d $TEST_PRIMARY_NODE_DIR -o $TX_FILE" $TX_FILE $LV_EXPECTED

puts "Verify asset create with validrounds=1000"
set TX_FILE $TEST_ROOT_DIR/tx-create-$FILE_COUNTER.tx

0 comments on commit affdd84

Please sign in to comment.
You can’t perform that action at this time.