github.com/deso-protocol/core@v1.2.9/lib/block_view_creator_coin.go (about) 1 package lib 2 3 import ( 4 "fmt" 5 "github.com/btcsuite/btcd/btcec" 6 "github.com/golang/glog" 7 "github.com/pkg/errors" 8 "math" 9 "math/big" 10 "reflect" 11 ) 12 13 func (bav *UtxoView) _getBalanceEntryForHODLerPKIDAndCreatorPKID( 14 hodlerPKID *PKID, creatorPKID *PKID) *BalanceEntry { 15 16 // If an entry exists in the in-memory map, return the value of that mapping. 17 balanceEntryKey := MakeCreatorCoinBalanceKey(hodlerPKID, creatorPKID) 18 mapValue, existsMapValue := bav.HODLerPKIDCreatorPKIDToBalanceEntry[balanceEntryKey] 19 if existsMapValue { 20 return mapValue 21 } 22 23 // If we get here it means no value exists in our in-memory map. In this case, 24 // defer to the db. If a mapping exists in the db, return it. If not, return 25 // nil. 26 var balanceEntry *BalanceEntry 27 if bav.Postgres != nil { 28 balance := bav.Postgres.GetCreatorCoinBalance(hodlerPKID, creatorPKID) 29 if balance != nil { 30 balanceEntry = &BalanceEntry{ 31 HODLerPKID: balance.HolderPKID, 32 CreatorPKID: balance.CreatorPKID, 33 BalanceNanos: balance.BalanceNanos, 34 HasPurchased: balance.HasPurchased, 35 } 36 } 37 } else { 38 balanceEntry = DBGetCreatorCoinBalanceEntryForHODLerAndCreatorPKIDs(bav.Handle, hodlerPKID, creatorPKID) 39 } 40 if balanceEntry != nil { 41 bav._setBalanceEntryMappingsWithPKIDs(balanceEntry, hodlerPKID, creatorPKID) 42 } 43 return balanceEntry 44 } 45 46 func (bav *UtxoView) GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 47 hodlerPubKey []byte, creatorPubKey []byte) ( 48 _balanceEntry *BalanceEntry, _hodlerPKID *PKID, _creatorPKID *PKID) { 49 50 // These are guaranteed to be non-nil as long as the public keys are valid. 51 hodlerPKID := bav.GetPKIDForPublicKey(hodlerPubKey) 52 creatorPKID := bav.GetPKIDForPublicKey(creatorPubKey) 53 54 return bav._getBalanceEntryForHODLerPKIDAndCreatorPKID(hodlerPKID.PKID, creatorPKID.PKID), hodlerPKID.PKID, creatorPKID.PKID 55 } 56 57 func (bav *UtxoView) _setBalanceEntryMappingsWithPKIDs( 58 balanceEntry *BalanceEntry, hodlerPKID *PKID, creatorPKID *PKID) { 59 60 // This function shouldn't be called with nil. 61 if balanceEntry == nil { 62 glog.Errorf("_setBalanceEntryMappings: Called with nil BalanceEntry; " + 63 "this should never happen.") 64 return 65 } 66 67 // Add a mapping for the BalancEntry. 68 balanceEntryKey := MakeCreatorCoinBalanceKey(hodlerPKID, creatorPKID) 69 bav.HODLerPKIDCreatorPKIDToBalanceEntry[balanceEntryKey] = balanceEntry 70 } 71 72 func (bav *UtxoView) _setBalanceEntryMappings( 73 balanceEntry *BalanceEntry) { 74 75 bav._setBalanceEntryMappingsWithPKIDs( 76 balanceEntry, balanceEntry.HODLerPKID, balanceEntry.CreatorPKID) 77 } 78 79 func (bav *UtxoView) _deleteBalanceEntryMappingsWithPKIDs( 80 balanceEntry *BalanceEntry, hodlerPKID *PKID, creatorPKID *PKID) { 81 82 // Create a tombstone entry. 83 tombstoneBalanceEntry := *balanceEntry 84 tombstoneBalanceEntry.isDeleted = true 85 86 // Set the mappings to point to the tombstone entry. 87 bav._setBalanceEntryMappingsWithPKIDs(&tombstoneBalanceEntry, hodlerPKID, creatorPKID) 88 } 89 90 func (bav *UtxoView) _deleteBalanceEntryMappings( 91 balanceEntry *BalanceEntry, hodlerPublicKey []byte, creatorPublicKey []byte) { 92 93 // These are guaranteed to be non-nil as long as the public keys are valid. 94 hodlerPKID := bav.GetPKIDForPublicKey(hodlerPublicKey) 95 creatorPKID := bav.GetPKIDForPublicKey(creatorPublicKey) 96 97 // Set the mappings to point to the tombstone entry. 98 bav._deleteBalanceEntryMappingsWithPKIDs(balanceEntry, hodlerPKID.PKID, creatorPKID.PKID) 99 } 100 101 func (bav *UtxoView) GetHoldings(pkid *PKID, fetchProfiles bool) ([]*BalanceEntry, []*ProfileEntry, error) { 102 var entriesYouHold []*BalanceEntry 103 if bav.Postgres != nil { 104 balances := bav.Postgres.GetHoldings(pkid) 105 for _, balance := range balances { 106 entriesYouHold = append(entriesYouHold, balance.NewBalanceEntry()) 107 } 108 } else { 109 holdings, err := DbGetBalanceEntriesYouHold(bav.Handle, pkid, true) 110 if err != nil { 111 return nil, nil, err 112 } 113 entriesYouHold = holdings 114 } 115 116 holdingsMap := make(map[PKID]*BalanceEntry) 117 for _, balanceEntry := range entriesYouHold { 118 holdingsMap[*balanceEntry.CreatorPKID] = balanceEntry 119 } 120 121 for _, balanceEntry := range bav.HODLerPKIDCreatorPKIDToBalanceEntry { 122 if reflect.DeepEqual(balanceEntry.HODLerPKID, pkid) { 123 if _, ok := holdingsMap[*balanceEntry.CreatorPKID]; ok { 124 // We found both a mempool and a db balanceEntry. Update the BalanceEntry using mempool data. 125 holdingsMap[*balanceEntry.CreatorPKID].BalanceNanos = balanceEntry.BalanceNanos 126 } else { 127 // Add new entries to the list 128 entriesYouHold = append(entriesYouHold, balanceEntry) 129 } 130 } 131 } 132 133 // Optionally fetch all the profile entries as well. 134 var profilesYouHold []*ProfileEntry 135 if fetchProfiles { 136 for _, balanceEntry := range entriesYouHold { 137 // In this case you're the hodler so the creator is the one whose profile we need to fetch. 138 currentProfileEntry := bav.GetProfileEntryForPKID(balanceEntry.CreatorPKID) 139 profilesYouHold = append(profilesYouHold, currentProfileEntry) 140 } 141 } 142 143 return entriesYouHold, profilesYouHold, nil 144 } 145 146 func (bav *UtxoView) GetHolders(pkid *PKID, fetchProfiles bool) ([]*BalanceEntry, []*ProfileEntry, error) { 147 var holderEntries []*BalanceEntry 148 if bav.Postgres != nil { 149 balances := bav.Postgres.GetHolders(pkid) 150 for _, balance := range balances { 151 holderEntries = append(holderEntries, balance.NewBalanceEntry()) 152 } 153 } else { 154 holders, err := DbGetBalanceEntriesHodlingYou(bav.Handle, pkid, true) 155 if err != nil { 156 return nil, nil, err 157 } 158 holderEntries = holders 159 } 160 161 holdersMap := make(map[PKID]*BalanceEntry) 162 for _, balanceEntry := range holderEntries { 163 holdersMap[*balanceEntry.HODLerPKID] = balanceEntry 164 } 165 166 for _, balanceEntry := range bav.HODLerPKIDCreatorPKIDToBalanceEntry { 167 if reflect.DeepEqual(balanceEntry.HODLerPKID, pkid) { 168 if _, ok := holdersMap[*balanceEntry.HODLerPKID]; ok { 169 // We found both a mempool and a db balanceEntry. Update the BalanceEntry using mempool data. 170 holdersMap[*balanceEntry.HODLerPKID].BalanceNanos = balanceEntry.BalanceNanos 171 } else { 172 // Add new entries to the list 173 holderEntries = append(holderEntries, balanceEntry) 174 } 175 } 176 } 177 178 // Optionally fetch all the profile entries as well. 179 var profilesYouHold []*ProfileEntry 180 if fetchProfiles { 181 for _, balanceEntry := range holderEntries { 182 // In this case you're the hodler so the creator is the one whose profile we need to fetch. 183 currentProfileEntry := bav.GetProfileEntryForPKID(balanceEntry.CreatorPKID) 184 profilesYouHold = append(profilesYouHold, currentProfileEntry) 185 } 186 } 187 188 return holderEntries, profilesYouHold, nil 189 } 190 191 func CalculateCreatorCoinToMintPolynomial( 192 deltaDeSoNanos uint64, currentCreatorCoinSupplyNanos uint64, params *DeSoParams) uint64 { 193 // The values our equations take are generally in whole units rather than 194 // nanos, so the first step is to convert the nano amounts into floats 195 // representing full coin units. 196 bigNanosPerUnit := NewFloat().SetUint64(NanosPerUnit) 197 bigDeltaDeSo := Div(NewFloat().SetUint64(deltaDeSoNanos), bigNanosPerUnit) 198 bigCurrentCreatorCoinSupply := 199 Div(NewFloat().SetUint64(currentCreatorCoinSupplyNanos), bigNanosPerUnit) 200 201 // These calculations are basically what you get when you integrate a 202 // polynomial price curve. For more information, see the comment on 203 // CreatorCoinSlope in constants.go and check out the Mathematica notebook 204 // linked in that comment. 205 // 206 // This is the formula: 207 // - (((dB + m*RR*s^(1/RR))/(m*RR)))^RR-s 208 // - where: 209 // dB = bigDeltaDeSo, 210 // m = params.CreatorCoinSlope 211 // RR = params.CreatorCoinReserveRatio 212 // s = bigCurrentCreatorCoinSupply 213 // 214 // If you think it's hard to understand the code below, don't worry-- I hate 215 // the Go float libary syntax too... 216 bigRet := Sub(BigFloatPow((Div((Add(bigDeltaDeSo, 217 Mul(params.CreatorCoinSlope, Mul(params.CreatorCoinReserveRatio, 218 BigFloatPow(bigCurrentCreatorCoinSupply, (Div(bigOne, 219 params.CreatorCoinReserveRatio))))))), Mul(params.CreatorCoinSlope, 220 params.CreatorCoinReserveRatio))), params.CreatorCoinReserveRatio), 221 bigCurrentCreatorCoinSupply) 222 // The value we get is generally a number of whole creator coins, and so we 223 // need to convert it to "nanos" as a last step. 224 retNanos, _ := Mul(bigRet, bigNanosPerUnit).Uint64() 225 return retNanos 226 } 227 228 func CalculateCreatorCoinToMintBancor( 229 deltaDeSoNanos uint64, currentCreatorCoinSupplyNanos uint64, 230 currentDeSoLockedNanos uint64, params *DeSoParams) uint64 { 231 // The values our equations take are generally in whole units rather than 232 // nanos, so the first step is to convert the nano amounts into floats 233 // representing full coin units. 234 bigNanosPerUnit := NewFloat().SetUint64(NanosPerUnit) 235 bigDeltaDeSo := Div(NewFloat().SetUint64(deltaDeSoNanos), bigNanosPerUnit) 236 bigCurrentCreatorCoinSupply := Div(NewFloat().SetUint64(currentCreatorCoinSupplyNanos), bigNanosPerUnit) 237 bigCurrentDeSoLocked := Div(NewFloat().SetUint64(currentDeSoLockedNanos), bigNanosPerUnit) 238 239 // These calculations are derived from the Bancor pricing formula, which 240 // is proportional to a polynomial price curve (and equivalent to Uniswap 241 // under certain assumptions). For more information, see the comment on 242 // CreatorCoinSlope in constants.go and check out the Mathematica notebook 243 // linked in that comment. 244 // 245 // This is the formula: 246 // - S0 * ((1 + dB / B0) ^ (RR) - 1) 247 // - where: 248 // dB = bigDeltaDeSo, 249 // B0 = bigCurrentDeSoLocked 250 // S0 = bigCurrentCreatorCoinSupply 251 // RR = params.CreatorCoinReserveRatio 252 // 253 // Sorry the code for the equation is so hard to read. 254 bigRet := Mul(bigCurrentCreatorCoinSupply, 255 Sub(BigFloatPow((Add(bigOne, Div(bigDeltaDeSo, 256 bigCurrentDeSoLocked))), 257 (params.CreatorCoinReserveRatio)), bigOne)) 258 // The value we get is generally a number of whole creator coins, and so we 259 // need to convert it to "nanos" as a last step. 260 retNanos, _ := Mul(bigRet, bigNanosPerUnit).Uint64() 261 return retNanos 262 } 263 264 func CalculateDeSoToReturn( 265 deltaCreatorCoinNanos uint64, currentCreatorCoinSupplyNanos uint64, 266 currentDeSoLockedNanos uint64, params *DeSoParams) uint64 { 267 // The values our equations take are generally in whole units rather than 268 // nanos, so the first step is to convert the nano amounts into floats 269 // representing full coin units. 270 bigNanosPerUnit := NewFloat().SetUint64(NanosPerUnit) 271 bigDeltaCreatorCoin := Div(NewFloat().SetUint64(deltaCreatorCoinNanos), bigNanosPerUnit) 272 bigCurrentCreatorCoinSupply := Div(NewFloat().SetUint64(currentCreatorCoinSupplyNanos), bigNanosPerUnit) 273 bigCurrentDeSoLocked := Div(NewFloat().SetUint64(currentDeSoLockedNanos), bigNanosPerUnit) 274 275 // These calculations are derived from the Bancor pricing formula, which 276 // is proportional to a polynomial price curve (and equivalent to Uniswap 277 // under certain assumptions). For more information, see the comment on 278 // CreatorCoinSlope in constants.go and check out the Mathematica notebook 279 // linked in that comment. 280 // 281 // This is the formula: 282 // - B0 * (1 - (1 - dS / S0)^(1/RR)) 283 // - where: 284 // dS = bigDeltaCreatorCoin, 285 // B0 = bigCurrentDeSoLocked 286 // S0 = bigCurrentCreatorCoinSupply 287 // RR = params.CreatorCoinReserveRatio 288 // 289 // Sorry the code for the equation is so hard to read. 290 bigRet := Mul(bigCurrentDeSoLocked, (Sub(bigOne, BigFloatPow((Sub(bigOne, 291 Div(bigDeltaCreatorCoin, bigCurrentCreatorCoinSupply))), (Div(bigOne, 292 params.CreatorCoinReserveRatio)))))) 293 // The value we get is generally a number of whole creator coins, and so we 294 // need to convert it to "nanos" as a last step. 295 retNanos, _ := Mul(bigRet, bigNanosPerUnit).Uint64() 296 return retNanos 297 } 298 299 func CalculateCreatorCoinToMint( 300 desoToSellNanos uint64, 301 coinsInCirculationNanos uint64, desoLockedNanos uint64, 302 params *DeSoParams) uint64 { 303 304 if desoLockedNanos == 0 { 305 // In this case, there is no DeSo in the profile so we have to use 306 // the polynomial equations to initialize the coin and determine how 307 // much to mint. 308 return CalculateCreatorCoinToMintPolynomial( 309 desoToSellNanos, coinsInCirculationNanos, 310 params) 311 } 312 313 // In this case, we have DeSo locked in the profile and so we use the 314 // standard Bancor equations to determine how much creator coin to mint. 315 return CalculateCreatorCoinToMintBancor( 316 desoToSellNanos, coinsInCirculationNanos, 317 desoLockedNanos, params) 318 } 319 320 func (bav *UtxoView) ValidateDiamondsAndGetNumCreatorCoinNanos( 321 senderPublicKey []byte, 322 receiverPublicKey []byte, 323 diamondPostHash *BlockHash, 324 diamondLevel int64, 325 blockHeight uint32, 326 ) (_numCreatorCoinNanos uint64, _netNewDiamonds int64, _err error) { 327 328 // Check that the diamond level is reasonable 329 diamondLevelMap := GetDeSoNanosDiamondLevelMapAtBlockHeight(int64(blockHeight)) 330 if _, isAllowedLevel := diamondLevelMap[diamondLevel]; !isAllowedLevel { 331 return 0, 0, fmt.Errorf( 332 "ValidateDiamondsAndGetNumCreatorCoinNanos: Diamond level %v not allowed", 333 diamondLevel) 334 } 335 336 // Convert pub keys into PKIDs. 337 senderPKID := bav.GetPKIDForPublicKey(senderPublicKey) 338 receiverPKID := bav.GetPKIDForPublicKey(receiverPublicKey) 339 340 // Look up if there is an existing diamond entry. 341 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 342 diamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 343 344 // Look up if there's an existing profile entry for the sender. There needs 345 // to be in order to be able to give one's creator coin as a diamond. 346 existingProfileEntry := bav.GetProfileEntryForPKID(senderPKID.PKID) 347 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 348 return 0, 0, fmt.Errorf( 349 "ValidateDiamondsAndGetNumCreatorCoinNanos: Cannot send CreatorCoin "+ 350 "with diamond because ProfileEntry for public key %v does not exist", 351 senderPublicKey) 352 } 353 // If we get here, then we're sure the ProfileEntry for this user exists. 354 355 currDiamondLevel := int64(0) 356 if diamondEntry != nil { 357 currDiamondLevel = diamondEntry.DiamondLevel 358 } 359 360 if currDiamondLevel >= diamondLevel { 361 return 0, 0, RuleErrorCreatorCoinTransferPostAlreadyHasSufficientDiamonds 362 } 363 364 // Calculate the number of creator coin nanos needed vs. already added for previous diamonds. 365 currCreatorCoinNanos := GetCreatorCoinNanosForDiamondLevelAtBlockHeight( 366 existingProfileEntry.CoinsInCirculationNanos, existingProfileEntry.DeSoLockedNanos, 367 currDiamondLevel, int64(blockHeight), bav.Params) 368 neededCreatorCoinNanos := GetCreatorCoinNanosForDiamondLevelAtBlockHeight( 369 existingProfileEntry.CoinsInCirculationNanos, existingProfileEntry.DeSoLockedNanos, 370 diamondLevel, int64(blockHeight), bav.Params) 371 372 // There is an edge case where, if the person's creator coin value goes down 373 // by a large enough amount, then they can get a "free" diamond upgrade. This 374 // seems fine for now. 375 creatorCoinToTransferNanos := uint64(0) 376 if neededCreatorCoinNanos > currCreatorCoinNanos { 377 creatorCoinToTransferNanos = neededCreatorCoinNanos - currCreatorCoinNanos 378 } 379 380 netNewDiamonds := diamondLevel - currDiamondLevel 381 382 return creatorCoinToTransferNanos, netNewDiamonds, nil 383 } 384 385 func (bav *UtxoView) _disconnectCreatorCoin( 386 operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash, 387 utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error { 388 389 // Verify that the last operation is a CreatorCoin opration 390 if len(utxoOpsForTxn) == 0 { 391 return fmt.Errorf("_disconnectCreatorCoin: utxoOperations are missing") 392 } 393 operationIndex := len(utxoOpsForTxn) - 1 394 if utxoOpsForTxn[operationIndex].Type != OperationTypeCreatorCoin { 395 return fmt.Errorf("_disconnectCreatorCoin: Trying to revert "+ 396 "OperationTypeCreatorCoin but found type %v", 397 utxoOpsForTxn[operationIndex].Type) 398 } 399 txMeta := currentTxn.TxnMeta.(*CreatorCoinMetadataa) 400 operationData := utxoOpsForTxn[operationIndex] 401 operationIndex-- 402 403 // We sometimes have some extra AddUtxo operations we need to remove 404 // These are "implicit" outputs that always occur at the end of the 405 // list of UtxoOperations. The number of implicit outputs is equal to 406 // the total number of "Add" operations minus the explicit outputs. 407 numUtxoAdds := 0 408 for _, utxoOp := range utxoOpsForTxn { 409 if utxoOp.Type == OperationTypeAddUtxo { 410 numUtxoAdds += 1 411 } 412 } 413 operationIndex -= numUtxoAdds - len(currentTxn.TxOutputs) 414 415 // Get the profile corresponding to the creator coin txn. 416 existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.ProfilePublicKey) 417 // Sanity-check that it exists. 418 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 419 return fmt.Errorf("_disconnectCreatorCoin: CreatorCoin profile for "+ 420 "public key %v doesn't exist; this should never happen", 421 PkToStringBoth(txMeta.ProfilePublicKey)) 422 } 423 // Get the BalanceEntry of the transactor. This should always exist. 424 transactorBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 425 currentTxn.PublicKey, txMeta.ProfilePublicKey) 426 // Sanity-check that the transactor BalanceEntry exists 427 if transactorBalanceEntry == nil || transactorBalanceEntry.isDeleted { 428 return fmt.Errorf("_disconnectCreatorCoin: Transactor BalanceEntry "+ 429 "pubkey %v and creator pubkey %v does not exist; this should "+ 430 "never happen", 431 PkToStringBoth(currentTxn.PublicKey), PkToStringBoth(txMeta.ProfilePublicKey)) 432 } 433 434 // Get the BalanceEntry of the creator. It could be nil if this is a sell 435 // transaction or if the balance entry was deleted by a creator coin transfer. 436 creatorBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 437 txMeta.ProfilePublicKey, txMeta.ProfilePublicKey) 438 if creatorBalanceEntry == nil || creatorBalanceEntry.isDeleted { 439 creatorPKID := bav.GetPKIDForPublicKey(txMeta.ProfilePublicKey) 440 creatorBalanceEntry = &BalanceEntry{ 441 HODLerPKID: creatorPKID.PKID, 442 CreatorPKID: creatorPKID.PKID, 443 BalanceNanos: uint64(0), 444 } 445 } 446 447 if txMeta.OperationType == CreatorCoinOperationTypeBuy { 448 // Set up some variables so that we can run some sanity-checks 449 deltaBuyerNanos := transactorBalanceEntry.BalanceNanos - operationData.PrevTransactorBalanceEntry.BalanceNanos 450 deltaCreatorNanos := creatorBalanceEntry.BalanceNanos - operationData.PrevCreatorBalanceEntry.BalanceNanos 451 deltaCoinsInCirculation := existingProfileEntry.CoinsInCirculationNanos - operationData.PrevCoinEntry.CoinsInCirculationNanos 452 453 // If the creator is distinct from the buyer, then reset their balance. 454 // This check avoids double-updating in situations where a creator bought 455 // their own coin. 456 if !reflect.DeepEqual(currentTxn.PublicKey, txMeta.ProfilePublicKey) { 457 458 // Sanity-check that the amount that we increased the CoinsInCirculation by 459 // equals the total amount received by the buyer and the creator. 460 if deltaBuyerNanos+deltaCreatorNanos != deltaCoinsInCirculation { 461 return fmt.Errorf("_disconnectCreatorCoin: The creator coin nanos "+ 462 "the buyer and the creator received (%v, %v) does not equal the "+ 463 "creator coins added to the circulating supply %v", 464 deltaBuyerNanos, deltaCreatorNanos, deltaCoinsInCirculation) 465 } 466 467 // Sanity-check that the watermark delta equates to what the creator received. 468 deltaNanos := uint64(0) 469 if blockHeight > DeSoFounderRewardBlockHeight { 470 // Do nothing. After the DeSoFounderRewardBlockHeight, creator coins are not 471 // minted as a founder's reward, just DeSo (see utxo reverted later). 472 } else if blockHeight > SalomonFixBlockHeight { 473 // Following the SalomonFixBlockHeight block, we calculate a founders reward 474 // on every buy, not just the ones that push a creator to a new all time high. 475 deltaNanos = existingProfileEntry.CoinsInCirculationNanos - operationData.PrevCoinEntry.CoinsInCirculationNanos 476 } else { 477 // Prior to the SalomonFixBlockHeight block, we calculate the founders reward 478 // only for new all time highs. 479 deltaNanos = existingProfileEntry.CoinWatermarkNanos - operationData.PrevCoinEntry.CoinWatermarkNanos 480 } 481 founderRewardNanos := IntDiv( 482 IntMul( 483 big.NewInt(int64(deltaNanos)), 484 big.NewInt(int64(existingProfileEntry.CreatorBasisPoints))), 485 big.NewInt(100*100)).Uint64() 486 if founderRewardNanos != deltaCreatorNanos { 487 return fmt.Errorf("_disconnectCreatorCoin: The creator coin nanos "+ 488 "the creator received %v does not equal the founder reward %v; "+ 489 "this should never happen", 490 deltaCreatorNanos, founderRewardNanos) 491 } 492 493 // Reset the creator's BalanceEntry to what it was previously. 494 *creatorBalanceEntry = *operationData.PrevCreatorBalanceEntry 495 bav._setBalanceEntryMappings(creatorBalanceEntry) 496 } else { 497 // We do a simliar sanity-check as above, but in this case we don't need to 498 // reset the creator mappings. 499 deltaBuyerNanos := transactorBalanceEntry.BalanceNanos - operationData.PrevTransactorBalanceEntry.BalanceNanos 500 deltaCoinsInCirculation := existingProfileEntry.CoinsInCirculationNanos - operationData.PrevCoinEntry.CoinsInCirculationNanos 501 if deltaBuyerNanos != deltaCoinsInCirculation { 502 return fmt.Errorf("_disconnectCreatorCoin: The creator coin nanos "+ 503 "the buyer/creator received (%v) does not equal the "+ 504 "creator coins added to the circulating supply %v", 505 deltaBuyerNanos, deltaCoinsInCirculation) 506 } 507 } 508 509 // Reset the Buyer's BalanceEntry to what it was previously. 510 *transactorBalanceEntry = *operationData.PrevTransactorBalanceEntry 511 bav._setBalanceEntryMappings(transactorBalanceEntry) 512 513 // If a DeSo founder reward was created, revert it. 514 if operationData.FounderRewardUtxoKey != nil { 515 if err := bav._unAddUtxo(operationData.FounderRewardUtxoKey); err != nil { 516 return errors.Wrapf(err, "_disconnectBitcoinExchange: Problem unAdding utxo %v: ", operationData.FounderRewardUtxoKey) 517 } 518 } 519 520 // The buyer will get the DeSo they locked up back when we revert the 521 // basic transfer. This is OK because resetting the CoinEntry to the previous 522 // value lowers the amount of DeSo locked in the profile by the same 523 // amount the buyer will receive. Thus no DeSo is created in this 524 // transaction. 525 } else if txMeta.OperationType == CreatorCoinOperationTypeSell { 526 // Set up some variables so that we can run some sanity-checks. The coins 527 // the transactor has and the coins in circulation should both have gone 528 // down as a result of the transaction, so both of these values should be 529 // positive. 530 deltaCoinNanos := operationData.PrevTransactorBalanceEntry.BalanceNanos - transactorBalanceEntry.BalanceNanos 531 deltaCoinsInCirculation := operationData.PrevCoinEntry.CoinsInCirculationNanos - existingProfileEntry.CoinsInCirculationNanos 532 533 // Sanity-check that the amount we decreased CoinsInCirculation by 534 // equals the total amount put in by the seller. 535 if deltaCoinNanos != deltaCoinsInCirculation { 536 return fmt.Errorf("_disconnectCreatorCoin: The creator coin nanos "+ 537 "the seller put in (%v) does not equal the "+ 538 "creator coins removed from the circulating supply %v", 539 deltaCoinNanos, deltaCoinsInCirculation) 540 } 541 542 // In the case of a sell we only need to revert the transactor's balance, 543 // and we don't have to worry about the creator's balance. 544 // Reset the transactor's BalanceEntry to what it was previously. 545 *transactorBalanceEntry = *operationData.PrevTransactorBalanceEntry 546 bav._setBalanceEntryMappings(transactorBalanceEntry) 547 548 // Un-add the UTXO taht was created as a result of this transaction. It should 549 // be the one at the end of our UTXO list at this point. 550 // 551 // The UtxoKey is simply the transaction hash with index set to the end of the 552 // transaction list. 553 utxoKey := UtxoKey{ 554 TxID: *currentTxn.Hash(), 555 // We give all UTXOs that are created as a result of BitcoinExchange transactions 556 // an index of zero. There is generally only one UTXO created in a BitcoinExchange 557 // transaction so this field doesn't really matter. 558 Index: uint32(len(currentTxn.TxOutputs)), 559 } 560 if err := bav._unAddUtxo(&utxoKey); err != nil { 561 return errors.Wrapf(err, "_disconnectBitcoinExchange: Problem unAdding utxo %v: ", utxoKey) 562 } 563 } else if txMeta.OperationType == CreatorCoinOperationTypeAddDeSo { 564 return fmt.Errorf("_disconnectCreatorCoin: Add DeSo operation txn not implemented") 565 } 566 567 // Reset the CoinEntry on the profile to what it was previously now that we 568 // have reverted the individual users' balances. 569 existingProfileEntry.CoinEntry = *operationData.PrevCoinEntry 570 bav._setProfileEntryMappings(existingProfileEntry) 571 572 // Now revert the basic transfer with the remaining operations. Cut off 573 // the CreatorCoin operation at the end since we just reverted it. 574 return bav._disconnectBasicTransfer( 575 currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight) 576 } 577 578 func (bav *UtxoView) _disconnectCreatorCoinTransfer( 579 operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash, 580 utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error { 581 582 // Verify that the last operation is a CreatorCoinTransfer operation 583 if len(utxoOpsForTxn) == 0 { 584 return fmt.Errorf("_disconnectCreatorCoinTransfer: utxoOperations are missing") 585 } 586 operationIndex := len(utxoOpsForTxn) - 1 587 if utxoOpsForTxn[operationIndex].Type != OperationTypeCreatorCoinTransfer { 588 return fmt.Errorf("_disconnectCreatorCoinTransfer: Trying to revert "+ 589 "OperationTypeCreatorCoinTransfer but found type %v", 590 utxoOpsForTxn[operationIndex].Type) 591 } 592 txMeta := currentTxn.TxnMeta.(*CreatorCoinTransferMetadataa) 593 operationData := utxoOpsForTxn[operationIndex] 594 operationIndex-- 595 596 // Get the profile corresponding to the creator coin txn. 597 existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.ProfilePublicKey) 598 // Sanity-check that it exists. 599 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 600 return fmt.Errorf("_disconnectCreatorCoinTransfer: CreatorCoinTransfer profile for "+ 601 "public key %v doesn't exist; this should never happen", 602 PkToStringBoth(txMeta.ProfilePublicKey)) 603 } 604 605 // Get the current / previous balance for the sender for sanity checking. 606 senderBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 607 currentTxn.PublicKey, txMeta.ProfilePublicKey) 608 // Sanity-check that the sender had a previous BalanceEntry, it should always exist. 609 if operationData.PrevSenderBalanceEntry == nil || operationData.PrevSenderBalanceEntry.isDeleted { 610 return fmt.Errorf("_disconnectCreatorCoinTransfer: Previous sender BalanceEntry "+ 611 "pubkey %v and creator pubkey %v does not exist; this should "+ 612 "never happen", 613 PkToStringBoth(currentTxn.PublicKey), PkToStringBoth(txMeta.ProfilePublicKey)) 614 } 615 senderPrevBalanceNanos := operationData.PrevSenderBalanceEntry.BalanceNanos 616 var senderCurrBalanceNanos uint64 617 // Since the sender may have given away their whole balance, their BalanceEntry can be nil. 618 if senderBalanceEntry != nil && !senderBalanceEntry.isDeleted { 619 senderCurrBalanceNanos = senderBalanceEntry.BalanceNanos 620 } 621 622 // Get the current / previous balance for the receiver for sanity checking. 623 receiverBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 624 txMeta.ReceiverPublicKey, txMeta.ProfilePublicKey) 625 // Sanity-check that the receiver BalanceEntry exists, it should always exist here. 626 if receiverBalanceEntry == nil || receiverBalanceEntry.isDeleted { 627 return fmt.Errorf("_disconnectCreatorCoinTransfer: Receiver BalanceEntry "+ 628 "pubkey %v and creator pubkey %v does not exist; this should "+ 629 "never happen", 630 PkToStringBoth(currentTxn.PublicKey), PkToStringBoth(txMeta.ProfilePublicKey)) 631 } 632 receiverCurrBalanceNanos := receiverBalanceEntry.BalanceNanos 633 var receiverPrevBalanceNanos uint64 634 if operationData.PrevReceiverBalanceEntry != nil { 635 receiverPrevBalanceNanos = operationData.PrevReceiverBalanceEntry.BalanceNanos 636 } 637 638 // Sanity check that the sender's current balance is less than their previous balance. 639 if senderCurrBalanceNanos > senderPrevBalanceNanos { 640 return fmt.Errorf("_disconnectCreatorCoinTransfer: Sender's current balance %d is "+ 641 "greater than their previous balance %d.", 642 senderCurrBalanceNanos, senderPrevBalanceNanos) 643 } 644 645 // Sanity check that the receiver's previous balance is less than their current balance. 646 if receiverPrevBalanceNanos > receiverCurrBalanceNanos { 647 return fmt.Errorf("_disconnectCreatorCoinTransfer: Receiver's previous balance %d is "+ 648 "greater than their current balance %d.", 649 receiverPrevBalanceNanos, receiverCurrBalanceNanos) 650 } 651 652 // Sanity check the sender's increase equals the receiver's decrease after disconnect. 653 senderBalanceIncrease := senderPrevBalanceNanos - senderCurrBalanceNanos 654 receiverBalanceDecrease := receiverCurrBalanceNanos - receiverPrevBalanceNanos 655 if senderBalanceIncrease != receiverBalanceDecrease { 656 return fmt.Errorf("_disconnectCreatorCoinTransfer: Sender's balance increase "+ 657 "of %d will not equal the receiver's balance decrease of %v after disconnect.", 658 senderBalanceIncrease, receiverBalanceDecrease) 659 } 660 661 // At this point we have sanity checked the current and previous state. Now we just 662 // need to revert the mappings. 663 664 // Delete the sender/receiver balance entries (they will be added back later if needed). 665 bav._deleteBalanceEntryMappings( 666 receiverBalanceEntry, txMeta.ReceiverPublicKey, txMeta.ProfilePublicKey) 667 if senderBalanceEntry != nil { 668 bav._deleteBalanceEntryMappings( 669 senderBalanceEntry, currentTxn.PublicKey, txMeta.ProfilePublicKey) 670 } 671 672 // Set the balance entries appropriately. 673 bav._setBalanceEntryMappings(operationData.PrevSenderBalanceEntry) 674 if operationData.PrevReceiverBalanceEntry != nil && operationData.PrevReceiverBalanceEntry.BalanceNanos != 0 { 675 bav._setBalanceEntryMappings(operationData.PrevReceiverBalanceEntry) 676 } 677 678 // Reset the CoinEntry on the profile to what it was previously now that we 679 // have reverted the individual users' balances. 680 existingProfileEntry.CoinEntry = *operationData.PrevCoinEntry 681 bav._setProfileEntryMappings(existingProfileEntry) 682 683 // If the transaction had diamonds, let's revert those too. 684 diamondPostHashBytes, hasDiamondPostHash := currentTxn.ExtraData[DiamondPostHashKey] 685 if hasDiamondPostHash { 686 // Sanity check the post hash bytes before creating the post hash. 687 diamondPostHash := &BlockHash{} 688 if len(diamondPostHashBytes) != HashSizeBytes { 689 return fmt.Errorf( 690 "_disconnectCreatorCoin: DiamondPostHashBytes has incorrect length: %d", 691 len(diamondPostHashBytes)) 692 } 693 copy(diamondPostHash[:], diamondPostHashBytes[:]) 694 695 // Get the existing diamondEntry so we can delete it. 696 senderPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey) 697 receiverPKID := bav.GetPKIDForPublicKey(txMeta.ReceiverPublicKey) 698 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 699 diamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 700 701 // Sanity check that the diamondEntry is not nil. 702 if diamondEntry == nil { 703 return fmt.Errorf( 704 "_disconnectCreatorCoin: Found nil diamond entry for diamondKey: %v", &diamondKey) 705 } 706 707 // Delete the diamond entry mapping and re-add it if the previous mapping is not nil. 708 bav._deleteDiamondEntryMappings(diamondEntry) 709 if operationData.PrevDiamondEntry != nil { 710 bav._setDiamondEntryMappings(operationData.PrevDiamondEntry) 711 } 712 713 // Finally, revert the post entry mapping since we likely updated the DiamondCount. 714 bav._setPostEntryMappings(operationData.PrevPostEntry) 715 } 716 717 // Now revert the basic transfer with the remaining operations. Cut off 718 // the CreatorCoin operation at the end since we just reverted it. 719 return bav._disconnectBasicTransfer( 720 currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight) 721 } 722 723 // TODO: A lot of duplicate code between buy and sell. Consider factoring 724 // out the common code. 725 func (bav *UtxoView) HelpConnectCreatorCoinBuy( 726 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 727 _totalInput uint64, _totalOutput uint64, _creatorCoinReturnedNanos uint64, _founderRewardNanos uint64, 728 _utxoOps []*UtxoOperation, _err error) { 729 730 // Connect basic txn to get the total input and the total output without 731 // considering the transaction metadata. 732 totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer( 733 txn, txHash, blockHeight, verifySignatures) 734 if err != nil { 735 return 0, 0, 0, 0, nil, errors.Wrapf(err, "_connectCreatorCoin: ") 736 } 737 738 // Force the input to be non-zero so that we can prevent replay attacks. If 739 // we didn't do this then someone could replay your sell over and over again 740 // to force-convert all your creator coin into DeSo. Think about it. 741 if totalInput == 0 { 742 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinRequiresNonZeroInput 743 } 744 745 // At this point the inputs and outputs have been processed. Now we 746 // need to handle the metadata. 747 748 // Check that the specified profile public key is valid and that a profile 749 // corresponding to that public key exists. 750 txMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 751 if len(txMeta.ProfilePublicKey) != btcec.PubKeyBytesLenCompressed { 752 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinInvalidPubKeySize 753 } 754 755 // Dig up the profile. It must exist for the user to be able to 756 // operate on its coin. 757 existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.ProfilePublicKey) 758 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 759 return 0, 0, 0, 0, nil, errors.Wrapf( 760 RuleErrorCreatorCoinOperationOnNonexistentProfile, 761 "_connectCreatorCoin: Profile pub key: %v %v", 762 PkToStringMainnet(txMeta.ProfilePublicKey), PkToStringTestnet(txMeta.ProfilePublicKey)) 763 } 764 765 // At this point we are confident that we have a profile that 766 // exists that corresponds to the profile public key the user 767 // provided. 768 769 // Check that the amount of DeSo being traded for creator coin is 770 // non-zero. 771 desoBeforeFeesNanos := txMeta.DeSoToSellNanos 772 if desoBeforeFeesNanos == 0 { 773 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustTradeNonZeroDeSo 774 } 775 // The amount of DeSo being traded counts as output being spent by 776 // this transaction, so add it to the transaction output and check that 777 // the resulting output does not exceed the total input. 778 // 779 // Check for overflow of the outputs before adding. 780 if totalOutput > math.MaxUint64-desoBeforeFeesNanos { 781 return 0, 0, 0, 0, nil, errors.Wrapf( 782 RuleErrorCreatorCoinTxnOutputWithInvalidBuyAmount, 783 "_connectCreatorCoin: %v", desoBeforeFeesNanos) 784 } 785 totalOutput += desoBeforeFeesNanos 786 // It's assumed the caller code will check that things like output <= input, 787 // but we check it here just in case... 788 if totalInput < totalOutput { 789 return 0, 0, 0, 0, nil, errors.Wrapf( 790 RuleErrorCreatorCoinTxnOutputExceedsInput, 791 "_connectCreatorCoin: Input: %v, Output: %v", totalInput, totalOutput) 792 } 793 // At this point we have verified that the output is sufficient to cover 794 // the amount the user wants to use to buy the creator's coin. 795 796 // Now we burn some DeSo before executing the creator coin buy. Doing 797 // this guarantees that floating point errors in our subsequent calculations 798 // will not result in a user being able to print infinite amounts of DeSo 799 // through the protocol. 800 // 801 // TODO(performance): We use bigints to avoid overflow in the intermediate 802 // stages of the calculation but this most likely isn't necessary. This 803 // formula is equal to: 804 // - desoAfterFeesNanos = desoBeforeFeesNanos * (CreatorCoinTradeFeeBasisPoints / (100*100)) 805 desoAfterFeesNanos := IntDiv( 806 IntMul( 807 big.NewInt(int64(desoBeforeFeesNanos)), 808 big.NewInt(int64(100*100-bav.Params.CreatorCoinTradeFeeBasisPoints))), 809 big.NewInt(100*100)).Uint64() 810 811 // The amount of DeSo being convertend must be nonzero after fees as well. 812 if desoAfterFeesNanos == 0 { 813 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustTradeNonZeroDeSoAfterFees 814 } 815 816 // Figure out how much deso goes to the founder. 817 // Note: If the user performing this transaction has the same public key as the 818 // profile being bought, we do not cut a founder reward. 819 desoRemainingNanos := uint64(0) 820 desoFounderRewardNanos := uint64(0) 821 if blockHeight > DeSoFounderRewardBlockHeight && 822 !reflect.DeepEqual(txn.PublicKey, existingProfileEntry.PublicKey) { 823 824 // This formula is equal to: 825 // desoFounderRewardNanos = desoAfterFeesNanos * creatorBasisPoints / (100*100) 826 desoFounderRewardNanos = IntDiv( 827 IntMul( 828 big.NewInt(int64(desoAfterFeesNanos)), 829 big.NewInt(int64(existingProfileEntry.CreatorBasisPoints))), 830 big.NewInt(100*100)).Uint64() 831 832 // Sanity check, just to be extra safe. 833 if desoAfterFeesNanos < desoFounderRewardNanos { 834 return 0, 0, 0, 0, nil, fmt.Errorf("HelpConnectCreatorCoinBuy: desoAfterFeesNanos"+ 835 " less than desoFounderRewardNanos: %v %v", 836 desoAfterFeesNanos, desoFounderRewardNanos) 837 } 838 839 desoRemainingNanos = desoAfterFeesNanos - desoFounderRewardNanos 840 } else { 841 desoRemainingNanos = desoAfterFeesNanos 842 } 843 844 if desoRemainingNanos == 0 { 845 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustTradeNonZeroDeSoAfterFounderReward 846 } 847 848 // If no DeSo is currently locked in the profile then we use the 849 // polynomial equation to mint creator coins. We do this because the 850 // Uniswap/Bancor equations don't work when zero coins have been minted, 851 // and so we have to special case here. See this wolfram sheet for all 852 // the equations with tests: 853 // - https://pastebin.com/raw/1EmgeW56 854 // 855 // Note also that we use big floats with a custom math library in order 856 // to guarantee that all nodes get the same result regardless of what 857 // architecture they're running on. If we didn't do this, then some nodes 858 // could round floats or use different levels of precision for intermediate 859 // results and get different answers which would break consensus. 860 creatorCoinToMintNanos := CalculateCreatorCoinToMint( 861 desoRemainingNanos, existingProfileEntry.CoinsInCirculationNanos, 862 existingProfileEntry.DeSoLockedNanos, bav.Params) 863 864 // Check if the total amount minted satisfies CreatorCoinAutoSellThresholdNanos. 865 // This makes it prohibitively expensive for a user to buy themself above the 866 // CreatorCoinAutoSellThresholdNanos and then spam tiny nano DeSo creator 867 // coin purchases causing the effective Bancor Creator Coin Reserve Ratio to drift. 868 if blockHeight > SalomonFixBlockHeight { 869 if creatorCoinToMintNanos < bav.Params.CreatorCoinAutoSellThresholdNanos { 870 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustSatisfyAutoSellThresholdNanos 871 } 872 } 873 874 // At this point, we know how much creator coin we are going to mint. 875 // Now it's just a matter of adjusting our bookkeeping and potentially 876 // giving the creator a founder reward. 877 878 // Save all the old values from the CoinEntry before we potentially 879 // update them. Note that CoinEntry doesn't contain any pointers and so 880 // a direct copy is OK. 881 prevCoinEntry := existingProfileEntry.CoinEntry 882 883 // Increment DeSoLockedNanos. Sanity-check that we're not going to 884 // overflow. 885 if existingProfileEntry.DeSoLockedNanos > math.MaxUint64-desoRemainingNanos { 886 return 0, 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Overflow while summing"+ 887 "DeSoLockedNanos and desoAfterFounderRewardNanos: %v %v", 888 existingProfileEntry.DeSoLockedNanos, desoRemainingNanos) 889 } 890 existingProfileEntry.DeSoLockedNanos += desoRemainingNanos 891 892 // Increment CoinsInCirculation. Sanity-check that we're not going to 893 // overflow. 894 if existingProfileEntry.CoinsInCirculationNanos > math.MaxUint64-creatorCoinToMintNanos { 895 return 0, 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Overflow while summing"+ 896 "CoinsInCirculationNanos and creatorCoinToMintNanos: %v %v", 897 existingProfileEntry.CoinsInCirculationNanos, creatorCoinToMintNanos) 898 } 899 existingProfileEntry.CoinsInCirculationNanos += creatorCoinToMintNanos 900 901 // Calculate the *Creator Coin nanos* to give as a founder reward. 902 creatorCoinFounderRewardNanos := uint64(0) 903 if blockHeight > DeSoFounderRewardBlockHeight { 904 // Do nothing. The chain stopped minting creator coins as a founder reward for 905 // creators at this blockheight. It gives DeSo as a founder reward now instead. 906 907 } else if blockHeight > SalomonFixBlockHeight { 908 // Following the SalomonFixBlockHeight block, creator coin buys continuously mint 909 // a founders reward based on the CreatorBasisPoints. 910 911 creatorCoinFounderRewardNanos = IntDiv( 912 IntMul( 913 big.NewInt(int64(creatorCoinToMintNanos)), 914 big.NewInt(int64(existingProfileEntry.CreatorBasisPoints))), 915 big.NewInt(100*100)).Uint64() 916 } else { 917 // Up to and including the SalomonFixBlockHeight block, creator coin buys only minted 918 // a founders reward if the creator reached a new all time high. 919 920 if existingProfileEntry.CoinsInCirculationNanos > existingProfileEntry.CoinWatermarkNanos { 921 // This value must be positive if we made it past the if condition above. 922 watermarkDiff := existingProfileEntry.CoinsInCirculationNanos - existingProfileEntry.CoinWatermarkNanos 923 // The founder reward is computed as a percentage of the "net coins created," 924 // which is equal to the watermarkDiff 925 creatorCoinFounderRewardNanos = IntDiv( 926 IntMul( 927 big.NewInt(int64(watermarkDiff)), 928 big.NewInt(int64(existingProfileEntry.CreatorBasisPoints))), 929 big.NewInt(100*100)).Uint64() 930 } 931 } 932 933 // CoinWatermarkNanos is no longer used, however it may be helpful for 934 // future analytics or updates so we continue to update it here. 935 if existingProfileEntry.CoinsInCirculationNanos > existingProfileEntry.CoinWatermarkNanos { 936 existingProfileEntry.CoinWatermarkNanos = existingProfileEntry.CoinsInCirculationNanos 937 } 938 939 // At this point, founderRewardNanos will be non-zero if and only if we increased 940 // the watermark *and* there was a non-zero CreatorBasisPoints set on the CoinEntry 941 // *and* the blockHeight is less than DeSoFounderRewardBlockHeight. 942 943 // The user gets whatever's left after we pay the founder their reward. 944 coinsBuyerGetsNanos := creatorCoinToMintNanos - creatorCoinFounderRewardNanos 945 946 // If the coins the buyer is getting is less than the minimum threshold that 947 // they expected to get, then the transaction is invalid. This prevents 948 // front-running attacks, but it also prevents the buyer from getting a 949 // terrible price. 950 // 951 // Note that when the min is set to zero it means we should skip this check. 952 if txMeta.MinCreatorCoinExpectedNanos != 0 && 953 coinsBuyerGetsNanos < txMeta.MinCreatorCoinExpectedNanos { 954 return 0, 0, 0, 0, nil, errors.Wrapf( 955 RuleErrorCreatorCoinLessThanMinimumSetByUser, 956 "_connectCreatorCoin: Amount that would be minted and given to user: "+ 957 "%v, amount that would be given to founder: %v, amount user needed: %v", 958 coinsBuyerGetsNanos, creatorCoinFounderRewardNanos, txMeta.MinCreatorCoinExpectedNanos) 959 } 960 961 // If we get here, we are good to go. We will now update the balance of the 962 // buyer and the creator (assuming we had a non-zero founderRewardNanos). 963 964 // Look up a CreatorCoinBalanceEntry for the buyer and the creator. Create 965 // an entry for each if one doesn't exist already. 966 buyerBalanceEntry, hodlerPKID, creatorPKID := 967 bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 968 txn.PublicKey, existingProfileEntry.PublicKey) 969 // If the user does not have a balance entry or the user's balance entry is deleted and we have passed the 970 // BuyCreatorCoinAfterDeletedBalanceEntryFixBlockHeight, we create a new balance entry. 971 if buyerBalanceEntry == nil || 972 (buyerBalanceEntry.isDeleted && blockHeight > BuyCreatorCoinAfterDeletedBalanceEntryFixBlockHeight) { 973 // If there is no balance entry for this mapping yet then just create it. 974 // In this case the balance will be zero. 975 buyerBalanceEntry = &BalanceEntry{ 976 // The person who created the txn is they buyer/hodler 977 HODLerPKID: hodlerPKID, 978 // The creator is the owner of the profile that corresponds to the coin. 979 CreatorPKID: creatorPKID, 980 BalanceNanos: uint64(0), 981 } 982 } 983 984 // Get the balance entry for the creator. In this case the creator owns 985 // their own coin and therefore the creator is also the HODLer. We need 986 // this so we can pay the creator their founder reward. Note that we have 987 // a special case when the creator is purchasing their own coin. 988 var creatorBalanceEntry *BalanceEntry 989 if reflect.DeepEqual(txn.PublicKey, existingProfileEntry.PublicKey) { 990 // If the creator is buying their own coin, don't fetch/create a 991 // duplicate entry. If we didn't do this, we might wind up with two 992 // duplicate BalanceEntrys when a creator is buying their own coin. 993 creatorBalanceEntry = buyerBalanceEntry 994 } else { 995 // In this case, the creator is distinct from the buyer, so fetch and 996 // potentially create a new BalanceEntry for them rather than using the 997 // existing one. 998 creatorBalanceEntry, hodlerPKID, creatorPKID = bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 999 existingProfileEntry.PublicKey, existingProfileEntry.PublicKey) 1000 // If the creator does not have a balance entry or the creator's balance entry is deleted and we have passed the 1001 // BuyCreatorCoinAfterDeletedBalanceEntryFixBlockHeight, we create a new balance entry. 1002 if creatorBalanceEntry == nil || 1003 (creatorBalanceEntry.isDeleted && blockHeight > BuyCreatorCoinAfterDeletedBalanceEntryFixBlockHeight) { 1004 // If there is no balance entry then it means the creator doesn't own 1005 // any of their coin yet. In this case we create a new entry for them 1006 // with a zero balance. 1007 creatorBalanceEntry = &BalanceEntry{ 1008 HODLerPKID: hodlerPKID, 1009 CreatorPKID: creatorPKID, 1010 BalanceNanos: uint64(0), 1011 } 1012 } 1013 } 1014 // At this point we should have a BalanceEntry for the buyer and the creator. 1015 // These may be the same BalancEntry if the creator is buying their own coin, 1016 // but that is OK. 1017 1018 // Save the previous balance entry before modifying it. If the creator is 1019 // buying their own coin, this will be the same BalanceEntry, which is fine. 1020 prevBuyerBalanceEntry := *buyerBalanceEntry 1021 prevCreatorBalanceEntry := *creatorBalanceEntry 1022 1023 // Increase the buyer and the creator's balances by the amounts computed 1024 // previously. Always check for overflow. 1025 if buyerBalanceEntry.BalanceNanos > math.MaxUint64-coinsBuyerGetsNanos { 1026 return 0, 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Overflow while summing"+ 1027 "buyerBalanceEntry.BalanceNanos and coinsBuyerGetsNanos %v %v", 1028 buyerBalanceEntry.BalanceNanos, coinsBuyerGetsNanos) 1029 } 1030 // Check that if the buyer is receiving nanos for the first time, it's enough 1031 // to push them above the CreatorCoinAutoSellThresholdNanos threshold. This helps 1032 // prevent tiny amounts of nanos from drifting the ratio of creator coins to DeSo locked. 1033 if blockHeight > SalomonFixBlockHeight { 1034 if buyerBalanceEntry.BalanceNanos == 0 && coinsBuyerGetsNanos != 0 && 1035 coinsBuyerGetsNanos < bav.Params.CreatorCoinAutoSellThresholdNanos { 1036 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustSatisfyAutoSellThresholdNanosForBuyer 1037 } 1038 } 1039 1040 // Check if this is the buyers first buy or first buy after a complete sell. 1041 // If it is, we increment the NumberOfHolders to reflect this value. 1042 if buyerBalanceEntry.BalanceNanos == 0 && coinsBuyerGetsNanos != 0 { 1043 // Increment number of holders by one to reflect the buyer 1044 existingProfileEntry.NumberOfHolders += 1 1045 1046 // Update the profile to reflect the new number of holders 1047 bav._setProfileEntryMappings(existingProfileEntry) 1048 } 1049 // Finally increment the buyerBalanceEntry.BalanceNanos to reflect 1050 // the purchased coinsBuyerGetsNanos. If coinsBuyerGetsNanos is greater than 0, we set HasPurchased to true. 1051 buyerBalanceEntry.BalanceNanos += coinsBuyerGetsNanos 1052 buyerBalanceEntry.HasPurchased = true 1053 1054 // If the creator is buying their own coin, this will just be modifying 1055 // the same pointer as the buyerBalanceEntry, which is what we want. 1056 if creatorBalanceEntry.BalanceNanos > math.MaxUint64-creatorCoinFounderRewardNanos { 1057 return 0, 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Overflow while summing"+ 1058 "creatorBalanceEntry.BalanceNanos and creatorCoinFounderRewardNanos %v %v", 1059 creatorBalanceEntry.BalanceNanos, creatorCoinFounderRewardNanos) 1060 } 1061 // Check that if the creator is receiving nanos for the first time, it's enough 1062 // to push them above the CreatorCoinAutoSellThresholdNanos threshold. This helps 1063 // prevent tiny amounts of nanos from drifting the effective creator coin reserve ratio drift. 1064 if creatorBalanceEntry.BalanceNanos == 0 && 1065 creatorCoinFounderRewardNanos != 0 && 1066 creatorCoinFounderRewardNanos < bav.Params.CreatorCoinAutoSellThresholdNanos && 1067 blockHeight > SalomonFixBlockHeight { 1068 1069 return 0, 0, 0, 0, nil, RuleErrorCreatorCoinBuyMustSatisfyAutoSellThresholdNanosForCreator 1070 } 1071 // Check if the creator's balance is going from zero to non-zero and increment the NumberOfHolders if so. 1072 if creatorBalanceEntry.BalanceNanos == 0 && creatorCoinFounderRewardNanos != 0 { 1073 // Increment number of holders by one to reflect the creator 1074 existingProfileEntry.NumberOfHolders += 1 1075 1076 // Update the profile to reflect the new number of holders 1077 bav._setProfileEntryMappings(existingProfileEntry) 1078 } 1079 creatorBalanceEntry.BalanceNanos += creatorCoinFounderRewardNanos 1080 1081 // At this point the balances for the buyer and the creator should be correct 1082 // so set the mappings in the view. 1083 bav._setBalanceEntryMappings(buyerBalanceEntry) 1084 // Avoid setting the same entry twice if the creator is buying their own coin. 1085 if buyerBalanceEntry != creatorBalanceEntry { 1086 bav._setBalanceEntryMappings(creatorBalanceEntry) 1087 } 1088 1089 // Finally, if the creator is getting a deso founder reward, add a UTXO for it. 1090 var outputKey *UtxoKey 1091 if blockHeight > DeSoFounderRewardBlockHeight { 1092 if desoFounderRewardNanos > 0 { 1093 // Create a new entry for this output and add it to the view. It should be 1094 // added at the end of the utxo list. 1095 outputKey = &UtxoKey{ 1096 TxID: *txHash, 1097 // The output is like an extra virtual output at the end of the transaction. 1098 Index: uint32(len(txn.TxOutputs)), 1099 } 1100 1101 utxoEntry := UtxoEntry{ 1102 AmountNanos: desoFounderRewardNanos, 1103 PublicKey: existingProfileEntry.PublicKey, 1104 BlockHeight: blockHeight, 1105 UtxoType: UtxoTypeCreatorCoinFounderReward, 1106 UtxoKey: outputKey, 1107 // We leave the position unset and isSpent to false by default. 1108 // The position will be set in the call to _addUtxo. 1109 } 1110 1111 utxoOp, err := bav._addUtxo(&utxoEntry) 1112 if err != nil { 1113 return 0, 0, 0, 0, nil, errors.Wrapf(err, "HelpConnectCreatorCoinBuy: Problem adding output utxo") 1114 } 1115 1116 // Rosetta uses this UtxoOperation to provide INPUT amounts 1117 utxoOpsForTxn = append(utxoOpsForTxn, utxoOp) 1118 } 1119 } 1120 1121 // Compute the change in DESO locked. This information is needed by Rosetta 1122 // and it's much more efficient to compute it here than it is to recompute 1123 // it later. 1124 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 1125 return 0, 0, 0, 0, nil, errors.Wrapf(err, "HelpConnectCreatorCoinBuy: Error computing "+ 1126 "desoLockedNanosDiff: Missing profile") 1127 } 1128 desoLockedNanosDiff := int64(existingProfileEntry.DeSoLockedNanos) - int64(prevCoinEntry.DeSoLockedNanos) 1129 1130 // Add an operation to the list at the end indicating we've executed a 1131 // CreatorCoin txn. Save the previous state of the CoinEntry for easy 1132 // reversion during disconnect. 1133 utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ 1134 Type: OperationTypeCreatorCoin, 1135 PrevCoinEntry: &prevCoinEntry, 1136 PrevTransactorBalanceEntry: &prevBuyerBalanceEntry, 1137 PrevCreatorBalanceEntry: &prevCreatorBalanceEntry, 1138 FounderRewardUtxoKey: outputKey, 1139 CreatorCoinDESOLockedNanosDiff: desoLockedNanosDiff, 1140 }) 1141 1142 return totalInput, totalOutput, coinsBuyerGetsNanos, creatorCoinFounderRewardNanos, utxoOpsForTxn, nil 1143 } 1144 1145 // TODO: A lot of duplicate code between buy and sell. Consider factoring 1146 // out the common code. 1147 func (bav *UtxoView) HelpConnectCreatorCoinSell( 1148 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 1149 _totalInput uint64, _totalOutput uint64, _desoReturnedNanos uint64, 1150 _utxoOps []*UtxoOperation, _err error) { 1151 1152 // Connect basic txn to get the total input and the total output without 1153 // considering the transaction metadata. 1154 totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer( 1155 txn, txHash, blockHeight, verifySignatures) 1156 if err != nil { 1157 return 0, 0, 0, nil, errors.Wrapf(err, "_connectCreatorCoin: ") 1158 } 1159 1160 // Force the input to be non-zero so that we can prevent replay attacks. If 1161 // we didn't do this then someone could replay your sell over and over again 1162 // to force-convert all your creator coin into DeSo. Think about it. 1163 if totalInput == 0 { 1164 return 0, 0, 0, nil, RuleErrorCreatorCoinRequiresNonZeroInput 1165 } 1166 1167 // Verify that the output does not exceed the input. This check should also 1168 // be done by the caller, but we do it here as well. 1169 if totalInput < totalOutput { 1170 return 0, 0, 0, nil, errors.Wrapf( 1171 RuleErrorCreatorCoinTxnOutputExceedsInput, 1172 "_connectCreatorCoin: Input: %v, Output: %v", totalInput, totalOutput) 1173 } 1174 1175 // At this point the inputs and outputs have been processed. Now we 1176 // need to handle the metadata. 1177 1178 // Check that the specified profile public key is valid and that a profile 1179 // corresponding to that public key exists. 1180 txMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1181 if len(txMeta.ProfilePublicKey) != btcec.PubKeyBytesLenCompressed { 1182 return 0, 0, 0, nil, RuleErrorCreatorCoinInvalidPubKeySize 1183 } 1184 1185 // Dig up the profile. It must exist for the user to be able to 1186 // operate on its coin. 1187 existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.ProfilePublicKey) 1188 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 1189 return 0, 0, 0, nil, errors.Wrapf( 1190 RuleErrorCreatorCoinOperationOnNonexistentProfile, 1191 "_connectCreatorCoin: Profile pub key: %v %v", 1192 PkToStringMainnet(txMeta.ProfilePublicKey), PkToStringTestnet(txMeta.ProfilePublicKey)) 1193 } 1194 1195 // At this point we are confident that we have a profile that 1196 // exists that corresponds to the profile public key the user 1197 // provided. 1198 1199 // Look up a BalanceEntry for the seller. If it doesn't exist then the seller 1200 // implicitly has a balance of zero coins, and so the sell transaction shouldn't be 1201 // allowed. 1202 sellerBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 1203 txn.PublicKey, existingProfileEntry.PublicKey) 1204 if sellerBalanceEntry == nil || sellerBalanceEntry.isDeleted { 1205 return 0, 0, 0, nil, RuleErrorCreatorCoinSellerBalanceEntryDoesNotExist 1206 } 1207 1208 // Check that the amount of creator coin being sold is non-zero. 1209 creatorCoinToSellNanos := txMeta.CreatorCoinToSellNanos 1210 if creatorCoinToSellNanos == 0 { 1211 return 0, 0, 0, nil, RuleErrorCreatorCoinSellMustTradeNonZeroCreatorCoin 1212 } 1213 1214 // Check that the amount of creator coin being sold does not exceed the user's 1215 // balance of this particular creator coin. 1216 if creatorCoinToSellNanos > sellerBalanceEntry.BalanceNanos { 1217 return 0, 0, 0, nil, errors.Wrapf( 1218 RuleErrorCreatorCoinSellInsufficientCoins, 1219 "_connectCreatorCoin: CreatorCoin nanos being sold %v exceeds "+ 1220 "user's creator coin balance %v", 1221 creatorCoinToSellNanos, sellerBalanceEntry.BalanceNanos) 1222 } 1223 1224 // If the amount of DeSo locked in the profile is zero then selling is 1225 // not allowed. 1226 if existingProfileEntry.DeSoLockedNanos == 0 { 1227 return 0, 0, 0, nil, RuleErrorCreatorCoinSellNotAllowedWhenZeroDeSoLocked 1228 } 1229 1230 desoBeforeFeesNanos := uint64(0) 1231 // Compute the amount of DeSo to return. 1232 if blockHeight > SalomonFixBlockHeight { 1233 // Following the SalomonFixBlockHeight block, if a user would be left with less than 1234 // bav.Params.CreatorCoinAutoSellThresholdNanos, we clear all their remaining holdings 1235 // to prevent 1 or 2 lingering creator coin nanos from staying in their wallet. 1236 // This also gives a method for cleanly and accurately reducing the numberOfHolders. 1237 1238 // Note that we check that sellerBalanceEntry.BalanceNanos >= creatorCoinToSellNanos above. 1239 if sellerBalanceEntry.BalanceNanos-creatorCoinToSellNanos < bav.Params.CreatorCoinAutoSellThresholdNanos { 1240 // Setup to sell all the creator coins the seller has. 1241 creatorCoinToSellNanos = sellerBalanceEntry.BalanceNanos 1242 1243 // Compute the amount of DeSo to return with the new creatorCoinToSellNanos. 1244 desoBeforeFeesNanos = CalculateDeSoToReturn( 1245 creatorCoinToSellNanos, existingProfileEntry.CoinsInCirculationNanos, 1246 existingProfileEntry.DeSoLockedNanos, bav.Params) 1247 1248 // If the amount the formula is offering is more than what is locked in the 1249 // profile, then truncate it down. This addresses an edge case where our 1250 // equations may return *too much* DeSo due to rounding errors. 1251 if desoBeforeFeesNanos > existingProfileEntry.DeSoLockedNanos { 1252 desoBeforeFeesNanos = existingProfileEntry.DeSoLockedNanos 1253 } 1254 } else { 1255 // If we're above the CreatorCoinAutoSellThresholdNanos, we can safely compute 1256 // the amount to return based on the Bancor curve. 1257 desoBeforeFeesNanos = CalculateDeSoToReturn( 1258 creatorCoinToSellNanos, existingProfileEntry.CoinsInCirculationNanos, 1259 existingProfileEntry.DeSoLockedNanos, bav.Params) 1260 1261 // If the amount the formula is offering is more than what is locked in the 1262 // profile, then truncate it down. This addresses an edge case where our 1263 // equations may return *too much* DeSo due to rounding errors. 1264 if desoBeforeFeesNanos > existingProfileEntry.DeSoLockedNanos { 1265 desoBeforeFeesNanos = existingProfileEntry.DeSoLockedNanos 1266 } 1267 } 1268 } else { 1269 // Prior to the SalomonFixBlockHeight block, coins would be minted based on floating point 1270 // arithmetic with the exception being if a creator was selling all remaining creator coins. This caused 1271 // a rare issue where a creator would be left with 1 creator coin nano in circulation 1272 // and 1 nano DeSo locked after completely selling. This in turn made the Bancor Curve unstable. 1273 1274 if creatorCoinToSellNanos == existingProfileEntry.CoinsInCirculationNanos { 1275 desoBeforeFeesNanos = existingProfileEntry.DeSoLockedNanos 1276 } else { 1277 // Calculate the amount to return based on the Bancor Curve. 1278 desoBeforeFeesNanos = CalculateDeSoToReturn( 1279 creatorCoinToSellNanos, existingProfileEntry.CoinsInCirculationNanos, 1280 existingProfileEntry.DeSoLockedNanos, bav.Params) 1281 1282 // If the amount the formula is offering is more than what is locked in the 1283 // profile, then truncate it down. This addresses an edge case where our 1284 // equations may return *too much* DeSo due to rounding errors. 1285 if desoBeforeFeesNanos > existingProfileEntry.DeSoLockedNanos { 1286 desoBeforeFeesNanos = existingProfileEntry.DeSoLockedNanos 1287 } 1288 } 1289 } 1290 1291 // Save all the old values from the CoinEntry before we potentially 1292 // update them. Note that CoinEntry doesn't contain any pointers and so 1293 // a direct copy is OK. 1294 prevCoinEntry := existingProfileEntry.CoinEntry 1295 1296 // Subtract the amount of DeSo the seller is getting from the amount of 1297 // DeSo locked in the profile. Sanity-check that it does not exceed the 1298 // total amount of DeSo locked. 1299 if desoBeforeFeesNanos > existingProfileEntry.DeSoLockedNanos { 1300 return 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: DeSo nanos seller "+ 1301 "would get %v exceeds DeSo nanos locked in profile %v", 1302 desoBeforeFeesNanos, existingProfileEntry.DeSoLockedNanos) 1303 } 1304 existingProfileEntry.DeSoLockedNanos -= desoBeforeFeesNanos 1305 1306 // Subtract the number of coins the seller is selling from the number of coins 1307 // in circulation. Sanity-check that it does not exceed the number of coins 1308 // currently in circulation. 1309 if creatorCoinToSellNanos > existingProfileEntry.CoinsInCirculationNanos { 1310 return 0, 0, 0, nil, fmt.Errorf("_connectCreatorCoin: CreatorCoin nanos seller "+ 1311 "is selling %v exceeds CreatorCoin nanos in circulation %v", 1312 creatorCoinToSellNanos, existingProfileEntry.CoinsInCirculationNanos) 1313 } 1314 existingProfileEntry.CoinsInCirculationNanos -= creatorCoinToSellNanos 1315 1316 // Check if this is a complete sell of the seller's remaining creator coins 1317 if sellerBalanceEntry.BalanceNanos == creatorCoinToSellNanos { 1318 existingProfileEntry.NumberOfHolders -= 1 1319 } 1320 1321 // If the number of holders has reached zero, we clear all the DeSoLockedNanos and 1322 // creatorCoinToSellNanos to ensure that the profile is reset to its normal initial state. 1323 // It's okay to modify these values because they are saved in the PrevCoinEntry. 1324 if existingProfileEntry.NumberOfHolders == 0 { 1325 existingProfileEntry.DeSoLockedNanos = 0 1326 existingProfileEntry.CoinsInCirculationNanos = 0 1327 } 1328 1329 // Save the seller's balance before we modify it. We don't need to save the 1330 // creator's BalancEntry on a sell because the creator's balance will not 1331 // be modified. 1332 prevTransactorBalanceEntry := *sellerBalanceEntry 1333 1334 // Subtract the number of coins the seller is selling from the number of coins 1335 // they HODL. Note that we already checked that this amount does not exceed the 1336 // seller's balance above. Note that this amount equals sellerBalanceEntry.BalanceNanos 1337 // in the event where the requested remaining creator coin balance dips 1338 // below CreatorCoinAutoSellThresholdNanos. 1339 sellerBalanceEntry.BalanceNanos -= creatorCoinToSellNanos 1340 1341 // If the seller's balance will be zero after this transaction, set HasPurchased to false 1342 if sellerBalanceEntry.BalanceNanos == 0 { 1343 sellerBalanceEntry.HasPurchased = false 1344 } 1345 1346 // Set the new BalanceEntry in our mappings for the seller and set the 1347 // ProfileEntry mappings as well since everything is up to date. 1348 bav._setBalanceEntryMappings(sellerBalanceEntry) 1349 bav._setProfileEntryMappings(existingProfileEntry) 1350 1351 // Charge a fee on the DeSo the seller is getting to hedge against 1352 // floating point errors 1353 desoAfterFeesNanos := IntDiv( 1354 IntMul( 1355 big.NewInt(int64(desoBeforeFeesNanos)), 1356 big.NewInt(int64(100*100-bav.Params.CreatorCoinTradeFeeBasisPoints))), 1357 big.NewInt(100*100)).Uint64() 1358 1359 // Check that the seller is getting back an amount of DeSo that is 1360 // greater than or equal to what they expect. Note that this check is 1361 // skipped if the min amount specified is zero. 1362 if txMeta.MinDeSoExpectedNanos != 0 && 1363 desoAfterFeesNanos < txMeta.MinDeSoExpectedNanos { 1364 1365 return 0, 0, 0, nil, errors.Wrapf( 1366 RuleErrorDeSoReceivedIsLessThanMinimumSetBySeller, 1367 "_connectCreatorCoin: DeSo nanos that would be given to seller: "+ 1368 "%v, amount user needed: %v", 1369 desoAfterFeesNanos, txMeta.MinDeSoExpectedNanos) 1370 } 1371 1372 // Now that we have all the information we need, save a UTXO allowing the user to 1373 // spend the DeSo from the sale in the future. 1374 outputKey := UtxoKey{ 1375 TxID: *txn.Hash(), 1376 // The output is like an extra virtual output at the end of the transaction. 1377 Index: uint32(len(txn.TxOutputs)), 1378 } 1379 utxoEntry := UtxoEntry{ 1380 AmountNanos: desoAfterFeesNanos, 1381 PublicKey: txn.PublicKey, 1382 BlockHeight: blockHeight, 1383 UtxoType: UtxoTypeCreatorCoinSale, 1384 UtxoKey: &outputKey, 1385 // We leave the position unset and isSpent to false by default. 1386 // The position will be set in the call to _addUtxo. 1387 } 1388 // If we have a problem adding this utxo return an error but don't 1389 // mark this block as invalid since it's not a rule error and the block 1390 // could therefore benefit from being processed in the future. 1391 utxoOp, err := bav._addUtxo(&utxoEntry) 1392 if err != nil { 1393 return 0, 0, 0, nil, errors.Wrapf( 1394 err, "_connectBitcoinExchange: Problem adding output utxo") 1395 } 1396 1397 // Rosetta uses this UtxoOperation to provide INPUT amounts 1398 utxoOpsForTxn = append(utxoOpsForTxn, utxoOp) 1399 1400 // Compute the change in DESO locked. This information is needed by Rosetta 1401 // and it's much more efficient to compute it here than it is to recompute 1402 // it later. 1403 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 1404 return 0, 0, 0, nil, errors.Wrapf( 1405 err, "HelpConnectCreatorCoinSell: Error computing "+ 1406 "desoLockedNanosDiff: Missing profile") 1407 } 1408 desoLockedNanosDiff := int64(existingProfileEntry.DeSoLockedNanos) - int64(prevCoinEntry.DeSoLockedNanos) 1409 1410 // Add an operation to the list at the end indicating we've executed a 1411 // CreatorCoin txn. Save the previous state of the CoinEntry for easy 1412 // reversion during disconnect. 1413 utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ 1414 Type: OperationTypeCreatorCoin, 1415 PrevCoinEntry: &prevCoinEntry, 1416 PrevTransactorBalanceEntry: &prevTransactorBalanceEntry, 1417 PrevCreatorBalanceEntry: nil, 1418 CreatorCoinDESOLockedNanosDiff: desoLockedNanosDiff, 1419 }) 1420 1421 // The DeSo that the user gets from selling their creator coin counts 1422 // as both input and output in the transaction. 1423 return totalInput + desoAfterFeesNanos, 1424 totalOutput + desoAfterFeesNanos, 1425 desoAfterFeesNanos, utxoOpsForTxn, nil 1426 } 1427 1428 func (bav *UtxoView) _connectCreatorCoin( 1429 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 1430 _totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) { 1431 1432 // Check that the transaction has the right TxnType. 1433 if txn.TxnMeta.GetTxnType() != TxnTypeCreatorCoin { 1434 return 0, 0, nil, fmt.Errorf("_connectCreatorCoin: called with bad TxnType %s", 1435 txn.TxnMeta.GetTxnType().String()) 1436 } 1437 txMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1438 1439 // We save the previous CoinEntry so that we can revert things easily during a 1440 // disconnect. If we didn't do this, it would be annoying to reset the coin 1441 // state when reverting a transaction. 1442 switch txMeta.OperationType { 1443 case CreatorCoinOperationTypeBuy: 1444 // We don't need the creatorCoinsReturned return value 1445 totalInput, totalOutput, _, _, utxoOps, err := 1446 bav.HelpConnectCreatorCoinBuy(txn, txHash, blockHeight, verifySignatures) 1447 return totalInput, totalOutput, utxoOps, err 1448 1449 case CreatorCoinOperationTypeSell: 1450 // We don't need the desoReturned return value 1451 totalInput, totalOutput, _, utxoOps, err := 1452 bav.HelpConnectCreatorCoinSell(txn, txHash, blockHeight, verifySignatures) 1453 return totalInput, totalOutput, utxoOps, err 1454 1455 case CreatorCoinOperationTypeAddDeSo: 1456 return 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Add DeSo not implemented") 1457 } 1458 1459 return 0, 0, nil, fmt.Errorf("_connectCreatorCoin: Unrecognized CreatorCoin "+ 1460 "OperationType: %v", txMeta.OperationType) 1461 } 1462 1463 func (bav *UtxoView) _connectCreatorCoinTransfer( 1464 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 1465 _totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) { 1466 1467 // Check that the transaction has the right TxnType. 1468 if txn.TxnMeta.GetTxnType() != TxnTypeCreatorCoinTransfer { 1469 return 0, 0, nil, fmt.Errorf("_connectCreatorCoinTransfer: called with bad TxnType %s", 1470 txn.TxnMeta.GetTxnType().String()) 1471 } 1472 txMeta := txn.TxnMeta.(*CreatorCoinTransferMetadataa) 1473 1474 // Connect basic txn to get the total input and the total output without 1475 // considering the transaction metadata. 1476 totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer( 1477 txn, txHash, blockHeight, verifySignatures) 1478 if err != nil { 1479 return 0, 0, nil, errors.Wrapf(err, "_connectCreatorCoin: ") 1480 } 1481 1482 // Force the input to be non-zero so that we can prevent replay attacks. If 1483 // we didn't do this then someone could replay your transfer over and over again 1484 // to force-convert all your creator coin into DeSo. Think about it. 1485 if totalInput == 0 { 1486 return 0, 0, nil, RuleErrorCreatorCoinTransferRequiresNonZeroInput 1487 } 1488 1489 // At this point the inputs and outputs have been processed. Now we 1490 // need to handle the metadata. 1491 1492 // Check that the specified receiver public key is valid. 1493 if len(txMeta.ReceiverPublicKey) != btcec.PubKeyBytesLenCompressed { 1494 return 0, 0, nil, RuleErrorCreatorCoinTransferInvalidReceiverPubKeySize 1495 } 1496 1497 // Check that the sender and receiver public keys are different. 1498 if reflect.DeepEqual(txn.PublicKey, txMeta.ReceiverPublicKey) { 1499 return 0, 0, nil, RuleErrorCreatorCoinTransferCannotTransferToSelf 1500 } 1501 1502 // Check that the specified profile public key is valid and that a profile 1503 // corresponding to that public key exists. 1504 if len(txMeta.ProfilePublicKey) != btcec.PubKeyBytesLenCompressed { 1505 return 0, 0, nil, RuleErrorCreatorCoinTransferInvalidProfilePubKeySize 1506 } 1507 1508 // Dig up the profile. It must exist for the user to be able to transfer its coin. 1509 existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.ProfilePublicKey) 1510 if existingProfileEntry == nil || existingProfileEntry.isDeleted { 1511 return 0, 0, nil, errors.Wrapf( 1512 RuleErrorCreatorCoinTransferOnNonexistentProfile, 1513 "_connectCreatorCoin: Profile pub key: %v %v", 1514 PkToStringMainnet(txMeta.ProfilePublicKey), PkToStringTestnet(txMeta.ProfilePublicKey)) 1515 } 1516 1517 // At this point we are confident that we have a profile that 1518 // exists that corresponds to the profile public key the user provided. 1519 1520 // Look up a BalanceEntry for the sender. If it doesn't exist then the sender implicitly 1521 // has a balance of zero coins, and so the transfer shouldn't be allowed. 1522 senderBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 1523 txn.PublicKey, existingProfileEntry.PublicKey) 1524 if senderBalanceEntry == nil || senderBalanceEntry.isDeleted { 1525 return 0, 0, nil, RuleErrorCreatorCoinTransferBalanceEntryDoesNotExist 1526 } 1527 1528 // Check that the amount of creator coin being transferred is not less than the min threshold. 1529 if txMeta.CreatorCoinToTransferNanos < bav.Params.CreatorCoinAutoSellThresholdNanos { 1530 return 0, 0, nil, RuleErrorCreatorCoinTransferMustBeGreaterThanMinThreshold 1531 } 1532 1533 // Check that the amount of creator coin being transferred does not exceed the user's 1534 // balance of this particular creator coin. 1535 if txMeta.CreatorCoinToTransferNanos > senderBalanceEntry.BalanceNanos { 1536 return 0, 0, nil, errors.Wrapf( 1537 RuleErrorCreatorCoinTransferInsufficientCoins, 1538 "_connectCreatorCoin: CreatorCoin nanos being transferred %v exceeds "+ 1539 "user's creator coin balance %v", 1540 txMeta.CreatorCoinToTransferNanos, senderBalanceEntry.BalanceNanos) 1541 } 1542 1543 // Now that we have validated this transaction, let's build the new BalanceEntry state. 1544 1545 // Look up a BalanceEntry for the receiver. 1546 receiverBalanceEntry, _, _ := bav.GetBalanceEntryForHODLerPubKeyAndCreatorPubKey( 1547 txMeta.ReceiverPublicKey, txMeta.ProfilePublicKey) 1548 1549 // Save the receiver's balance if it is non-nil. 1550 var prevReceiverBalanceEntry *BalanceEntry 1551 if receiverBalanceEntry != nil { 1552 prevReceiverBalanceEntry = &BalanceEntry{} 1553 *prevReceiverBalanceEntry = *receiverBalanceEntry 1554 } 1555 1556 // If the receiver's balance entry is nil, we need to make one. 1557 if receiverBalanceEntry == nil || receiverBalanceEntry.isDeleted { 1558 receiverPKID := bav.GetPKIDForPublicKey(txMeta.ReceiverPublicKey) 1559 creatorPKID := bav.GetPKIDForPublicKey(existingProfileEntry.PublicKey) 1560 // Sanity check that we found a PKID entry for these pub keys (should never fail). 1561 if receiverPKID == nil || receiverPKID.isDeleted || creatorPKID == nil || creatorPKID.isDeleted { 1562 return 0, 0, nil, fmt.Errorf( 1563 "_connectCreatorCoin: Found nil or deleted PKID for receiver or creator, this should never "+ 1564 "happen. Receiver pubkey: %v, creator pubkey: %v", 1565 PkToStringMainnet(txMeta.ReceiverPublicKey), 1566 PkToStringMainnet(existingProfileEntry.PublicKey)) 1567 } 1568 receiverBalanceEntry = &BalanceEntry{ 1569 HODLerPKID: receiverPKID.PKID, 1570 CreatorPKID: creatorPKID.PKID, 1571 BalanceNanos: uint64(0), 1572 } 1573 } 1574 1575 // Save the sender's balance before we modify it. 1576 prevSenderBalanceEntry := *senderBalanceEntry 1577 1578 // Subtract the number of coins being given from the sender and add them to the receiver. 1579 // TODO: We should avoid editing the pointer returned by "bav._getX" directly before 1580 // deleting / setting. Since the pointer returned is the one held by the view, it 1581 // makes setting redundant. An alternative would be to not call _set after modification. 1582 senderBalanceEntry.BalanceNanos -= txMeta.CreatorCoinToTransferNanos 1583 receiverBalanceEntry.BalanceNanos += txMeta.CreatorCoinToTransferNanos 1584 1585 // We do not allow accounts to maintain tiny creator coin balances in order to avoid 1586 // Bancor curve price anomalies as famously demonstrated by @salomon. Thus, if the 1587 // sender tries to make a transfer that will leave them below the threshold we give 1588 // their remaining balance to the receiver in order to zero them out. 1589 if senderBalanceEntry.BalanceNanos < bav.Params.CreatorCoinAutoSellThresholdNanos { 1590 receiverBalanceEntry.BalanceNanos += senderBalanceEntry.BalanceNanos 1591 senderBalanceEntry.BalanceNanos = 0 1592 senderBalanceEntry.HasPurchased = false 1593 } 1594 1595 // Delete the sender's balance entry under the assumption that the sender gave away all 1596 // of their coins. We add it back later, if this is not the case. 1597 bav._deleteBalanceEntryMappings(senderBalanceEntry, txn.PublicKey, txMeta.ProfilePublicKey) 1598 // Delete the receiver's balance entry just to be safe. Added back immediately after. 1599 bav._deleteBalanceEntryMappings( 1600 receiverBalanceEntry, txMeta.ReceiverPublicKey, txMeta.ProfilePublicKey) 1601 1602 bav._setBalanceEntryMappings(receiverBalanceEntry) 1603 if senderBalanceEntry.BalanceNanos > 0 { 1604 bav._setBalanceEntryMappings(senderBalanceEntry) 1605 } 1606 1607 // Save all the old values from the CoinEntry before we potentially update them. Note 1608 // that CoinEntry doesn't contain any pointers and so a direct copy is OK. 1609 prevCoinEntry := existingProfileEntry.CoinEntry 1610 1611 if prevReceiverBalanceEntry == nil || prevReceiverBalanceEntry.BalanceNanos == 0 { 1612 // The receiver did not have a BalanceEntry before. Increment num holders. 1613 existingProfileEntry.CoinEntry.NumberOfHolders++ 1614 } 1615 1616 if senderBalanceEntry.BalanceNanos == 0 { 1617 // The sender no longer holds any of this creator's coin, so we decrement num holders. 1618 existingProfileEntry.CoinEntry.NumberOfHolders-- 1619 } 1620 1621 // Update and set the new profile entry. 1622 bav._setProfileEntryMappings(existingProfileEntry) 1623 1624 // If this creator coin transfer has diamonds, validate them and do the connection. 1625 diamondPostHashBytes, hasDiamondPostHash := txn.ExtraData[DiamondPostHashKey] 1626 diamondPostHash := &BlockHash{} 1627 diamondLevelBytes, hasDiamondLevel := txn.ExtraData[DiamondLevelKey] 1628 var previousDiamondPostEntry *PostEntry 1629 var previousDiamondEntry *DiamondEntry 1630 // After the DeSoDiamondsBlockHeight, we no longer accept creator coin diamonds. 1631 if hasDiamondPostHash && blockHeight > DeSoDiamondsBlockHeight { 1632 return 0, 0, nil, RuleErrorCreatorCoinTransferHasDiamondsAfterDeSoBlockHeight 1633 } else if hasDiamondPostHash { 1634 if !hasDiamondLevel { 1635 return 0, 0, nil, RuleErrorCreatorCoinTransferHasDiamondPostHashWithoutDiamondLevel 1636 } 1637 diamondLevel, bytesRead := Varint(diamondLevelBytes) 1638 // NOTE: Despite being an int, diamondLevel is required to be non-negative. This 1639 // is useful for sorting our dbkeys by diamondLevel. 1640 if bytesRead < 0 || diamondLevel < 0 { 1641 return 0, 0, nil, RuleErrorCreatorCoinTransferHasInvalidDiamondLevel 1642 } 1643 1644 if !reflect.DeepEqual(txn.PublicKey, existingProfileEntry.PublicKey) { 1645 return 0, 0, nil, RuleErrorCreatorCoinTransferCantSendDiamondsForOtherProfiles 1646 } 1647 if reflect.DeepEqual(txMeta.ReceiverPublicKey, existingProfileEntry.PublicKey) { 1648 return 0, 0, nil, RuleErrorCreatorCoinTransferCantDiamondYourself 1649 } 1650 1651 if len(diamondPostHashBytes) != HashSizeBytes { 1652 return 0, 0, nil, errors.Wrapf( 1653 RuleErrorCreatorCoinTransferInvalidLengthForPostHashBytes, 1654 "_connectCreatorCoin: DiamondPostHashBytes length: %d", len(diamondPostHashBytes)) 1655 } 1656 copy(diamondPostHash[:], diamondPostHashBytes[:]) 1657 1658 previousDiamondPostEntry = bav.GetPostEntryForPostHash(diamondPostHash) 1659 if previousDiamondPostEntry == nil || previousDiamondPostEntry.isDeleted { 1660 return 0, 0, nil, RuleErrorCreatorCoinTransferDiamondPostEntryDoesNotExist 1661 } 1662 1663 expectedCreatorCoinNanosToTransfer, netNewDiamonds, err := bav.ValidateDiamondsAndGetNumCreatorCoinNanos( 1664 txn.PublicKey, txMeta.ReceiverPublicKey, diamondPostHash, diamondLevel, blockHeight) 1665 if err != nil { 1666 return 0, 0, nil, errors.Wrapf(err, "_connectCreatorCoin: ") 1667 } 1668 1669 if txMeta.CreatorCoinToTransferNanos < expectedCreatorCoinNanosToTransfer { 1670 return 0, 0, nil, RuleErrorCreatorCoinTransferInsufficientCreatorCoinsForDiamondLevel 1671 } 1672 1673 // The diamondPostEntry needs to be updated with the number of new diamonds. 1674 // We make a copy to avoid issues with disconnecting. 1675 newDiamondPostEntry := &PostEntry{} 1676 *newDiamondPostEntry = *previousDiamondPostEntry 1677 newDiamondPostEntry.DiamondCount += uint64(netNewDiamonds) 1678 bav._setPostEntryMappings(newDiamondPostEntry) 1679 1680 // Convert pub keys into PKIDs so we can make the DiamondEntry. 1681 senderPKID := bav.GetPKIDForPublicKey(txn.PublicKey) 1682 receiverPKID := bav.GetPKIDForPublicKey(txMeta.ReceiverPublicKey) 1683 1684 // Create a new DiamondEntry 1685 newDiamondEntry := &DiamondEntry{ 1686 SenderPKID: senderPKID.PKID, 1687 ReceiverPKID: receiverPKID.PKID, 1688 DiamondPostHash: diamondPostHash, 1689 DiamondLevel: diamondLevel, 1690 } 1691 1692 // Save the old DiamondEntry 1693 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 1694 existingDiamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 1695 // Save the existing DiamondEntry, if it exists, so we can disconnect 1696 if existingDiamondEntry != nil { 1697 dd := &DiamondEntry{} 1698 *dd = *existingDiamondEntry 1699 previousDiamondEntry = dd 1700 } 1701 1702 // Now set the diamond entry mappings on the view so they are flushed to the DB. 1703 bav._setDiamondEntryMappings(newDiamondEntry) 1704 } 1705 1706 // Add an operation to the list at the end indicating we've executed a 1707 // CreatorCoin txn. Save the previous state of the CoinEntry for easy 1708 // reversion during disconnect. 1709 utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ 1710 Type: OperationTypeCreatorCoinTransfer, 1711 PrevSenderBalanceEntry: &prevSenderBalanceEntry, 1712 PrevReceiverBalanceEntry: prevReceiverBalanceEntry, 1713 PrevCoinEntry: &prevCoinEntry, 1714 PrevPostEntry: previousDiamondPostEntry, 1715 PrevDiamondEntry: previousDiamondEntry, 1716 }) 1717 1718 return totalInput, totalOutput, utxoOpsForTxn, nil 1719 }