TM#016 — PyUSD Explained (and Fixed)

Antematter
5 min readAug 30, 2023

Crypto community is always hungry for dollars especially stablecoins, the crypto dollars. Feeling the same need PayPal also decided to join the stablecoins club by launching PYUSD.

Goals

  • Providing crypto dollars fully backed by fiat dollars at a 1:1 ratio
  • Providing ease of use of crypto dollars by instant, cheap transfers
  • On ramping for users to buy crypto dollars which they can spend on decentralized apps

Features

  • Fully centralized
  • Fully audited by a number of firms
  • Regulatory oversight by the New York State Department of Financial Services
  • Issued and secured by Paxos, the same reputable organization behind USDP, BUSD, and PAXG token.
  • In case PayPal or paxos goes bankrupt even then funds are safe under bankruptcy remote law.
  • Developed as a ERC-20 token on Ethereum blockchain so highly compatible with decentralized apps.

Read more at : https://paxos.com/pyusd/

Code walkthrough

PYUSD code is here : https://github.com/paxosglobal/pyusd-contract/tree/master/contracts Implementation Address : https://etherscan.io/address/0xe17b8adf8e46b15f3f9ab4bb9e3b6e31db09126e

Proxy Address : https://etherscan.io/token/0x6c3ea9036406852006290770bedfcaba0e23a0e8

Now if you see the proxy code you will notice the following things

  • Compiler version 0.4.24 is used
  • AdminUpgradeable proxy by openZeppelin is used

Now lets move to the implementation code. You will notice the following :

  • Compiler 0.4.24 is used and safemath library is used
  • Pause, unpause, and ERC-20 related functions are all standard but most are using custom logic due to wipe and freeze logic which was to be used.
  • Account is frozen or not is checked in approve, increaseApproval, decreaseApproval

Issues

PYUSD was supposed to be centralized and that is not the issue here. Issues however do rise regarding the smart contract that was used.

Although they have used ERC-20 standard which is a good thing but they have

  • Used quite an old solidity compiler version for the contract which is inefficient. There are currently 11 known bugs that were fixed after version 0.4.24.
  • Contract code is inefficient.
  • There are roles/Admins who can freeze and wipe assets without any check and balance.
  • It is an upgradeable contract that means the Admin can just change the logic of the contract at will and this logic can be anything.
  • Upgrade proxy is outdated also.
  • They are stopping accounts from transfer and transferFrom if they freeze but they are also checking frozen condition in approve, increaseAllowance, decreaseAllowance Functions which is not needed. If someone is frozen then it does not matter to check approve or allowance because in the end they or anyone allowed still wont be able to transfer anything.

Solutions

  • They could have gone with a tried and tested solidity version from 0.8.1 to 0.8.10.
  • There are soo many improvements since version 0.4 solidity.
  • Proxy could have been improved by using UUPS proxy or even a diamond proxy
  • UUPS is an efficient proxy compared to what PYUSD has used.
  • Openzepplin pausable library could have been used.
  • Writing custom code is not recommended when it could have been avoided by using widely used tested libraries.
  • SafeMath library was never needed had they gone with solidity version 0.8.1 and beyond because safemath was made part of those versions
  • Access control by Openzepplin is industry tested and that was better than implementing your own modifiers just for the sake of controlling access.
  • Contracts and especially stablecoin contracts are supposed to be immutable. Making it upgradeable makes no sense.

Improved Implementation

Solution should be according to the goals of PayPal USD and what it originally focuses on. Solution also should be simple, no complex shenanigans.

We made a simple yet efficient PYUSD contract which can be found here: Antematter PYUSD

Before we jump into why and what optimizations were made, let’s see what the deployment cost was in case of original PYUSD. Since PYUSD is upgradeable contract so proxy deployment alone had the following cost : 432,040 units. Can be seen on this transaction:

https://etherscan.io/tx/0xd2660a80f27d6bdea7760e6f0866debe9b11b33f072cc66e8b447d77410dcf0d

Logic contract of PYUSD had the following cost : 3,266,267 units. Can be seen here:

https://etherscan.io/tx/0x8fa810d17fe86669a7ac369f5e8b7bed0785f2cfe7472606698506f8bc7425af

Total = 3,698,307 units

Do notice that PYUSD had deployment with compiler optimizer runs set at 200.

Now After deploying the antematterPYUSD contract on remix VM mainnet fork it had the following cost:

At compiler optimizer runs of 200 it took 2,194,176 units. So that is a more than 41% savings alone on deployment of antematterPYUSD.

Now lets compare transfer function of both contracts. For 10 PYUSD transfer original contract took 49,197 gas. See here: https://etherscan.io/tx/0x4727710f415fd36f11d60d7f5e733c2fe22f180437e1a3ec5385f50c3a2fe246

For the same 10 PYUSD transfer antematter contract took : 36,384 which is 26% cheaper.

The Changes

The following changes were made to the PYUSD contract :

  1. Contract was a simple contract non upgradeable as upgrade is not needed.
  2. Openzepplin Access control library was used instead of storing addresses in contract storage and managing access accordingly
  3. Events were simplified
  4. Compiler version 0.8.9 was used which highly optimized and safer than 0.4.24 which was originally used.
  5. SafeMath library was not used as it already a part of compiler 0.8.9
  6. transfer function was used from openzepplin erc20 with bool return value removed
  7. Frozen require was added in _beforeTokenTransfer function instead of adding it into transfer and transferFrom
  8. Return bool values were removed from everywhere as function success can easily be checked without that also
  9. proposeOwner , disregardProposeOwner, claimOwnership was converted into one function as changeOwner function
  10. reclaimPYUSD function was removed as contract address itself was frozen in constructor so sending tokens to contract is not possible so reclaiming function is not needed
  11. All role changing functions were changed to use AccessControl functions from openzepplin Access Control library
  12. wipeFrozenAddress function logic was updated with _burn function of erc20
  13. Increase and decrease supply logic was updated with _mint function of erc20

Conclusion

Original PYUSD contract was made by PAXOS with centralization in mind however still using outdated compiler version and not using tried and tested libraries was an approach which should have been avoided. There are still many ways to improve the contract, but the point here is that even by using the simplest of methods PAXOS could have saved a lot of user funds from being wasted as gas fees.

This article is written by Abdul Mueed, a Senior Blockchain Developer at Antematter.io

--

--

Antematter

We help SMBs automate workflows & generate revenue through AI & Blockchain tech.