Geo-Web-Project / specs

1 stars 1 forks source link

Tax collection #10

Open codynhat opened 3 years ago

codynhat commented 3 years ago

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.

codynhat commented 3 years ago

Assign this to @CodinMaster since the conversation started on Miro.

codynhat commented 3 years ago

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:

Global State

- Data stored in a single, global state

Per-Parcel State

- State stored for each land parcel

State Changes

- 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:

Global State

totalSelfAssessedValues ($)
taxRate (%)
currentReserveBalance ($)
lastTaxCollectionDate

Per-Parcel State

expirationDate
selfAssessedValue ($)

State Changes

# 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
codynhat commented 3 years ago

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.

codynhat commented 3 years ago

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
codynhat commented 3 years ago

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:

  1. Tax collection scales linearly with number of land parcels
  2. Tax collection scales linearly with number of expired land parcels yet to be foreclosed
    • This is a subset of all land parcels, which means it still scales linearly with the number of land parcels
    • However, the foreclosure mechanism could be designed to keep this a constant. For example, auction starting prices decrease exponentially as the number of expired land parcels increases
  3. Reserves are too low (shown above)
    • Could result in land owners not being able to withdraw their unpaid taxes in a "bank run" type scenario
  4. Reserves are too high
    • Simple approach is to require a year's worth of tax from all parcels to remain in reserve and not allow more than a year of tax to be paid ahead of time
    • Severely limits the amount of tax that can be collected
  5. There are no reserves
    • Taxes yet to be paid must be paid by the next owner at time of sale
    • Also a potential opportunity for our own token. Short summary: if payments are made in a native token, instead of deposits + withdrawals being made to a reserve, minting + burning can be made to a supply of tokens
codynhat commented 3 years ago

@CodinMaster @gravenp Let me know your thoughts from above! Sorry, a lot of info at once.

gravenp commented 3 years ago

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:

codynhat commented 3 years ago

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).

codynhat commented 3 years ago

@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.

codynhat commented 3 years ago

Thinking more about an auction.

Foreclosing

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".

Tax collection

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.

gravenp commented 3 years ago
codynhat commented 3 years ago
codynhat commented 3 years ago

Here is what we are leaning toward after discussing offline: