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

Paxos token contracts #2

Merged
merged 3 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 27 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.idea/
node_modules/
build/
abi-out/
coverage/
coverage.json
coverageEnv/
scTopics
allFiredEvents
artifacts/
cache/
typechain-types/

.DS_Store
.env

# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

*.out
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
init.author.name=PAXOS
registry=https://registry.npmjs.org/
16 changes: 16 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"plugins": ["prettier-plugin-solidity"],
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": true,
"explicitTypes": "preserve"
}
}
]
}
5 changes: 5 additions & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
testCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle test --network coverage',
compileCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle compile --network coverage',
skipFiles: ['Migrations.sol', 'mocks', 'interfaces', 'archive']
};
8 changes: 8 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "solhint:recommended",
"rules": {
"custom-errors": "off",
"reason-string": ["warn", {"maxLength": 120} ],
"func-visibility": ["warn", {"ignoreConstructors":true} ]
}
}
2 changes: 2 additions & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
contracts/zeppelin/
contracts/archive/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Paxos Technology Solutions, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1 change: 1 addition & 0 deletions PaxosToken.abi

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions PaxosToken.bin

Large diffs are not rendered by default.

207 changes: 206 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,206 @@
# paxos-token-contracts
# PAXOS TOKEN

Paxos-issued USD-collateralized ERC20 stablecoin public smart contract repository.

https://github.com/paxosglobal/paxos-token-contract

### Roles

| Role |
| ------------------------------ |
| DEFAULT_ADMIN_ROLE |
| PAUSE_ROLE |
| ASSET_PROTECTION_ROLE |
| SUPPLY_CONTROLLER_MANAGER_ROLE |
| SUPPLY_CONTROLLER_ROLE |

To guard against centralized control, the addresses above utilize multisignature contracts ([source](https://github.com/paxosglobal/simple-multisig)). Any change requires the presence of a quorum of signers in the same physical location, ensuring that no individual signer can unilaterally influence a change.

### ABI and Addresses

The contract abi is in `PaxosToken.abi`, which is the implementation contract abi.

Interaction with token is done at the address of the proxy. Deployed token addresses can be found in
the [Paxos docs](https://docs.paxos.com/stablecoin).

## Contract Specification

Paxos Token is an ERC20 token that is Centrally Minted and Burned by Paxos,
representing the trusted party backing the token with USD.

### ERC20 Token

The public interface of PaxosToken is the ERC20 interface
specified by [EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md).

- `name()`
- `symbol()`
- `decimals()`
- `totalSupply()`
- `balanceOf(address who)`
- `transfer(address to, uint256 value)`
- `approve(address spender, uint256 value)`
- `increaseApproval(address spender, uint256 addedValue)`
- `decreaseApproval(address spender, uint256 subtractedValue)`
- `allowance(address owner, address spender)`
- `transferFrom(address from, address to, uint256 value)`

And the usual events.

- `event Transfer(address indexed from, address indexed to, uint256 value)`
- `event Approval(address indexed owner, address indexed spender, uint256 value)`

Typical interaction with the contract will use `transfer` to move the token as payment.
Additionally, a pattern involving `approve` and `transferFrom` can be used to allow another
address to move tokens from your address to a third party without the need for the middleperson
to custody the tokens, such as in the 0x protocol.

#### Warning about ERC20 approve front-running

[There is a well known gotcha](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729) involving the ERC20
`approve` method. The problem occurs when the owner decides to change the allowance of a spender that already has an
allowance. If the spender sends a `transferFrom` transaction at a similar time that the owner sends the new `approve`
transaction and the `transferFrom` by the spender goes through first, then the spender gets to use the original
allowance, and also get approved for the intended new allowance.

To mitigate this risk, we recommend that smart contract users utilize the alternative functions `increaseApproval` and
`decreaseApproval` instead of using `approve` directly.

### Controlling the token supply

PaxosToken uses a separately deployed `SupplyControl` contract to control the token supply. `SupplyControl` has a `SUPPLY_CONTROLLER_MANAGER_ROLE` which is responsible for managing addresses with the `SUPPLY_CONTROLLER_ROLE`, referred
to as supplyControllers. Only supplyControllers can mint and burn tokens. SupplyControllers can optionally have rate
limits to limit how many tokens can be minted over a given time frame.

`SupplyControl` also includes functions to get all of the supply controller addresses
and get configuration for a specific supply controller.

### Pausing the contract

In the event of a critical security threat, Paxos has the ability to pause transfers
and approvals of the token. The ability to pause is controlled by a single `owner` role,
following OpenZeppelin's
[Ownable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/5daaf60d11ee2075260d0f3adfb22b1c536db983/contracts/ownership/Ownable.sol).
The simple model for pausing transfers following OpenZeppelin's
[Pausable](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/5daaf60d11ee2075260d0f3adfb22b1c536db983/contracts/lifecycle/Pausable.sol).

While paused, the supply controller retains the ability to mint and burn tokens.

### Asset Protection Role

The `ASSET_PROTECTION_ROLE` can freeze and unfreeze the token balance of any address on chain.
It can also wipe the balance of an address after it is frozen
to allow the appropriate authorities to seize the backing assets.

Freezing is something that Paxos will not do on its own accord,
and as such we expect to happen extremely rarely. Checking if an address is frozen is possible
via `isFrozen(address who)`.

### Delegate Transfer

To facilitate gas-less transactions, we have implemented [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) and [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612).

#### EIP-3009
The public functions, `transferWithAuthorization` and `transferWithAuthorizationBatch` (for multiple transfers request), allows a spender(delegate) to transfer tokens on behalf of the sender, with condition that a signature, conforming to [EIP-712](https://eips.ethereum.org/EIPS/eip-712), is provided by the respective sender.

```
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;

function transferWithAuthorizationBatch(
address[] memory from,
address[] memory to,
uint256[] memory value,
uint256[] memory validAfter,
uint256[] memory validBefore,
bytes32[] memory nonce,
uint8[] memory v,
bytes32[] memory r,
bytes32[] memory s
) external;
```

#### EIP-2612
The sender can establish an allowance for the spender using the permit function, which employs an EIP-712 signature for authorization. Subsequently, the spender can employ the `transferFrom` and `transferFromBatch` functions to initiate transfers on behalf of the sender.

```
function permit(
address owner,
address spender,
uint value,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;

function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool);

function transferFromBatch(
address[] calldata _from,
address[] calldata _to,
uint256[] calldata _value
) public returns (bool);
```

### Upgradeability Proxy

To facilitate upgradeability on the immutable blockchain we follow a standard
two-contract delegation pattern: a proxy contract represents the token,
while all calls not involving upgrading the contract are delegated to an
implementation contract.

The delegation uses `delegatecall`, which runs the code of the implementation contract
_in the context of the proxy storage_. This way the implementation pointer can
be changed to a different implementation contract while still keeping the same
data and contract address, which are really for the proxy contract.

USDP and PYUSD use `AdminUpgradeabilityProxy` from OpenZeppelin.

USDG and SupplyControl use `UUPSUpgradeable` from OpenZeppelin.

`UUPSUpgradeable` is a newer proxy pattern which
has some advantages over `AdminUpgradeabilityProxy`. One issue with `AdminUpgradeabilityProxy` is the proxy admin
cannot call any of the implementation functions which means the proxy admin must be a separate address
from the DEFAULT_ADMIN_ROLE. This is not an issue with `UUPSUpgradeable`. Another advantage is updating
the proxy admin in `UUPSUpgradeable` is a two step process due to using OpenZeppelin's AccessControlDefaultAdmin.
However, in `AdminUpgradeabilityProxy` it's one step which is more dangerous.

## Upgrade Process

The implementation contract is only used for the logic of the non-admin methods.
A new implementation contract can be set by calling `upgradeTo()` or `upgradeToAndCall()` on the proxy,
where the latter is used for upgrades requiring a new initialization or data migration so that
it can all be done in one transaction. You must first deploy a copy of the new implementation
contract, which is automatically paused by its constructor to help avoid accidental calls directly
to the proxy contract.

## Contract Tests
Install dependencies:

`npm install`

Compile the contracts:

`npm run compile`

Run unit tests:

`npm run test`

Check test coverage:

`npm run coverage`
Binary file added assets/logo_paxos-standard_cropped_28.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions bs-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"server": {
"baseDir": ["./src", "./build/contracts"]
}
}
53 changes: 53 additions & 0 deletions contracts/BaseStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import { SupplyControl } from "./SupplyControl.sol";

/**
* @title BaseStorage
* @notice The BaseStorage contract is a storage abstraction contract for the PaxosToken.
* @custom:security-contact [email protected]
*/
contract BaseStorage {
// Check if contract is initialized until version 1.
bool internal initializedV1;

// ERC20 Basic data to capture balances and total supply.
mapping(address => uint256) internal balances;
uint256 internal totalSupply_;

// Storage to keep track of allowances.
mapping(address => mapping(address => uint256)) internal allowed;

// Owner of contract: Deprecated.
address public ownerDeprecated;

// Represents if the contact is paused or not.
bool public paused;

// Asset protection data: Deprecated.
address public assetProtectionRoleDeprecated;

// Mapping to keep track of frozen addresses.
mapping(address => bool) internal frozen;

// Supply controller of the contract.
address public supplyControllerDeprecated;

// Proposed owner of the contract: Deprecated.
address public proposedOwnerDeprecated;

// Delegated transfer data: Deprecated.
address public betaDelegateWhitelisterDeprecated;
mapping(address => bool) internal betaDelegateWhitelistDeprecated;
mapping(address => uint256) internal nextSeqsDeprecated;
// Hash of the EIP712 Domain Separator data: Deprecated.
// solhint-disable-next-line var-name-mixedcase
bytes32 public EIP712_DOMAIN_HASH_DEPRECATED;

// Address of the supply control contract
SupplyControl public supplyControl;

// Storage gap: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps
uint256[24] __gap_BaseStorage;
}
Loading