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

add OptimismMintableFiatTokenV2_2 #1

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions @types/AnyFiatTokenV2Instance.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { FiatTokenV2Instance } from "./generated/FiatTokenV2";
import { FiatTokenV2_1Instance } from "./generated/FiatTokenV2_1";
import { FiatTokenV2_2Instance } from "./generated/FiatTokenV2_2";
import { OptimismFiatTokenV2_2Instance } from "./generated/OptimismFiatTokenV2_2";
import { OptimismMintableFiatTokenV2_2Instance } from "./generated/OptimismMintableFiatTokenV2_2";

export interface FiatTokenV2_2InstanceExtended extends FiatTokenV2_2Instance {
permit?: typeof FiatTokenV2Instance.permit;
Expand All @@ -28,8 +28,8 @@ export interface FiatTokenV2_2InstanceExtended extends FiatTokenV2_2Instance {
cancelAuthorization?: typeof FiatTokenV2Instance.cancelAuthorization;
}

export interface OptimismFiatTokenV2_2InstanceExtended
extends OptimismFiatTokenV2_2Instance {
export interface OptimismMintableFiatTokenV2_2InstanceExtended
extends OptimismMintableFiatTokenV2_2Instance {
permit?: typeof FiatTokenV2Instance.permit;
transferWithAuthorization?: typeof FiatTokenV2Instance.transferWithAuthorization;
receiveWithAuthorization?: typeof FiatTokenV2Instance.receiveWithAuthorization;
Expand All @@ -40,4 +40,4 @@ export type AnyFiatTokenV2Instance =
| FiatTokenV2Instance
| FiatTokenV2_1Instance
| FiatTokenV2_2InstanceExtended
| OptimismFiatTokenV2_2InstanceExtended;
| OptimismMintableFiatTokenV2_2InstanceExtended;
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,20 @@ interface IOptimismMintableERC20 is IERC165 {

function burn(address _from, uint256 _amount) external;
}

/**
* @title IOptimismMintableFiatToken
* @author Lattice (https://lattice.xyz)
* @notice This interface adds the functions from IOptimismMintableERC20 missing from FiatTokenV2_2.
* It doesn't include `mint(address _to, uint256 _amount)`, as this function already exists
* on FiatTokenV2_2 (from FiatTokenV1), and can't be overridden. The only difference is a
* (bool) return type for the FiatTokenV1 version, which doesn't matter for consumers of
* IOptimismMintableERC20 that don't expect a return type.
*/
interface IOptimismMintableFiatToken is IERC165 {
function remoteToken() external view returns (address);

function bridge() external returns (address);

function burn(address _from, uint256 _amount) external;
}
43 changes: 0 additions & 43 deletions contracts/v2/OptimismFiatTokenV2_2.sol

This file was deleted.

74 changes: 74 additions & 0 deletions contracts/v2/OptimismMintableFiatTokenV2_2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import { FiatTokenV1 } from "../v1/FiatTokenV1.sol";
import { FiatTokenV2_2 } from "./FiatTokenV2_2.sol";
import {
IOptimismMintableERC20,
IOptimismMintableFiatToken
} from "./IOptimismMintableFiatToken.sol";
import { IERC165 } from "@openzeppelin/contracts/introspection/IERC165.sol";

/**
* @title OptimismMintableFiatTokenV2_2
* @author Lattice (https://lattice.xyz)
* @notice Adds compatibility with IOptimismMintableERC20 to the Bridged USDC Standard,
* so it can be used with Optimism's StandardBridge.
*/
contract OptimismMintableFiatTokenV2_2 is
FiatTokenV2_2,
IOptimismMintableFiatToken
{
address private immutable l1RemoteToken;

constructor(address _l1RemoteToken) public FiatTokenV2_2() {
l1RemoteToken = _l1RemoteToken;
}

function remoteToken() external override view returns (address) {
return l1RemoteToken;
}

function bridge() external override returns (address) {
// OP Stack L2StandardBridge predeploy
// https://specs.optimism.io/protocol/predeploys.html
return address(0x4200000000000000000000000000000000000010);
}

function supportsInterface(bytes4 interfaceId)
external
override
view
returns (bool)
{
return
interfaceId == type(IOptimismMintableERC20).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}

/**
* @notice Allows a minter to burn tokens for the given account.
* @dev The caller must be a minter, must not be blacklisted, and the amount to burn
* should be less than or equal to the account's balance.
* The function is a requirement for IOptimismMintableERC20.
* It is mostly equivalent to FiatTokenV1.burn, with the only change being
* the additional _from parameter to burn from instead of burning from msg.sender.
* @param _amount the amount of tokens to be burned.
*/
function burn(address _from, uint256 _amount)
external
override
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
{
uint256 balance = _balanceOf(_from);
require(_amount > 0, "FiatToken: burn amount not greater than 0");
require(balance >= _amount, "FiatToken: burn amount exceeds balance");

totalSupply_ = totalSupply_.sub(_amount);
_setBalance(_from, balance.sub(_amount));
emit Burn(_from, _amount);
emit Transfer(_from, address(0), _amount);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if emit Transfer makes sense here? We are not transferring tokens to address(0) as far as I can tell, just decreasing totalSupply and user's balance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is taken from FiatTokenV1, only change is the _from param instead of using msg.sender:

/**
* @notice Allows a minter to burn some of its own tokens.
* @dev The caller must be a minter, must not be blacklisted, and the amount to burn
* should be less than or equal to the account's balance.
* @param _amount the amount of tokens to be burned.
*/
function burn(uint256 _amount)
external
whenNotPaused
onlyMinters
notBlacklisted(msg.sender)
{
uint256 balance = _balanceOf(msg.sender);
require(_amount > 0, "FiatToken: burn amount not greater than 0");
require(balance >= _amount, "FiatToken: burn amount exceeds balance");
totalSupply_ = totalSupply_.sub(_amount);
_setBalance(msg.sender, balance.sub(_amount));
emit Burn(msg.sender, _amount);
emit Transfer(msg.sender, address(0), _amount);
}

Copy link

@karooolis karooolis Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the OpenZeppelin example though there is an _update function that increases the balance of address(0) - https://github.com/OpenZeppelin/openzeppelin-contracts/blob/e3786e63e6def6f3b71ce7b4b30906123bffe67c/contracts/token/ERC20/ERC20.sol#L206 so it makes sense to emit Transfer to address(0).

Although I see in Solmate's case the balance is also not increased. And also was not aware of such pattern. I suppose it's useful if you want to monitor for burned tokens, fair enough.

}
}
12 changes: 6 additions & 6 deletions scripts/deploy/DeployImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pragma solidity 0.6.12;

import { FiatTokenV2_2 } from "../../contracts/v2/FiatTokenV2_2.sol";
import {
OptimismFiatTokenV2_2
} from "../../contracts/v2/OptimismFiatTokenV2_2.sol";
OptimismMintableFiatTokenV2_2
} from "../../contracts/v2/OptimismMintableFiatTokenV2_2.sol";

/**
* @notice A utility contract that exposes a re-useable getOrDeployImpl function.
Expand Down Expand Up @@ -74,16 +74,16 @@ contract DeployImpl {
*
* @param impl configured of the implementation contract, where address(0) represents a new instance should be deployed
* @param l1RemoteToken token on the L1 corresponding to this bridged version of the token
* @return OptimismFiatTokenV2_2 newly deployed or loaded instance
* @return OptimismMintableFiatTokenV2_2 newly deployed or loaded instance
*/
function getOrDeployImpl(address impl, address l1RemoteToken)
internal
returns (FiatTokenV2_2)
{
OptimismFiatTokenV2_2 fiatTokenV2_2;
OptimismMintableFiatTokenV2_2 fiatTokenV2_2;

if (impl == address(0)) {
fiatTokenV2_2 = new OptimismFiatTokenV2_2(l1RemoteToken);
fiatTokenV2_2 = new OptimismMintableFiatTokenV2_2(l1RemoteToken);

// Initializing the implementation contract with dummy values here prevents
// the contract from being reinitialized later on with different values.
Expand All @@ -106,7 +106,7 @@ contract DeployImpl {
newSymbol: ""
});
} else {
fiatTokenV2_2 = OptimismFiatTokenV2_2(impl);
fiatTokenV2_2 = OptimismMintableFiatTokenV2_2(impl);
}

return fiatTokenV2_2;
Expand Down
4 changes: 2 additions & 2 deletions scripts/deploy/deploy-fiat-token.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { DeployImpl } from "./DeployImpl.sol";
import { FiatTokenProxy } from "../../contracts/v1/FiatTokenProxy.sol";
import { FiatTokenV2_2 } from "../../contracts/v2/FiatTokenV2_2.sol";
import {
OptimismFiatTokenV2_2
} from "../../contracts/v2/OptimismFiatTokenV2_2.sol";
OptimismMintableFiatTokenV2_2
} from "../../contracts/v2/OptimismMintableFiatTokenV2_2.sol";
import { MasterMinter } from "../../contracts/minting/MasterMinter.sol";

/**
Expand Down
10 changes: 5 additions & 5 deletions scripts/deploy/set-l2-standard-bridge.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ pragma solidity 0.6.12;
import "forge-std/console.sol"; // solhint-disable no-global-import, no-console
import { Script } from "forge-std/Script.sol";
import {
OptimismFiatTokenV2_2
} from "../../contracts/v2/OptimismFiatTokenV2_2.sol";
OptimismMintableFiatTokenV2_2
} from "../../contracts/v2/OptimismMintableFiatTokenV2_2.sol";
import { MasterMinter } from "../../contracts/minting/MasterMinter.sol";

/**
Expand All @@ -17,14 +17,14 @@ contract SetL2StandardBridge is Script {
*/
function run(
address masterMinterOwner,
OptimismFiatTokenV2_2 optimismFiatTokenV2_2
OptimismMintableFiatTokenV2_2 optimismMintableFiatTokenV2_2
) external {
address l2StandardBridge = optimismFiatTokenV2_2.bridge();
address l2StandardBridge = optimismMintableFiatTokenV2_2.bridge();
if (l2StandardBridge == address(0)) {
revert("Expected no-zero bridge address");
}
MasterMinter masterMinter = MasterMinter(
optimismFiatTokenV2_2.masterMinter()
optimismMintableFiatTokenV2_2.masterMinter()
);

vm.startBroadcast(masterMinterOwner);
Expand Down
4 changes: 3 additions & 1 deletion test/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
FiatTokenV2_1Instance,
FiatTokenV2_2Instance,
FiatTokenV2Instance,
OptimismMintableFiatTokenV2_2Instance,
} from "../../@types/generated";
import _ from "lodash";

Expand Down Expand Up @@ -140,7 +141,8 @@ export async function initializeToVersion(
| FiatTokenV1_1Instance
| FiatTokenV2Instance
| FiatTokenV2_1Instance
| FiatTokenV2_2Instance,
| FiatTokenV2_2Instance
| OptimismMintableFiatTokenV2_2Instance,
version: "1" | "1.1" | "2" | "2.1" | "2.2",
fiatTokenOwner: string,
lostAndFound: string,
Expand Down
6 changes: 4 additions & 2 deletions test/helpers/storageSlots.behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ const FiatTokenV1_1 = artifacts.require("FiatTokenV1_1");
const FiatTokenV2 = artifacts.require("FiatTokenV2");
const FiatTokenV2_1 = artifacts.require("FiatTokenV2_1");
const FiatTokenV2_2 = artifacts.require("FiatTokenV2_2");
const OptimismFiatTokenV2_2 = artifacts.require("OptimismFiatTokenV2_2");
const OptimismMintableFiatTokenV2_2 = artifacts.require(
"OptimismMintableFiatTokenV2_2"
);

export const STORAGE_SLOT_NUMBERS = {
_deprecatedBlacklisted: 3,
Expand Down Expand Up @@ -117,7 +119,7 @@ export function usesOriginalStorageSlotPositions<
}
if (version >= 2.2) {
const proxyAsFiatTokenV2_2 = constructorArgs
? await OptimismFiatTokenV2_2.at(proxy.address)
? await OptimismMintableFiatTokenV2_2.at(proxy.address)
: await FiatTokenV2_2.at(proxy.address);
await proxyAsFiatTokenV2_2.initializeV2_2([], symbol);
}
Expand Down
12 changes: 8 additions & 4 deletions test/misc/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,17 @@ describe(`gas costs for version ${TARGET_VERSION}`, () => {
});

it("burn() entire balance", async () => {
const tx = await fiatToken.burn(entireBalance, { from: fiatTokenOwner });
console.log(consoleMessage, tx.receipt.gasUsed);
if ("burn" in fiatToken) {
const tx = await fiatToken.burn(entireBalance, { from: fiatTokenOwner });
console.log(consoleMessage, tx.receipt.gasUsed);
}
});

it("burn() partial balance", async () => {
const tx = await fiatToken.burn(partialBalance, { from: fiatTokenOwner });
console.log(consoleMessage, tx.receipt.gasUsed);
if ("burn" in fiatToken) {
const tx = await fiatToken.burn(partialBalance, { from: fiatTokenOwner });
console.log(consoleMessage, tx.receipt.gasUsed);
}
});

it("transfer() where both parties have a balance before and after", async () => {
Expand Down
22 changes: 13 additions & 9 deletions test/v2/OptimismFiatTokenV2_2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import BN from "bn.js";
import {
AnyFiatTokenV2Instance,
OptimismFiatTokenV2_2InstanceExtended,
OptimismMintableFiatTokenV2_2InstanceExtended,
} from "../../@types/AnyFiatTokenV2Instance";
import {
expectRevert,
Expand Down Expand Up @@ -51,16 +51,18 @@ import { behavesLikeFiatTokenV22 } from "./v2_2.behavior";

const FiatTokenProxy = artifacts.require("FiatTokenProxy");
const FiatTokenV2_1 = artifacts.require("FiatTokenV2_1");
const OptimismFiatTokenV2_2 = artifacts.require("OptimismFiatTokenV2_2");
const OptimismMintableFiatTokenV2_2 = artifacts.require(
"OptimismMintableFiatTokenV2_2"
);

describe("OptimismFiatTokenV2_2", () => {
describe("OptimismMintableFiatTokenV2_2", () => {
const newSymbol = "USDCUSDC";
const fiatTokenOwner = HARDHAT_ACCOUNTS[9];
const lostAndFound = HARDHAT_ACCOUNTS[2];
const proxyOwnerAccount = HARDHAT_ACCOUNTS[14];
const l1RemoteToken = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";

let fiatToken: OptimismFiatTokenV2_2InstanceExtended;
let fiatToken: OptimismMintableFiatTokenV2_2InstanceExtended;

const getFiatToken = (
signatureBytesType: SignatureBytesType
Expand All @@ -73,11 +75,11 @@ describe("OptimismFiatTokenV2_2", () => {

before(async () => {
await linkLibraryToTokenContract(FiatTokenV2_1);
await linkLibraryToTokenContract(OptimismFiatTokenV2_2);
await linkLibraryToTokenContract(OptimismMintableFiatTokenV2_2);
});

beforeEach(async () => {
fiatToken = await OptimismFiatTokenV2_2.new(l1RemoteToken);
fiatToken = await OptimismMintableFiatTokenV2_2.new(l1RemoteToken);
await initializeToVersion(fiatToken, "2.1", fiatTokenOwner, lostAndFound);
});

Expand Down Expand Up @@ -154,7 +156,9 @@ describe("OptimismFiatTokenV2_2", () => {
});

// Validate that isBlacklisted returns true for every accountsToBlacklist.
const _proxyAsV2_2 = await OptimismFiatTokenV2_2.at(_proxy.address);
const _proxyAsV2_2 = await OptimismMintableFiatTokenV2_2.at(
_proxy.address
);
const areAccountsBlacklisted = await Promise.all(
accountsToBlacklist.map((account) =>
_proxyAsV2_2.isBlacklisted(account)
Expand Down Expand Up @@ -216,7 +220,7 @@ describe("OptimismFiatTokenV2_2", () => {
behavesLikeFiatTokenV22(getFiatToken(SignatureBytesType.Packed));
console.log("before");
usesOriginalStorageSlotPositions({
Contract: OptimismFiatTokenV2_2,
Contract: OptimismMintableFiatTokenV2_2,
version: 2.2,
constructorArgs: [l1RemoteToken],
});
Expand All @@ -240,7 +244,7 @@ describe("OptimismFiatTokenV2_2", () => {
* here we re-assign the overloaded method definition to the method name shorthand.
*/
export function initializeOverloadedMethods(
fiatToken: OptimismFiatTokenV2_2InstanceExtended,
fiatToken: OptimismMintableFiatTokenV2_2InstanceExtended,
signatureBytesType: SignatureBytesType
): void {
if (signatureBytesType == SignatureBytesType.Unpacked) {
Expand Down