Skip to content
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