Skip to content

Commit

Permalink
add deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
balasan committed Sep 8, 2021
1 parent f080873 commit 48370b5
Show file tree
Hide file tree
Showing 22 changed files with 4,426 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .openzeppelin/unknown-31337.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
},
"3a3084e454b9bed5c2c691412b6705c071a84bc3edb7b7bccdc1fe284f49f0b7": {
"address": "0xF5e3F0e5F55812E22C67a8d2712Fa626296002B0",
"txHash": "0x7f68eab73821164159bec9f47aeb55cd4a7e1dca15ccc8de77fb5b04892e6b65",
"txHash": "0xeef1e4691f07b7389cb8fa4b2ff580471b50d7bb38dd67b77053b17e0f0288af",
"layout": {
"storage": [
{
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"solidity.compileUsingRemoteVersion": "v0.5.2+commit.1df8f40c"
"solidity.compileUsingRemoteVersion": "v0.5.2+commit.1df8f40c",
"solidity.enabledAsYouTypeCompilationErrorCheck": false
}
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,63 @@
# The Relevant Token
# The Relevant Token Smart Contracts

This repository contains the code for REL, an ERC20 token used by [Relevant](https://relevant.community) to reward content curation.
This repository contains smart contracts used by [Relevant APP](https://relevant.community) to reward content curation.

## Key Contract Parameters
## RelevantTokenV3.sol

Users of the Relevant App are able to earn rewards for curating tokens. These rewards are generated by minting new REL tokens. REL token `admin` distributes the tokens as balances in the web2 database of the Relevant App. Users can then `claim` these tokens by requesting an attestation by the `admin` account and submitting it the REL smart contract.

There is currently a limit to how many tokens can be claimed this way by any individual account. Balances over the limit can be claimed as vested `sRel` tokens. These tokens are not transferrable (for the most part) but can otherwise be used for governance and within the app.

### Key Contract Parameters

- `inflation` is the yearly APR in basis points.
- `allocatedRewards` are the tokens that still belong to the smart contract but are allocated to users as Curation rewards. These tokens can either be claimed or vested by the users.
- `admin` is a designated hot wallet address that signs user's token claims. Security note: if this account is compromised the attacker can drain the `allocatedRewards` but not more. This address should be rotate periodically by 'owner'.

## Key Methods
### Key Methods

- `releaseTokens` can be called by anyone to either mint or allocate existing tokens. If there are enough tokens in the smart contract that are not part of the `allocatedRewards`, they will be used, otherwise new tokens will be minted.
- `claimTokens` can be used by users to claim REL from `allocatedRewards`

## Owner Methods
### Owner Methods

- `vestAllocatedTokens` sends a portion of the `allocatedTokens` to a vesting smart contract. This method should be called bafore initializing any specific vesting account.
- `updateAllocatedRewards` updates `allocatedRewards` - we may need to do this to ensure app rewards match up with allocated rewards.
- `setInflation` sets yearly inflation rate in basis points.
- `setAdmin` updates the token claim signer.
- `burn` can burn an amount of tokens that are not part of the `allocatedRewards`. We might want to do this in the future to simplify accounting. Note: yearly inflation rate should be adjusted if a significant number of tokens is burned.

# Relevant Protocol Governance Smart Contracts

## sREL token

sREL is governance wrapper for REL tokens and allows staking and vesting.

### Roles

- `owner` (this will eventually be a DAO)
- can initialize initial vesting accounts
- can set `vestAdmin` role
- `vestAdmin` is designed to be a hotwallet that allows automated vesting initialization via the Relevant App

### Staking

- REL tokens can be staked via the contract in exchange for sRel
- sRel cannot be transferred or exchanged back to REL unless they are 'unlocked' (unstaked) and unvested
- `lockPeriod` deterimines the time it takes to unstake the tokens

### Vesting

- Vested tokens can be added by the `owner` of the contract or via a signature from the `vestAdmin'
- There are two vesting schedules - short and long, exact params TBD, likely 4 and 16 years respectively
- The params are global - meant to distribute a set amount of tokens to users
- Vested tokens can be used to cast governance votes
- The full amount of vested tokens can be transferred to a new account

## Governor

Openzeppelin Governor contract that can self-modify the `votingPeriod`, `proposalThreshhold` and `votingDelay`.

## Timelock

Openzeppelin Timlock that will add a delay to all governance decisions.
3 changes: 2 additions & 1 deletion contracts/sRel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ contract sRel is IsRel, ERC20, ERC20Permit, ERC20Votes, Ownable {
mapping(address => Utils.Unlock) public unlocks;
mapping(address => Utils.Vest) public vest;

constructor(address _r3l, uint _vestBegin, uint _vestShort, uint _vestLong)
constructor(address _r3l, address _vestAdmin, uint _vestBegin, uint _vestShort, uint _vestLong)
ERC20("Staked REL", "sREL")
ERC20Permit("Staked REL")
{
r3l = _r3l;
vestBegin = _vestBegin;
vestShort = _vestShort;
vestLong = _vestLong;
vestAdmin = _vestAdmin;
}

// The functions below are overrides required by Solidity.
Expand Down
17 changes: 17 additions & 0 deletions deploy/00_upgradeRel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { network } = require('hardhat')
const { setupAccount } = require('../test/utils')
const { upgradeRel, initV3 } = require('../scripts/upgradeRel')

module.exports = async ({ getNamedAccounts, deployments }) => {
const { proxyAdmin, relOwner, relAdmin } = await getNamedAccounts()

// this step should be done manually on the mainnet via upgrade script
if (network.name == 'hardhat') {
const proxyAdminSigner = await setupAccount(proxyAdmin)
await upgradeRel(proxyAdminSigner)

const relOwnerSigner = await setupAccount(relOwner)
await initV3(relOwnerSigner, relAdmin)
}
}
module.exports.tags = ['Rel']
10 changes: 10 additions & 0 deletions deploy/01_deplyLib.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()

await deploy('Utils', {
from: deployer,
})
}

module.exports.tags = ['Utils']
28 changes: 28 additions & 0 deletions deploy/02_deploySRel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const OZ_SDK_EXPORT = require('../openzeppelin-cli-export.json')

const getVestingParams = () => {
const vestBegin = Math.round(new Date('10.01.2021').getTime() / 1000)
const vestShort = vestBegin + 4 * 365 * 24 * 60 * 60
const vestLong = vestBegin + 16 * 365 * 24 * 60 * 60
return [vestBegin, vestShort, vestLong]
}

module.exports = async ({ getNamedAccounts, deployments }) => {
const [RelevantToken] = OZ_SDK_EXPORT.networks.mainnet.proxies[
'REL/RelevantToken'
]

const utils = await deployments.get('Utils')
const { deploy } = deployments
const { deployer, vestAdmin } = await getNamedAccounts()

await deploy('sRel', {
from: deployer,
args: [RelevantToken.address, vestAdmin, ...getVestingParams()],
libraries: { Utils: utils.address },
log: true,
})
}

module.exports.tags = ['sRel']
module.exports.dependencies = ['Utils']
17 changes: 17 additions & 0 deletions deploy/03_deployTimelock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { constants } = require('ethers')
const { AddressZero } = constants

module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()

const minDelay = 60 * 60 * 24 * 4

await deploy('RelTimelock', {
from: deployer,
args: [minDelay, [], [AddressZero]],
log: true,
})
}

module.exports.tags = ['RelTimelock']
16 changes: 16 additions & 0 deletions deploy/04_deployGovernor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()

const timelock = await deployments.get('RelTimelock')
const sRel = await deployments.get('sRel')

await deploy('RelGovernor', {
from: deployer,
args: [sRel.address, timelock.address],
log: true,
})
}

module.exports.tags = ['Governance']
module.exports.dependencies = ['sRel', 'RelTimelock']
65 changes: 65 additions & 0 deletions deploy/05_setupRoles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const { ethers } = require('hardhat')
const OZ_SDK_EXPORT = require('../openzeppelin-cli-export.json')

const TIMELOCK_ADMIN_ROLE =
'0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5'
const PROPOSER_ROLE =
'0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1'
const EXECUTOR_ROLE =
'0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63'

module.exports = async ({ getNamedAccounts, deployments }) => {
const [RelevantToken] = OZ_SDK_EXPORT.networks.mainnet.proxies[
'REL/RelevantToken'
]

const { deployer, relOwner } = await getNamedAccounts()
const RelevantTokenV3 = await ethers.getContractFactory(
'RelevantTokenV3',
await ethers.getSigner(relOwner),
)
const rel = RelevantTokenV3.attach(RelevantToken.address)

const timelock = await ethers.getContract('RelTimelock', deployer)
const relGov = await ethers.getContract('RelGovernor', deployer)
const sRel = await ethers.getContract('sRel', deployer)

// REL OWNER
const relTokenOwner = await rel.owner()
if (relTokenOwner !== timelock.addres) {
console.log('setting Timelock as owner of Rel', timelock.address)
await rel.transferOwnership(timelock.address)
} else {
console.log('Timelock is owner of Rel', timelock.address)
}

// sREL OWNER
const sRelOwner = await sRel.owner()
if (sRelOwner !== timelock.addres) {
console.log('setting Timelock as owner of sRel', timelock.address)
await sRel.transferOwnership(timelock.address)
} else {
console.log('Timelock is owner of sRel', timelock.address)
}

// Timelock Proposer TODO - do it via constructor with deterministic deploys
const govIsProposer = await timelock.hasRole(PROPOSER_ROLE, relGov.address)
if (!govIsProposer) {
console.log('Setting RelGovernor as timelock proposer')
await timelock.grantRole(PROPOSER_ROLE, relGov.address)
} else {
console.log('RelGovernor is timelock proposer')
}

// Revoke timelock admin (only self-admin)
const deployerIsAdmin = await timelock.hasRole(TIMELOCK_ADMIN_ROLE, deployer)
if (deployerIsAdmin) {
console.log('Renounce Deployer Admin Role')
await timelock.renounceRole(TIMELOCK_ADMIN_ROLE, deployer)
} else {
console.log('Deployer is not Admin')
}
}

module.exports.tags = ['Governance']
module.exports.dependencies = ['RelGovernor', 'Rel']
1 change: 1 addition & 0 deletions deployments/localhost/.chainId
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
31337
Loading

0 comments on commit 48370b5

Please sign in to comment.