celo-org / celo-monorepo

Official repository for core projects comprising the Celo platform
https://celo.org
Apache License 2.0
684 stars 360 forks source link

Genesis accounts SBAT lock Gold and receive funds only on a vesting schedule #800

Closed timmoreton closed 4 years ago

timmoreton commented 4 years ago

Expected Behavior

We want to be able to include in the genesis block a contract (call it VestingFactory)

While vesting, funds should be able to be locked using the LockedGold contract so that they can be used for voting for validators, in on-chain governance, and earn compound rewards. To do this, VestingFactory might have a function createVesting() that creates a new contract, owned by a specified beneficiary, that exposes wrappers for the public functions of LockedGold and Accounts.

The beneficiary will be able to call LockedGold.lock to lock up gold, and Accounts.authorize*() to specify keys that can be used for other governance actions (e.g. voting, running a validator). At any time, the beneficiary may unlock locked gold to return it to the vesting contract. Gold in the vesting contract may be transferred to the beneficiary pursuant to the vesting contract's Schedule.

Note that we may not need to support wrappers around Validators, Governance, and Election, since the beneficiary will be able to authorize other keys that can perform those actions.

The Schedule will be composed of the following parameters:

The user can withdraw VestingInstance.balance * (vestAmountPerPeriod * ((now - vestingStartTime)/vestPeriodSec)) / totalAmount, provided now > vestingCliff + vestingStartTime. This allows users to withdraw their rewards pro rata according to the schedule, and supports continuous (e.g. monthly) vesting.

The data in VestingFactory will need to comprise a map: address -> Schedule such that for a given address we have:

As we go we'll also need to track:

Once the original vesting schedule would be complete, i.e vestAmountPerPeriod*(now-vestingStartTime)/vestPeriodSec>=totalAmount, all funds may be withdrawn.

Further requirement for revocation: the Celo Foundation may need to revoke vesting schedule instances. Each Schedule should be either revocable or not.

VestingFactory should also take two parameters:

To revoke a schedule that hasn't started, the VestingFactory should support a revoke(vestingEndTime) function. Can only be called once. This should have no effect if address has a Schedule that is revokable=false. Otherwise, if they have not called 'vesting:start', then set vestingRevokedFrom and calculate the portion to return to transfer to refundDestination. All rewards are retained by the user. The vesting instance contracts should support a similar function.

We want support for this contract in ContractKit, and in the CLI, as a new set of methods, vesting:authorize, vesting:lock, vesting:unlock, etc.

Requirement for pause:

If a grant is revokable, then the revoker can also pause withdrawals for a certain period, up to 365 days. This does not affect the underlying timing of vesting, it only affects whether withdrawals can be made. We would need something like a pause(seconds) method only callable by revoker, then on withdraw check that a pause is not currently in place.

Current Behavior

No support for this.

Perseverance commented 4 years ago

Some notes based on latest call.

Roles

We will have three main roles:

asaj commented 4 years ago

Thanks @Perseverance, that all looks good to me. I've made some changes to the initial spec after reading it more carefully. Most notably:

Please take a look at the updated spec and let me know if any of the changes don't make sense

m-chrzan commented 4 years ago

Should we create the VestingInstances as proxied contracts, with the revoker being able to update the proxy? These VestingInstances we create might be holding a lot of Celo Gold, so any bugs would be great cause for concern.

mcortesi commented 4 years ago

Spec Amends

  1. Instead of revoke(timestamp) we have revoke() which revokes now. On revoking, the contract will record the owed amount for the beneficiary that he can later withdraw.

  2. After revoking the beneficiary should only be able to call withdraw and the revoke should be able to call the other methods to interact with LockedGold and Accounts

  3. After revoking, the revoker is responsible for calling the necessary revokeVote, unlock, withdrawLockedGold combinations to actually unlock the locked gold and transfer it to the VestingInstance.

  4. After revoking, the beneficiary can call withdraw to extract what it's owed to him. It will only be able to withdraw whatever it's owed and it's currently in the contract balance. It can make many calls. To extract the full amount it will have to wait for locked gold to be transfered to the contract such that contract balance >= owed gold

  5. When calling revoke, no funds are transfered

  6. After revoking, and after all the locked gold has been transfered to the contract, the revoker can call refundAndFinalize() which will (a) transfer remaining funds to revoker (b) transfer remaining owed money to the beneficiary (c) destroy the vesting instance contract

  7. If the contract is never revoked, then when the beneficiary withdraws all the gold from the vesting schedule, the call should also destroy the vesting instance contract.