Skip to content

Commit

Permalink
fix: spearbit audit (#139)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes OPT-XXX
  • Loading branch information
excaliborr authored Aug 13, 2024
2 parents fbb6414 + 859db9d commit bee2bc9
Show file tree
Hide file tree
Showing 714 changed files with 1,501,733 additions and 9,298 deletions.
36 changes: 28 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,55 @@ MAINNET_RPC=
OPTIMISM_RPC=
BASE_RPC=

## Mainnet
# Mainnet
MAINNET_PK=
L1_FACTORY_MAINNET=
L1_ADAPTER_OP=
L2_ADAPTER_OP=

### Optimism
#### Migration
# Custom Chain
BRIDGED_USDC_IMPLEMENTATION=
L1_MESSENGER=
CHAIN_NAME=
L1_ADAPTER=
ROLE_CALLER=
BURN_CALLER=


# Optimism
# Migration related variables
OP_USDC_ADMIN=
OP_ROLE_CALLER=
OP_ROLE_CALLER_PK=
OP_BURN_CALLER=
OP_BURN_CALLER_PK=
OP_NEW_USDC_OWNER=

## Testnet
# Testnet
SEPOLIA_PK=
L1_FACTORY_SEPOLIA=
L1_ADAPTER_OP_SEPOLIA=
L2_ADAPTER_OP_SEPOLIA=

### Op Sepolia
#### Migration
# Op Sepolia
# Migration related variables
OP_SEPOLIA_USDC_ADMIN=
OP_SEPOLIA_ROLE_CALLER=
OP_SEPOLIA_ROLE_CALLER_PK=
SEPOLIA_OP_BURN_CALLER=
SEPOLIA_OP_BURN_CALLER_PK=
OP_SEPOLIA_NEW_USDC_OWNER=

## Etherscan
ETHERSCAN_API_KEY=
# Etherscan
ETHERSCAN_API_KEY=

# USDC Implementation addresses
# These need to be deployed and set manually, ideally from circle's stablecoin-evm repo

# Mainnets
USDC_BASE_IMPLEMENTATION=
USDC_OPTIMISM_IMPLEMENTATION=

# Testnets
USDC_BASE_SEPOLIA_IMPLEMENTATION=
USDC_OPTIMISM_SEPOLIA_IMPLEMENTATION=
28 changes: 14 additions & 14 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,20 @@ L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature:test_invalidSignature(addres
L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature:test_nonceIncrement(address,uint256,uint256,uint32) (runs: 1000, μ: 67918, ~: 67918)
L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature:test_revertOnExpiredMessage(address,uint256,bytes,uint256,uint256,uint32) (runs: 1000, μ: 17618, ~: 17571)
L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature:test_revertOnMessengerNotActive(address,uint256,bytes,uint256,uint32) (runs: 1000, μ: 37675, ~: 37632)
L2OpUSDCFactory_Unit_Constructor:test_callChangeAdminWithFallbackProxy() (gas: 2617614)
L2OpUSDCFactory_Unit_Constructor:test_deployAdapter() (gas: 2626966)
L2OpUSDCFactory_Unit_Constructor:test_deployUsdcImplementation() (gas: 2617585)
L2OpUSDCFactory_Unit_Constructor:test_deployUsdcProxy() (gas: 2620287)
L2OpUSDCFactory_Unit_Constructor:test_executeUsdcImplInitTxs() (gas: 2632056)
L2OpUSDCFactory_Unit_Constructor:test_executeUsdcProxyInitTxs() (gas: 2632101)
L2OpUSDCFactory_Unit_DeployCreate:test_deployCreate() (gas: 467261)
L2OpUSDCFactory_Unit_DeployCreate:test_revertIfDeploymentFailed() (gas: 41318)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_callConfigureMinter(address) (runs: 1000, μ: 27586, ~: 27586)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_callInitialize(address) (runs: 1000, μ: 30082, ~: 30082)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_callTransferOwnership(address) (runs: 1000, μ: 27463, ~: 27463)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_callUpdateMasterMinter(address) (runs: 1000, μ: 27420, ~: 27420)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_executeInitTxsArray(address) (runs: 1000, μ: 45148, ~: 45148)
L2OpUSDCFactory_Unit_ExecuteInitTxs:test_revertIfInitTxsOnArrayFail(address) (runs: 1000, μ: 77139, ~: 77139)
L2OpUSDCDeploy_Unit_Constructor:test_callChangeAdminWithFallbackProxy() (gas: 2617614)
L2OpUSDCDeploy_Unit_Constructor:test_deployAdapter() (gas: 2626966)
L2OpUSDCDeploy_Unit_Constructor:test_deployUsdcImplementation() (gas: 2617585)
L2OpUSDCDeploy_Unit_Constructor:test_deployUsdcProxy() (gas: 2620287)
L2OpUSDCDeploy_Unit_Constructor:test_executeUsdcImplInitTxs() (gas: 2632056)
L2OpUSDCDeploy_Unit_Constructor:test_executeUsdcProxyInitTxs() (gas: 2632101)
L2OpUSDCDeploy_Unit_DeployCreate:test_deployCreate() (gas: 467261)
L2OpUSDCDeploy_Unit_DeployCreate:test_revertIfDeploymentFailed() (gas: 41318)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_callConfigureMinter(address) (runs: 1000, μ: 27586, ~: 27586)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_callInitialize(address) (runs: 1000, μ: 30082, ~: 30082)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_callTransferOwnership(address) (runs: 1000, μ: 27463, ~: 27463)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_callUpdateMasterMinter(address) (runs: 1000, μ: 27420, ~: 27420)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_executeInitTxsArray(address) (runs: 1000, μ: 45148, ~: 45148)
L2OpUSDCDeploy_Unit_ExecuteInitTxs:test_revertIfInitTxsOnArrayFail(address) (runs: 1000, μ: 77139, ~: 77139)
OpUSDCBridgeAdapter_Unit_CheckSignature:test_invalidSignature(bytes,string) (runs: 1000, μ: 70125, ~: 70146)
OpUSDCBridgeAdapter_Unit_CheckSignature:test_validSignature(bytes) (runs: 1000, μ: 19615, ~: 19563)
OpUSDCBridgeAdapter_Unit_Constructor:test_constructorParams() (gas: 20858)
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright © 2023 Wonderland
Copyright © 2024 Wonderland

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:

Expand Down
112 changes: 101 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
# opUSDC
# Bridged USDC Standard for the OP Stack

> ⚠️ This code has not been audited yet and is not production ready at this time, tread with caution.
USDC is one of the most bridged assets across the crypto ecosystem, and USDC is often bridged to new chains prior to any action from Circle. This can create a challenge when Bridged USDC achieves substantial marketshare, but Native USDC is preferred by the ecosystem, leading to fragmentation between multiple representations of USDC. Circle introduced the [Bridged USDC Standard](https://www.circle.com/blog/bridged-usdc-standard) to ensure that chains can easily deploy a form of USDC that is capable of being upgraded in-place by Circle to Native USDC, if and when appropriate, and prevent the fragmentation problem.

Bridged USDC Standard for the OP Stack allows for an efficient and modular solution for expanding the Bridged USDC Standard across the Superchain ecosystem. Utilizing the cross chain messaging of the canonical OP Stack bridge the adapter allows for easy access to Bridged USDC liquidity across OP Stack chains.

Chain operators can use the Bridged USDC Standard for the OP Stack to get Bridged USDC on their OP Stack chain while also providing the optionality for Circle to seamlessly upgrade Bridged USDC to Native USDC and retain existing supply, holders, and app integrations.

opUSDC allows for an efficient and modular solution for expanding USDC across the optimism super chain ecosystem. Utilizing the cross chain messaging of the canonical bridge the adapter allows for easy access to USDC liquidity across all op chains.

## Contracts

_`L1OpUSDCFactory.sol`_ - Factory contract to deploy and setup the `L1OpUSDCBridgeAdapter` contract on L1. Precalculates the addresses of the L2 deployments and triggers their deployment. Setup the L1 adapter and the L2 adapter.
_`L1OpUSDCFactory.sol`_ - Factory contract to deploy and setup the `L1OpUSDCBridgeAdapter` contract on L1. Precalculates the addresses of the L2 deployments and triggers their deployment, by sending a transaction to L2.

_`L2OpUSDCFactory.sol`_ - Factory contract deployed from the L1 factory through a cross-chain deployment for deploying the L2 USDC implementation, proxy, and `L2OpUSDCBridgeAdapter` contract, all at once on the `deploy()` function.
_`L2OpUSDCDeploy.sol`_ - One time use deployer contract deployed from the L1 factory through a cross-chain deployment. Used as a utility contract for deploying the L2 USDC Proxy, and `L2OpUSDCBridgeAdapter` contract, all at once in its constructor.

_`L1OpUSDCBridgeAdapter`_ - Contract that allows for the transfer of USDC from Ethereum Mainnet to a specific OP-chain. Locks USDC on Ethereum Mainnet and sends a message to the other chain to mint the equivalent amount of USDC. Receives messages from the other chain and unlocks USDC on the Ethereum Mainnet. Controls the message flow between layers. Supports the requirements for the Bridged USDC to be migrated to Native USDC, should the chain operator and Circle want to.
_`L1OpUSDCBridgeAdapter`_ - Contract that allows for the transfer of USDC from Ethereum Mainnet to a specific OP-chain. Locks USDC on Ethereum Mainnet and sends a message to the other chain to mint the equivalent amount of USDC. Receives messages from the other chain and unlocks USDC on the Ethereum Mainnet. Controls the message flow between layers. Supports the requirements for the Bridged USDC to be migrated to Native USDC should the chain operator and Circle want to.

_`L2OpUSDCBridgeAdapter`_ - Contract that allows for the transfer of USDC from the a specific OP-chain to Ethereum Mainnet. Burns USDC on the other chain and sends a message to Ethereum Mainnet to unlock the equivalent amount of USDC. Receives messages from Ethereum Mainnet and mints USDC on the the other chain. Allows contract owner to execute arbitrary functions on the Bridged USDC contract.
_`L2OpUSDCBridgeAdapter`_ - Contract that allows for the transfer of USDC from the specific OP-chain to Ethereum Mainnet. Burns USDC on the L2 and sends a message to Ethereum Mainnet to unlock the equivalent amount of USDC. Receives messages from Ethereum Mainnet and mints USDC. Allows chain operator to execute arbitrary functions on the Bridged USDC contract as if they were the owner of the contract.

## L1 → L2 Deployment
![image](https://github.com/defi-wonderland/opUSDC/assets/165055168/ac9d0b57-03e7-40ae-b109-34d656d7539b)

![image](https://github.com/user-attachments/assets/1ec286f6-87ae-4b08-8086-ee8077a36ae3)

## L1 → L2 USDC Canonical Bridging

![image](https://github.com/defi-wonderland/opUSDC/assets/165055168/eaf55522-e768-463f-830b-b9305cec1e79)

## Migrating from Bridged USDC to Native USDC
![image](https://github.com/defi-wonderland/opUSDC/assets/165055168/17aebc4a-709f-4084-ab83-000e299a70bd)

![image](https://github.com/user-attachments/assets/291aae4c-e9fb-43a5-a11d-71bb3fc78311)


## Security
Bridged USDC Standard for the OP Stack has undergone audits from [Spearbit](https://spearbit.com/) and is recommended for production use. The audit report is available [here](./audits/spearbit.pdf).

## Setup

1. Install Foundry by following the instructions from [their repository](https://github.com/foundry-rs/foundry#installation).
2. Copy the `.env.example` file to `.env` and fill in the variables.
3. Install the dependencies by running: `yarn install`. In case there is an error with the commands, run `foundryup` and try them again.
3. Install the dependencies by running: `yarn install`. If there is an error with the commands, run `foundryup` and try them again.

## Build

Expand All @@ -46,7 +55,7 @@ yarn build:optimized

## Running tests

Unit tests should be isolated from any externalities, while Integration usually run in a fork of the blockchain. In this boilerplate you will find example of both.
Unit tests should be isolated from any externalities, while Integration tests usually run in a blockchain fork. In this boilerplate, you will find examples of both.

In order to run both unit and integration tests, run:

Expand Down Expand Up @@ -78,7 +87,88 @@ In order to check your current code coverage, run:
yarn coverage
```

## Deploying

In order to deploy the opUSDC procotol for your op-chain, you will need to fill out these variables in the `.env` file:

```python
# The factory contract address on L1
L1_FACTORY_MAINNET=
# The bridged USDC implementation address on L2
BRIDGED_USDC_IMPLEMENTATION=
# The address of your CrossDomainMessenger on L1
L1_MESSENGER=
# The name of your chain
CHAIN_NAME=
# The private key that will sign the transactions on L1
MAINNET_PK=
# Ethereum RPC URL
MAINNET_RPC=
```

After all these variables are set, navigate to the `script/mainnet/Deploy.s.sol` file and edit the following lines with your desired configuration, we add a sanity check that will revert if you forget to change this value:
```solidity
// NOTE: We have these hardcoded to default values, if used in product you will need to change them
bytes[] memory _usdcInitTxs = new bytes[](3);
_usdcInitTxs[0] = USDCInitTxs.INITIALIZEV2;
_usdcInitTxs[1] = USDCInitTxs.INITIALIZEV2_1;
_usdcInitTxs[2] = USDCInitTxs.INITIALIZEV2_2;
// Sanity check to ensure the caller of this script changed this value to the proper naming
assert(keccak256(_usdcInitTxs[0]) != keccak256(USDCInitTxs.INITIALIZEV2));
```

Then run this command to test:
```bash
yarn script:deploy
```

And when you are ready to deploy to mainnet, run:
```bash
yarn script:deploy:broadcast
```

## Migrating to Native USDC
> ⚠️ Migrating to native USDC is a manual process that requires communication with circle, this section assumes both parties are ready to migrate to native USDC.
In order to migrate to native USDC, you will need to fill out these variables in the `.env` file:
```python
# The address of the L1 opUSDC bridge adapter
L1_ADAPTER=
# The private key of the transaction signer, should be the owner of the L1 Adapter
MAINNET_OWNER_PK=
# The address of the role caller, should be provided by circle
ROLE_CALLER=
# The address of the burn caller, should be provided by circle
BURN_CALLER
```

After all these variables are set, run this command to test:
```bash
yarn script:migrate
```

And when you are ready to migrate to native USDC, run:
```bash
yarn script:migrate:broadcast
```

### What will circle need at migration?

#### Circle will need the metadata from the original deployment of the USDC implementation that was used

To do this you will need to go back to the `stablecoin-evm` github repo that the implementation was deployed from in order to extract the raw metadata from the compiled files. The compiled files are usually found in the `out/` or `artifacts/` folders. To extract the raw metadata you can run a command like this:

```bash
cat out/example.sol/example.json | jq -jr '.rawMetadata' > example.metadata.json
```

You will need to do this for both the token contract and any external libraries that get deployed with it, at the time of writing this these are `FiatTokenV2_2` and `SignatureChecker` but these are subject to change in the future.

## Licensing

The primary license for the boilerplate is MIT, see [`LICENSE`](https://github.com/defi-wonderland/opUSDC/blob/main/LICENSE)

## Bridged USDC Standard Factory Disclaimer
Expand Down
Binary file added audits/spearbit.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion crytic-export/combined_solc.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'",
"lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'",
"prepare": "husky install",
"script:deploy": "forge script script/mainnet/deploy/Deploy.s.sol --slow --via-ir",
"script:deploy:broadcast": "forge script script/mainnet/deploy/Deploy.s.sol --broadcast --verify --slow --via-ir",
"script:migrate": "forge script script/mainnet/migration/MigrateToNative.s.sol --slow --via-ir",
"script:migrate:broadcast": "forge script script/mainnet/migration/MigrateToNative.s.sol --broadcast --verify --slow --via-ir",
"test": "forge test -vvv",
"test:fuzz": "echidna test/invariants/fuzz/OpUSDC.t.sol --config test/invariants/fuzz/config.yaml --contract OpUsdcTest",
"test:integration": "forge test --match-contract Integration -vvv",
Expand Down
49 changes: 49 additions & 0 deletions script/mainnet/deploy/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/Test.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';
import {USDCInitTxs} from 'src/contracts/utils/USDCInitTxs.sol';

contract Deploy is Script {
uint32 public constant MIN_GAS_LIMIT_DEPLOY = 9_000_000;
IL1OpUSDCFactory public immutable L1_FACTORY = IL1OpUSDCFactory(vm.envAddress('L1_FACTORY_MAINNET'));
address public immutable BRIDGED_USDC_IMPLEMENTATION = vm.envAddress('BRIDGED_USDC_IMPLEMENTATION');
address public immutable L1_MESSENGER = vm.envAddress('L1_MESSENGER');
string public chainName = vm.envString('CHAIN_NAME');
address public owner = vm.rememberKey(vm.envUint('MAINNET_PK'));

function run() public {
vm.createSelectFork(vm.rpcUrl(vm.envString('MAINNET_RPC')));
vm.startBroadcast(owner);

// NOTE: We have these hardcoded to default values, if used in product you will need to change them

bytes[] memory _usdcInitTxs = new bytes[](3);

_usdcInitTxs[0] = USDCInitTxs.INITIALIZEV2;
_usdcInitTxs[1] = USDCInitTxs.INITIALIZEV2_1;
_usdcInitTxs[2] = USDCInitTxs.INITIALIZEV2_2;

// Sanity check to ensure the caller of this script changed this value to the proper naming
assert(keccak256(_usdcInitTxs[0]) != keccak256(USDCInitTxs.INITIALIZEV2));

IL1OpUSDCFactory.L2Deployments memory _l2Deployments = IL1OpUSDCFactory.L2Deployments({
l2AdapterOwner: owner,
usdcImplAddr: BRIDGED_USDC_IMPLEMENTATION,
usdcInitTxs: _usdcInitTxs,
minGasLimitDeploy: MIN_GAS_LIMIT_DEPLOY
});

// Deploy the L2 contracts
(address _l1Adapter, address _l2Factory, address _l2Adapter) =
L1_FACTORY.deploy(L1_MESSENGER, owner, chainName, _l2Deployments);
vm.stopBroadcast();

/// NOTE: Hardcode the `L1_ADAPTER_BASE` and `L2_ADAPTER_BASE` addresses inside the `.env` file
console.log('L1 Adapter:', _l1Adapter);
console.log('L2 Factory:', _l2Factory);
console.log('L2 Adapter:', _l2Adapter);
}
}
7 changes: 4 additions & 3 deletions script/mainnet/deploy/DeployBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ pragma solidity 0.8.25;
import {Script} from 'forge-std/Script.sol';
import {console} from 'forge-std/Test.sol';
import {IL1OpUSDCFactory} from 'interfaces/IL1OpUSDCFactory.sol';
import {USDC_IMPLEMENTATION_CREATION_CODE} from 'script/utils/USDCImplementationCreationCode.sol';
import {USDCInitTxs} from 'src/contracts/utils/USDCInitTxs.sol';

contract DeployBase is Script {
address public constant L1_MESSENGER = 0x866E82a600A1414e583f7F13623F1aC5d58b0Afa;
uint32 public constant MIN_GAS_LIMIT_DEPLOY = 9_000_000;
string public constant CHAIN_NAME = 'Base';
IL1OpUSDCFactory public immutable L1_FACTORY = IL1OpUSDCFactory(vm.envAddress('L1_FACTORY_MAINNET'));
address public immutable USDC_BASE_IMPLEMENTATION = vm.envAddress('USDC_BASE_IMPLEMENTATION');
address public owner = vm.rememberKey(vm.envUint('MAINNET_PK'));

function run() public {
Expand All @@ -22,14 +23,14 @@ contract DeployBase is Script {

IL1OpUSDCFactory.L2Deployments memory _l2Deployments = IL1OpUSDCFactory.L2Deployments({
l2AdapterOwner: owner,
usdcImplementationInitCode: USDC_IMPLEMENTATION_CREATION_CODE,
usdcImplAddr: USDC_BASE_IMPLEMENTATION,
usdcInitTxs: _usdcInitTxs,
minGasLimitDeploy: MIN_GAS_LIMIT_DEPLOY
});

// Deploy the L2 contracts
(address _l1Adapter, address _l2Factory, address _l2Adapter) =
L1_FACTORY.deploy(L1_MESSENGER, owner, _l2Deployments);
L1_FACTORY.deploy(L1_MESSENGER, owner, CHAIN_NAME, _l2Deployments);
vm.stopBroadcast();

/// NOTE: Hardcode the `L1_ADAPTER_BASE` and `L2_ADAPTER_BASE` addresses inside the `.env` file
Expand Down
Loading

0 comments on commit bee2bc9

Please sign in to comment.