Open codynhat opened 3 years ago
Assign this to @CodinMaster since the conversation started on Miro.
Let's keep the conversation going about tax collection here. I think we are getting much closer.
As an attempt to help us communicate about solutions more clearly, let's use this framework:
- Data stored in a single, global state
- State stored for each land parcel
- Logic for making changes to the above state
This obviously maps directly to how smart contracts are built (really computers in general I suppose). State changes must be triggered by some transaction.
Taking a first crack at fitting our latest design in this framework:
totalSelfAssessedValues ($)
taxRate (%)
currentReserveBalance ($)
lastTaxCollectionDate
expirationDate
selfAssessedValue ($)
# Land owner deposits more taxes
depositTaxesForParcel(parcel, amount):
# Update land parcel state
taxesPerPeriod <- parcel.selfAssessedValue * taxRate
depositPeriodValue <- amount / taxesPerPeriod
parcel.expirationDate += depositPeriodValue
# Update global state
taxesAccrued <- totalSelfAssessedValues * taxRate * (now - lastTaxCollectionDate)
newReserveBalance <- currentReserveBalance + amount - taxesAccrued
currentReserveBalance <- newReserveBalance
lastTaxCollectionDate <- now
# Land owner withdraws taxes that have not accrued
withdrawTaxesForParcel(parcel , amount):
# Update land parcel state
taxesPerPeriod <- parcel.selfAssessedValue * taxRate
withdrawPeriodValue <- amount / taxesPerPeriod
parcel.expirationDate -= withdrawPeriodValue
require(parcel.expirationDate > now)
# Update global state
taxesAccrued <- totalSelfAssessedValues * taxRate * (now - lastTaxCollectionDate)
newReserveBalance <- currentReserveBalance - amount - taxesAccrued
currentReserveBalance <- newReserveBalance
lastTaxCollectionDate <- now
# Self assessed value is changed
updateSelfAssessedValue(parcel, newValue):
# Update global state
taxesAccrued <- totalSelfAssessedValues * taxRate * (now - lastTaxCollectionDate)
newReserveBalance <- currentReserveBalance - taxesAccrued
currentReserveBalance <- newReserveBalance
lastTaxCollectionDate <- now
totalSelfAssessedValues -= parcel.selfAssessedValue
totalSelfAssessedValues += newValue
# Update land parcel state
taxesPerPeriod <- parcel.selfAssessedValue * taxRate
currentParcelReserve <- taxesPerPeriod * (parcel.expirationDate - now)
newTaxesPerPeriod <- newValue * taxRate
parcel.expirationDate <- now + (currentParcelReserve / newTaxesPerPeriod)
require(parcel.expirationDate > now)
parcel.selfAssessedValue <- newValue
Well that turned into a lot of pseudocode.
The main issue with this approach is foreclosures. Currently, if land passes its expiration date, taxes will continue to be collected from the land even though nobody is paying them, resulting in an out-of-balance reserve.
Here's a generalized version of the state changes:
# Re-calculate global state any time one of the inputs change:
updateGlobalState(netTaxDeposits, selfAssessedValueChange):
taxesAccrued <- totalSelfAssessedValues * taxRate * (now - lastTaxCollectionDate)
newReserveBalance <- currentReserveBalance - taxesAccrued + netTaxDeposits
currentReserveBalance <- newReserveBalance
lastTaxCollectionDate <- now
totalSelfAssessedValues += selfAssessedValueChange
# Re-calculate parcel state any time one of the inputs change
updateParcelState(parcel, netTaxDeposits, selfAssessedValueChange):
taxesPerPeriod <- parcel.selfAssessedValue * taxRate
currentParcelReserve <- taxesPerPeriod * (parcel.expirationDate - now)
newTaxesPerPeriod <- (parcel.selfAssessedValue + selfAssessedValueChange) * taxRate
netDepositsPeriodValue <- netTaxDeposits / newTaxesPerPeriod
parcel.expirationDate <- now + (currentParcelReserve / newTaxesPerPeriod) + netDepositsPeriodValue
parcel.selfAssessedValue += selfAssessedValueChange
The issue here is taxesAccrued
will continue to include taxes even from parcels that have expired, resulting in a reserve that is too low.
I started looking into using a global expiration date approach as well, but there is still the same problem. This is full of tradeoffs and I see five where we must choose one:
@CodinMaster @gravenp Let me know your thoughts from above! Sorry, a lot of info at once.
Good work @codynhat! I'll jump in with my non-engineer, high-level thoughts, so please excuse the lack of CS precision 😃. No expectations for a line-for-line response, but maybe this can populate some potential paths forward:
https://www.youtube.com/watch?v=LYm-cVuo0sg
Good reference for how WildCards handles tax collection.
To map this to the model above, I believe they are choosing 4 (but a week instead of a year in my example).
@gravenp I agree that this depends a lot on how we want to handle foreclosures. In particular, choosing 2 and designing the foreclosure mechanism in a way that keeps the number of expired parcels a constant.
If that were the case, all expired parcels could be processed in the smart contract before collecting taxes, ensuring no taxes are accrued for expired parcels.
Thinking more about an auction.
If you think about it, a Harberger tax is basically a never-ending auction. What are we trying to make more fair? I think what we want is to ensure many players have enough time to place their self-assessed value on a piece of unclaimed land before the rights are immediately given to someone else.
Another piece is about the network recapturing value from foreclosed land. Is this what we want? This is separate than making land claims "fair".
I'm not sure what is meant by a "repo man" contract. But the problem is we need triggers to be done for expired land in order to fix the accrued tax. We are saying we don't want these triggers done by a central org, whether in batches or one-by-one.
The WildCards solution essentially gives a one week buffer on foreclosed land. Meaning the org cannot collect the mistakenly accrued taxes of expired land until one week passes. But after this week, they are ok with the org "stealing" these funds because it is enough time for the community to catch it by foreclosing. I think our concerns are a little different. Can we rely on 100% of expired land to be foreclosed within a week? No. And we also never want the reserves to get too low. So whatever land is still not foreclosed must be manually foreclosed by the org. This buffer could help decrease the % of land that is waiting to be foreclosed, but it may still be linear. Unless we come up with a clever way to have this be constant, which means the % of land waiting to be foreclosed decreases linearly with the number of land parcels.
An example of this could be to make the starting auction price a factor of the current % of expired land. The more land that is expired, the lower the starting price. This may result in more foreclosures and lowering the % to some equilibrium.
Here is what we are leaning toward after discussing offline:
Tax "collection" should be done in a scalable way. Specifically, the tax collection should be a constant-time operation that does not scale with the number of land parcels.
This may mean work is amortized among land owners, for example.