Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation #50

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docs
*.md
3 changes: 3 additions & 0 deletions contracts/Mocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Mocks

This directory contains mocks of contracts used for testing purposes.
13 changes: 13 additions & 0 deletions contracts/Mocks/Upgrades/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Upgrades

This directory contrains **upgrade test** mock contracts.

The contracts placed here **must** follow a pattern (in order to be properly picked up by the upgrade test harness):

- Test contract names **must** match an existing upgradeable contract **exactly**
- Test contract names **must** end in `V2`
- eg. `Foo` -> `FooV2`
- Test contracts **must** imlement a `checkUpgrade()` function that **must** return at least a confitrmation string (default expected value being `"OK"`)
- Test contractss **should** reference all state in the contract, in the order is declared in the contract (including base contracts)

If properly implemented, the test harness will be able to check if the upgraded contract retains state integrity after the upgrade.
121 changes: 121 additions & 0 deletions contracts/TokenDistro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# TokenDistro Contract

This directory contains the `TokenDistro` contract. The `TokenDistro` contract handles the allocation of rewards by releasing them over time in the **GIVStream**.

The contract is based on the original `TokenDistro` contracts initially provided by Dappnode.

## Initialization

During intialization the TokenDistro defines the follwing:
- The token that is being distributed
- Start time, cliff period and duration of the distribution
- Initial percentage available to claim
- If the admin can cancel an allocation

The `msg.sender` assumes the inital `DEFAULT_ADMIN_ROLE` role used in the contract.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be rephrased.


## Acess Control

The Token Distro contract utilizes Openzeppelin `AccessControl` roles. Two roles are recognized:

1. `DEFAULT_ADMIN_ROLE` is essentally the owner of the TokenDistro. It is assigned to the initializer address. `DEFAULT_ADMIN_ROLE` can add **distributors**, set the start time and cancel allocations.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And assign tokens (add credit) to distributors.

2. `DISTRIBUTOR_ROLE` are the only addresses allowed to allocate tokens for distribution through claims. This should usually be an address of a liquidity mining contract such as eg. `Unipool` or similar.

## Assigning Distributors

TokenDistro assumes that there exist distributors (contracts or EOAs) that will later be used to distribute tokens. There are for example:
- The UniV3 staking incentive program
- The `UnipoolDistributor` used for liquidity mining on Honeyswap an Balancer
paxthemax marked this conversation as resolved.
Show resolved Hide resolved
- The `GardenUnipoolDistirbutor` used for GIVGarden staking

The `assign(address account, uint256 amount)` function will assign the given amount of tokens to an address. It must be caled by the `DEFAULT_ADMIN_ROLE`. The `DEFAULT_ADMIN_ROLE` may assign any address to be a Distributor if it has claimed a `DISTRIBUTOR` ROLE.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

## Allocating/claiming GIV

When a recipient wants to claim her tokens, she is expected to interact with a deployed distributor contract. The logic of the distributor governshe how much the recipient is owed and at what time.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

In an example, a deployed `MerkleDistributor` checks if an address has a GIV merkle drop available.

**IMPORTANT:** A distributor cannot allocate GIV to another `DISTRIBUTOR_ROLE`. This is to avoid tokens being trapped in a distribution contract.

### Allocating GIV

Before (or in the process of) claiming rewards, a registered distributor should call `allocate(address account, uint256 amount, bool claim)` with the address and the amount of GIV the recipient is entitled to. This will allocate the amount to the recipient where
- One part can be claimed immediately as GIV
- The rest is allocated to the GIVStream and is released over time

The proportion of GIV that can be claimed is determined by the
- Initial percentage of GIV that is set as claimable
- Current block time (percentage of duration that is elapsed)
- Amount of tokens already claimed

The globally claimable amount of tokens is:
```
/**
* Function to get the total claimable tokens at some moment
* @param timestamp Unix time to check the number of tokens claimable
* @return Number of tokens claimable at that timestamp
*/
function globallyClaimableAt(uint256 timestamp)
public
view
override
returns (uint256)
{
if (timestamp < startTime) return 0;
if (timestamp < cliffTime) return initialAmount;
if (timestamp > startTime + duration) return totalTokens;

uint256 deltaTime = timestamp - startTime;
return initialAmount + (deltaTime * lockedAmount) / duration;
}
```

This determines the current unlocked amount of allocated tokens (at a given timestamp) as:
```
uint256 unlockedAmount = (globallyClaimableAt(timestamp) *
balances[recipient].allocatedTokens) / totalTokens;

return unlockedAmount - balances[recipient].claimed;
```

Only a registered distributor can assign tokens to be claimed to an address.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

NOTE: If the claim flag is set to `true` the distributor will also perform a claim (as seen in the *Claiming GIV* section).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth to mention each distributor has a limited balance to allocate. The distributor balance can be increased by the assign function

### SendGIVbacks wrapper

The `sendGIVbacks(address[] memory recipients, uint256[] memory amounts)` is a wrapper function that will allocate an amount of GIV tokens to a given number of recipients. It uses the underlying `_allocateMany` call. It will emit a `GivBacksPaid(address)` event that logs the function caller.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

This is useful for tracking GIVBack payouts that are tracked off-chain and triggered on a regular basis.

### Claiming GIV

Anyone can initialte a claim by calling `claim` or `claimTo` functions. This will transfer any tokens that are available to be claimed to the recipient.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

This way the recipient can access her GIVStream tokens regardless of the distributor.

## Changing adress allocations

The TokenDistro contract supports modifying the recipient of an allocation. This can be very useful if an address is compromised (invalid, lost keys etc.) This can be done by the recipient or (if enabled) by `DEFAULT_ADMIN_ROLE`.

### Regular mode

Any recipient can change the address to which she receives GIV allocations by calling `changeAddress(address newAddress)`. The new address **must not** be **in use**, that is it must not have any allocated (or claimed) tokens.

This call emits a `ChangeAddress(address prevRecipient, address newRecipient)` event.

**NOTE:** The `DISTRIBUTOR_ROLE` **cannot** change it's recipient address.

When a recipient changes her address, she will be able to claim the remainder of her tokens from existing allocations.

### Admin mode

The `DEFAULT_ADMIN_ROLE` has an option to call the `cancelAllocation(address prevRecipient, address newRecipient)` function to change a recipient by force, buy only if the `cancellable` parameter was set to `true` on TokenDistro initialization.

This call emits a `ChangeAddress(address prevRecipient, address newRecipient)` event.

**NOTE:** The `DISTRIBUTOR_ROLE` recipient address **cannot** be changed.



85 changes: 85 additions & 0 deletions contracts/Tokens/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Token Contracts

This directory contains different **token contracts** used by the GIVEconomy. The contracts here are mainly used for reference and testing purposes, with the execption of the `UniswapV3RewardToken`.

## UniswapV3 Reward Token

This token is used as a bridge between `UniswapV3Staker` contract which handles the liquidity mining on Uniswap V3 and the `TokenDistro`.

We must use this bridging token because the UniV3 incentive has no way to call `TokenDistro.allocate` when rewards are claimed. If the rewards in GIV are claimed directly from UniV3, there would be no way to enforce that the correct proportion of the claim is placed in the GIVStream.

To accomplish this, the reward token contract maintains a fake balance that represent GIV that should be assigned and claimed from the TokenDistro. When a recipient withdraws rewards from the UniV3 incentive contract, the appropriate amount of reward tokens is "burned" and real GIV is allocated to the recipient via `TokenDistro.allocate`.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

The token implements the ERC-20 standard in such a way to **only** allow interactions between the TokenDistro, GIV token and the UniV3 staker contract.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

The contract implements `ownable` and is owned by the deployer address.

### The `approve` and `allowance` functions

The UniV3 incentive contract is the only account that is allowed to call transfer from, and it has `MAX_UINT256` allowance.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved
Approve is unused, but for interface compatibility always returns `true`.

### The `transferFrom` function

This function is called by the `UniV3Staker` contract when creating a new incentive:
```
function createIncentive(IncentiveKey memory key, uint256 reward) external override {
...

TransferHelper.safeTransferFrom(
address(key.rewardToken),
msg.sender,
address(this),
reward
);

...
}
```

The `transferFrom` call will only succeed if:
- Is called by the UniV3 staker contract
- Is `from` the contract `owner`
- Is `to` the the UniV3 staker contract

For the `createIncentive` to succeed, the caller **must** be the `owner` of the contract.

The transferFrom will "mint" the tokens that can later be allocated in the TokenDistro by calling transfer.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

### The `transfer` function

This function is called by the `UniV3Staker` when a recipient claims rewards:
```
function claimReward(
IERC20Minimal rewardToken,
address to,
uint256 amountRequested
) external override returns (uint256 reward) {
...

TransferHelper.safeTransfer(address(rewardToken), to, reward);

...
}
```
To succeed, `transfer` **must** be called by the UniV3 staker contract.

When called, this function will "burn" the claimed amount of reward tokens and then call `TokenDistro.allocate`. The `allocate` call will allocate actual GIV tokens and perform a claim.
paxthemax marked this conversation as resolved.
Show resolved Hide resolved

This function emits a `RewardPaid(uint256 amount, address to)` event.

## GIV Token

This is the GIV token contract that is deployed to both Ethereum mainnet and Gnosis chain.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the GIV token only deployed on Mainnet. The Genosis chain one is created by bridge!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I almost missed that part.


GIV is a lightweight ERC-20 token modeled after the [uniswap implementation](https://github.com/Uniswap/v2-core/blob/v1.0.1/contracts/UniswapV2ERC20.sol) with some modifications:

- It has exposed `mint` and `burn` functions
- With associated `minter` role
- Total supply is uncapped
- [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `transferWithAuthorization`
- `DOMAIN_SEPARATOR` is computed inside `_validateSignedData` to avoid reply-attacks due to Hardforks
- Forbids transfers to the contract and address(0)
- This is to avoid losing tokens in the contract and to enforce burn events

**NOTE:** The actual GIV token is not meant to be deployed from the GIVEconomy repo, and was deployed separately before mainnet launch.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GIV Token on mainnet is deployed by the current version ofGIV.sol. @paxthemax is it what you mean?

9 changes: 9 additions & 0 deletions contracts/UniswapV3Staker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Uniswap V3 Staker Contracts

This is the cannonical Uniswap V3 Staker staker contract, sourced directly from the [uniswap repo](https://github.com/Uniswap/v3-staker/tree/main/contracts).

The contract source is not modified in any way, and is only included
- To provide bytecode and ABI to reference during deployment
- To use in testing

Uniswap V3Staker is deployed to Ethereum **mainnet**.
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Documentation

This directory will contain **documentation** for the GivEconomy contracts.

See `NOTES.md` for a general overview of contrats.

TODO: add diagrams
Empty file added docs/assets/.gitkeep
Empty file.
Binary file added docs/assets/Merkle Distro claim.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions docs/src/MerkleDistroClaimSequene.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@startuml "Merkle Distro claim"

actor Recipient as caller
participant MerkleDistro as merkle
participant TokenDistro as distro
participant GIVToken as token

caller -> merkle : isClaimed(index)
activate merkle

merkle --> caller : bool
deactivate merkle

caller -> merkle : claim(index, amount, proof)
activate merkle

alt proof is invalid
merkle --> caller : revert
destroy merkle
else
merkle -> distro: allocate()

activate distro
note right: set allocation

alt claim
distro -> distro : claim()
activate distro

distro -> token: safeTransfer()
activate token

token --> caller: transfer tokens

token --> distro: OK
deactivate token
deactivate distro
end

distro --> merkle: OK
deactivate distro

merkle -->caller : OK
deactivate merkle
end

@enduml