Skip to content

Commit

Permalink
Update documentations
Browse files Browse the repository at this point in the history
* Update centrehq to circlefin

* Update centre-tokens references

* Remove typo

* Update more docs

* Organize README

* Add more content

* Add reference to OZ Proxy Upgrade Pattern
  • Loading branch information
circle-aloychan committed Oct 27, 2023
1 parent a1b5455 commit 08e3b16
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 176 deletions.
93 changes: 70 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
# centre-tokens

Fiat tokens on the [CENTRE](https://centre.io) network.
<!-- prettier-ignore-start -->
<!-- omit in toc -->
# Circle's Stablecoin Smart Contracts on EVM-compatible blockchains
<!-- prettier-ignore-end -->

This repository contains the smart contracts used by
[Circle's](https://www.circle.com/) stablecoins on EVM-compatible blockchains.
All contracts are written in [Solidity](https://soliditylang.org/) and managed
by the [Truffle](https://trufflesuite.com/) framework.

<!-- prettier-ignore-start -->
<!-- omit in toc -->
## Table of contents
<!-- prettier-ignore-end -->

- [Setup](#setup)
- [Development Environment](#development-environment)
- [IDE](#ide)
- [Development](#development)
- [TypeScript type definition files for the contracts](#typescript-type-definition-files-for-the-contracts)
- [Linting and Formatting](#linting-and-formatting)
- [Testing](#testing)
- [Deployment](#deployment)
- [Contracts](#contracts)
- [FiatToken features](#fiattoken-features)
- [ERC20 compatible](#erc20-compatible)
- [Pausable](#pausable)
- [Upgradable](#upgradable)
- [Blacklist](#blacklist)
- [Minting/Burning](#mintingburning)
- [Ownable](#ownable)
- [Additional Documentations](#additional-documentations)

## Setup

Expand All @@ -11,9 +40,9 @@ Requirements:
- Node 16.14.0
- Yarn 1.22.19

```
$ git clone [email protected]:centrehq/centre-tokens.git
$ cd centre-tokens
```sh
$ git clone [email protected]:circlefin/stablecoin-evm.git
$ cd stablecoin-evm
$ nvm use
$ npm i -g [email protected] # Install yarn if you don't already have it
$ yarn install # Install dependencies
Expand All @@ -24,25 +53,27 @@ $ yarn install # Install dependencies
We recommend using VSCode for the project here with these
[extensions](./.vscode/extensions.json) installed.

## TypeScript type definition files for the contracts
## Development

### TypeScript type definition files for the contracts

To generate type definitions:

```
```sh
$ yarn typechain
```

## Linting and Formatting
### Linting and Formatting

To check code for problems:

```
```sh
$ yarn static-check # Runs a static check on the repo.
```

or run the checks individually:

```
```sh
$ yarn typecheck # Type-check TypeScript code
$ yarn lint # Check JavaScript and TypeScript code
$ yarn lint --fix # Fix problems where possible
Expand All @@ -52,33 +83,33 @@ $ yarn slither # Run Slither

To auto-format code:

```
```sh
$ yarn fmt
```

## Testing
### Testing

First, make sure Ganache is running.

```
```sh
$ yarn ganache
```

Run all tests:

```
```sh
$ yarn test
```

To run tests in a specific file, run:

```
```sh
$ yarn test [path/to/file]
```

To run tests and generate test coverage, run:

```
```sh
$ yarn coverage
```

Expand Down Expand Up @@ -107,16 +138,22 @@ Run `yarn migrate --network NETWORK`, where NETWORK is either `mainnet` or

## Contracts

The implementation uses 2 separate contracts - a proxy contract
(`FiatTokenProxy.sol`) and an implementation contract (`FiatToken.sol`). This
allows upgrading the contract, as a new implementation contact can be deployed
and the Proxy updated to point to it.
The FiatToken contracts adheres to OpenZeppelin's
[Proxy Upgrade Pattern](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies)
([permalink](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/65cf285bd36af24570186ca6409341540c67238a/docs/modules/ROOT/pages/proxies.adoc#L1)).
There are 2 main contracts - an implementation contract
([`FiatTokenV2_2.sol`](./contracts/v2/FiatTokenV2_2.sol)) that contains the main
logic for FiatToken's functionalities, and a proxy contract
([`FiatTokenProxy.sol`](./contracts/v1/FiatTokenProxy.sol)) that redirects
function calls to the implementation contract. This allows upgrading FiatToken's
functionalities, as a new implementation contact can be deployed and the Proxy
can be updated to point to it.

### FiatToken
## FiatToken features

The FiatToken offers a number of capabilities, which briefly are described
below. There are more [detailed design docs](./doc/tokendesign.md) in the `doc`
folder.
directory.

### ERC20 compatible

Expand Down Expand Up @@ -155,3 +192,13 @@ the `masterMinter`.
The contract has an Owner, who can change the `owner`, `pauser`, `blacklister`,
or `masterMinter` addresses. The `owner` can not change the `proxyOwner`
address.

## Additional Documentations

- [FiatToken design](./doc/tokendesign.md)
- [MasterMinter design](./doc/masterminter.md)
- [Deployment process](./doc/deployment.md)
- [Preparing an upgrade](./doc/upgrade.md)
- [Upgrading from v1 to v2](./doc/v2_upgrade.md)
- [Upgrading from v2 to v2.1](./doc/v2.1_upgrade.md)
- [Upgrading from v2.1 to v2.2](./doc/v2.2_upgrade.md)
91 changes: 44 additions & 47 deletions doc/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,42 @@
This is the process for deploying a new proxy and implementation (as opposed to
upgrading an existing proxy).

Since the proxy uses `delegatecall` to forward calls to the implementation
initialization of the contracts becomes a little tricky because we can not
Since the proxy uses `delegatecall` to forward calls to the implementation,
initialization of the contracts becomes a little tricky because we cannot
initialize fields in the implementation contract via the constructor. Instead
there is an initialize method in the implementation contract, which is publicly
available, but can only be called once per proxy.

## Deploying the implementation contract

1. Deploy [FiatTokenV1](../contracts/FiatTokenV1.sol)
2. Initialize the fields in FiatToken via the `initialize` method. The values
are not important, but this will stop anyone else initializing the roles and
trying to use it as a token or pass it off as a real CENTRE token.
```
1. Deploy [FiatTokenV2_2](../contracts/v2/FiatTokenV2_2.sol)
2. Initialize the fields in FiatTokenV2_2 via the `initialize*` methods. The
values are not important, but this will stop anyone else initializing the
roles and trying to use it as a token or pass it off as a real Circle token.

```js
initialize(
"",
"",
"",
0,
throwawayAddress,
throwawayAddress,
throwawayAddress,
throwawayAddress
)
"",
"",
"",
0,
THROWAWAY_ADDRESS,
THROWAWAY_ADDRESS,
THROWAWAY_ADDRESS,
THROWAWAY_ADDRESS
);
initializeV2("");
initializeV2_1(THROWAWAY_ADDRESS);
initializeV2_2([], "");
```

3. Verify that all fields in the FiatToken have been initialized correctly and
have the expected values. See [README.validate.md](../validate/validate.js).

## Deploying a Proxy:

1. Obtain addresses for the various contract roles from CENTRE ops. The keys for
these addresses will be stored offline. The address needed are:
1. Obtain addresses for the following contract roles. Ensure that the keys for
these addresses are securely stored.

```
admin
Expand All @@ -50,7 +55,7 @@ available, but can only be called once per proxy.
of the deployed implementation contract to the constructor, which will
initialize the `_implementation` field.

3. The `admin` of the proxy contract defaults to msg.sender. You must either
3. The `admin` of the proxy contract defaults to `msg.sender`. You must either
change the `admin` now, or send the remaining transactions from a different
address. The `admin` can only see methods in the Proxy, any method calls from
`admin` will not be forwarded to the implementation contract. The `admin`
Expand All @@ -59,47 +64,39 @@ available, but can only be called once per proxy.

```
changeAdmin(adminAddress)
```

4. Initialize the proxy, via the `initialize` method. This call will get
4. Initialize the proxy via the `initialize*` methods. This call will get
forwarded to the implementation contract, but since it is via `delegatecall`
it will run in the context of the Proxy contract, so the fields it is
initializing will be stored it the storage of the Proxy. The values passed
initializing will be stored in the storage of the Proxy. The values passed
here are important, especially for the roles that will control the contract.
These addresses should be obtained from CENTRE ops, and the keys will be
stored offline.

```
```js
initialize(
"USD//C",
"USDC",
"USD",
6,
masterMinterAddress,
pauserAddress,
blacklisterAddress,
ownerAddress
)
tokenName,
tokenSymbol,
tokenCurrency,
tokenDecimals,
masterMinterAddress,
pauserAddress,
blacklisterAddress,
ownerAddress
);
initializeV2(newTokenName);
initializeV2_1(lostAndFoundAddress);
initializeV2_2(accountsToBlacklist, newTokenSymbol);
```

5. Verify the fields have been properly initialized. Verification should be
performed independently by multiple people to make sure that the contract has
been deployed correctly. The following fields should be verified:

- name, symbol, and currency are as expected
- `decimals` is 6
- `masterMinter` is the expected address
- `pauser` is the expected address
- `blacklister` is the expected address
- `owner` is the expected address
- `admin` is the expected address. Note that `admin` is not callable by
anyone other than the admin, so this can be verified by calling
`eth.getStorageAt(proxyAddress, 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b)`
- `_implementation` is the address of the implementation contract. Note that
`implementation` is not callable by anyone other than the admin, so this
can be verified by calling
`eth.getStorageAt(proxyAddress, 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3)`
- `admin` is the expected address
- `implementation` is the address of the implementation contract
- `name`, `symbol`, `currency` and `decimals` are as expected
- `version` is 2
- `owner`, `masterMinter`, `pauser`, `blacklister` are the expected addresses
- `totalSupply` is 0
- `initialized` is `true`

Expand Down
19 changes: 8 additions & 11 deletions doc/masterminter.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# MasterMinter contract

The MasterMinter is a governance contract. It delegates the functionality of the
`masterMinter` role in the CENTRE USDC contract to multiple addresses. (The
`masterMinter` role in the Circle FiatToken contract to multiple addresses. (The
`masterMinter` role can add and remove minters from a FiatToken and set their
allowances.) The MasterMinter contract delegates the minter management
capability to `controllers`. Each `controller` manages exactly one `minter`, and
a single `minter` may be managed by multiple `controllers`. This allows
separation of duties (off-line key management) and simplifies nonce management
separation of duties (offline key management) and simplifies nonce management
for warm transactions.

Minters and FiatToken holders are not affected by replacing a `masterMinter`
Expand Down Expand Up @@ -45,8 +45,8 @@ contract to call minter management functions on the FiatToken contract:

Together, these four functions are defined as the `MinterManagementInterface`.
The `MasterMinter` contains the address of a `minterManager` that implements the
`MinterManagementInterface`. The `MasterMinter` interacts with the USDC token
via the `minterManager`.
`MinterManagementInterface`. The `MasterMinter` interacts with the FiatToken
contract via the `minterManager`.

When a `controller` calls a function on `MasterMinter`, the `MasterMinter` will
call the appropriate function on the `FiatToken` contract on its behalf. Both
Expand Down Expand Up @@ -108,13 +108,10 @@ We recommend assigning at least <b>two</b> `controllers` to each `minter`.
- <b>SecurityController.</b> Use this `controller` to sign a single
`removeMinter` transaction and store it for emergencies.

The private keys to the `AllowanceController` and `SecurityController` should
stay in cold storage. This configuration lets the Controller keep multiple warm
`incrementMinterAllowance` transactions on hand, as well as the `removeMinter`
transaction in case of a problem. Broadcasting the `removeMinter` transaction
will cause all future `incrementMinterAllowance` transactions to `throw`. Since
the two types of transactions are managed by different addresses, there is no
need to worry about nonce management.
This configuration allows the `removeMinter` transaction to be presigned as
nonces for the `SecurityController` are deterministic, which reduces the time to
respond when there's an issue. Broadcasting the `removeMinter` transaction will
cause all future interactions from the `AllowanceController` to `throw`.

# MasterMinter vs. MintController

Expand Down
Loading

0 comments on commit 08e3b16

Please sign in to comment.