Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
78393c3
Update MockOKB contract to mint a total supply of 660,000
doutv Oct 14, 2025
d902be4
Separate MockOKB and setupCGT into 2 scripts
doutv Oct 14, 2025
3a0d7e9
Enhance cgt-only-contract.sh for improved OKB token handling
doutv Oct 14, 2025
70b82ee
Add require statement for gas token address validation in SetupCustom…
doutv Oct 14, 2025
244a801
Update example.env to include OKB token address configuration
doutv Oct 14, 2025
7df7b49
Refactor Custom Gas Token setup and verification scripts
doutv Oct 14, 2025
afd0281
Remove SystemConfig UpdateType GAS_PAYING_TOKEN event. gasPayingToken…
doutv Oct 15, 2025
f3f868e
Update Dockerfile and .dockerignore for improved build process
doutv Oct 15, 2025
e089378
refine okbAdapter contract
albbm Oct 15, 2025
06bc178
Optimize opstack Dockerfile build time
doutv Oct 15, 2025
91e3024
Refactor output handling in setup_cgt function
doutv Oct 15, 2025
5b17a11
Update console log output in SetupCustomGasToken script
doutv Oct 15, 2025
8eea8b6
Update environment configuration in test script
doutv Oct 15, 2025
4fa4c8d
Merge remote-tracking branch 'origin/pre-release' into fix/cgt-contract
doutv Oct 15, 2025
2430a94
Remove duplicate balanceOf function from IOKB interface
albbm Oct 15, 2025
199b7d0
fix script
doutv Oct 15, 2025
f93104e
Merge branch 'fix/cgt-contract' into review/cgt-contract
doutv Oct 15, 2025
95e3ffe
Revert "Merge branch 'dev' into pre-release"
doutv Oct 15, 2025
85b5250
forge fmt
doutv Oct 15, 2025
5ba352b
Optimize dockerfile build time (#63)
doutv Oct 15, 2025
f2e23d1
Add read access for test configuration directory in foundry.toml Add …
albbm Oct 16, 2025
8b818bf
Refactor DepositedOKBAdapter and introduce ERC20Rescuer contract
doutv Oct 16, 2025
e2a5080
Add new error for invalid gas token in IOptimismPortal2 interface
doutv Oct 16, 2025
4bd32ff
Enhance DepositedOKBAdapter with Whitelist Functionality and Owner Co…
doutv Oct 16, 2025
7b27238
Enhance DepositedOKBAdapter with Transfer Failure Handling
doutv Oct 16, 2025
caf3034
Refactor transfer error handling in DepositedOKBAdapter
doutv Oct 16, 2025
cf5ed5b
Add comprehensive tests for DepositedOKBAdapter functionality
doutv Oct 16, 2025
718d5f2
Update Dockerfile to use latest op-contracts image
doutv Oct 17, 2025
2896cb3
Refactor SetupCustomGasToken script to remove OKBBurner implementation
doutv Oct 17, 2025
b676c86
Merge pull request #70 from okx/fix/okb-adapter
doutv Oct 17, 2025
22f543d
Add tests for depositERC20Transaction in OptimismPortal2
doutv Oct 17, 2025
5dd3108
Merge pull request #78 from okx/feat/add-cgt-contract-test
doutv Oct 17, 2025
5ba027e
forge fmt
doutv Oct 17, 2025
2abdab0
Merge remote-tracking branch 'origin/pre-release' into review/cgt-con…
doutv Oct 17, 2025
aeb3b51
add OKB mainnet address
doutv Oct 17, 2025
003b931
Refactor DepositedOKBAdapter to clarify token minting and transfer re…
doutv Oct 17, 2025
a2acfcd
fix lint
doutv Oct 17, 2025
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
1 change: 1 addition & 0 deletions packages/contracts-bedrock/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fs_permissions = [
{ access='read-write', path='./.testdata/' },
{ access='read', path='./kout-deployment' },
{ access='read', path='./test/fixtures' },
{ access='read', path='../../test/config-op/' },
{ access='read', path='./lib/superchain-registry/superchain/configs/' },
{ access='read', path='./lib/superchain-registry/validation/standard/' },
]
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts-bedrock/interfaces/L1/IOKB.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

interface IOKB is IERC20 {
interface IOKB is IERC20, IERC20Metadata {
function triggerBridge() external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase {
error OptimismPortal_Unproven();
error OptimismPortal_InvalidLockboxState();
error OptimismPortal_OnlyCustomGasToken();
error OptimismPortal_InvalidGasToken();
error OutOfGas();
error UnexpectedList();
error UnexpectedString();
Expand Down
74 changes: 74 additions & 0 deletions packages/contracts-bedrock/scripts/DeployMockOKB.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";

// Contracts
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

/// @title MockOKB
/// @notice Mock OKB token for testing custom gas token setup
contract MockOKB is ERC20, ERC20Burnable {
constructor() ERC20("Mock OKB", "OKB") {
_mint(msg.sender, 660000 * 10 ** decimals());
}

function decimals() public pure override returns (uint8) {
return 18;
}

/// @notice Burn all tokens of msg.sender
function triggerBridge() external {
_burn(msg.sender, balanceOf(msg.sender));
}
}

/// @title DeployMockOKB
/// @notice Foundry script to deploy MockOKB token for testing
/// @dev This script deploys a mock OKB token with 660,000 initial supply
contract DeployMockOKB is Script {
// Deployed contract
MockOKB okbToken;
address deployerAddress;

function setUp() public {
// Get deployer address from msg.sender (set by forge script --private-key)
deployerAddress = msg.sender;
console.log("Deployer address:", deployerAddress);
}

function run() public {
console.log("\n=== Deploying Mock OKB Token ===\n");

vm.startBroadcast(msg.sender);

// Deploy Mock OKB Token
deployMockOKB();

vm.stopBroadcast();

// Print deployment summary
printDeploymentSummary();
}

/// @notice Deploy mock OKB token with 660,000 supply
function deployMockOKB() internal {
okbToken = new MockOKB();
console.log("MockOKB deployed at:", address(okbToken));
}

/// @notice Print deployment summary with token details
function printDeploymentSummary() internal view {
console.log("\n=== Deployment Summary ===");
console.log("MockOKB Address:", address(okbToken));
console.log("Token name:", okbToken.name());
console.log("Token symbol:", okbToken.symbol());
console.log("Token decimals:", okbToken.decimals());
console.log("Total supply:", okbToken.totalSupply() / 1e18, "OKB");
console.log("Deployer balance:", okbToken.balanceOf(deployerAddress) / 1e18, "OKB");
console.log("\nEnvironment variable to set:");
console.log("export OKB_TOKEN_ADDRESS=", address(okbToken));
}
}
207 changes: 91 additions & 116 deletions packages/contracts-bedrock/scripts/SetupCustomGasToken.s.sol
Original file line number Diff line number Diff line change
@@ -1,75 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {Script} from "forge-std/Script.sol";
import {console2 as console} from "forge-std/console2.sol";
import {stdJson} from "forge-std/StdJson.sol";
import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";
import { stdJson } from "forge-std/StdJson.sol";

// Contracts
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {DepositedOKBAdapter} from "src/L1/DepositedOKBAdapter.sol";
import {OKBBurner} from "src/L1/OKBBurner.sol";

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { DepositedOKBAdapter } from "src/L1/DepositedOKBAdapter.sol";

// Interfaces
import {IOKB} from "interfaces/L1/IOKB.sol";
import {ISystemConfig} from "interfaces/L1/ISystemConfig.sol";
import {IOptimismPortal2} from "interfaces/L1/IOptimismPortal2.sol";
import {IL1Block} from "interfaces/L2/IL1Block.sol";
import { IOKB } from "interfaces/L1/IOKB.sol";
import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { Constants } from "src/libraries/Constants.sol";
import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol";
import { IL1Block } from "interfaces/L2/IL1Block.sol";

// Libraries
import {Features} from "src/libraries/Features.sol";
import {GasPayingToken} from "src/libraries/GasPayingToken.sol";
import {LibString} from "@solady/utils/LibString.sol";
import {Predeploys} from "src/libraries/Predeploys.sol";

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

/// @title MockOKB
/// @notice Mock OKB token for testing custom gas token setup
contract MockOKB is ERC20, ERC20Burnable {
constructor() ERC20("Mock OKB", "OKB") {
_mint(msg.sender, 660000 * 10 ** decimals());
}

function decimals() public pure override returns (uint8) {
return 18;
}

/// @notice Burn all tokens of msg.sender
function triggerBridge() external {
_burn(msg.sender, balanceOf(msg.sender));
}
}
import { Features } from "src/libraries/Features.sol";
import { GasPayingToken } from "src/libraries/GasPayingToken.sol";
import { LibString } from "@solady/utils/LibString.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";

/// @title SetupCustomGasToken
/// @notice Foundry script to set up and verify custom gas token configuration
/// @dev This script:
/// 1. Deploys mock OKB token with triggerBridge functionality
/// 2. Deploys OKBBurner implementation contract for minimal proxy pattern
/// 3. Deploys DepositedOKBAdapter with burner implementation reference
/// 1. Reads OKB token address from environment variable
/// 2. Deploys DepositedOKBAdapter that handles OKB burning internally
/// 3. Adds deployer address to whitelist for deposits
/// 4. Sets gas paying token in SystemConfig storage
/// 5. Verifies all configurations on L1
/// 6. Provides test function for deposit functionality
contract SetupCustomGasToken is Script {
using stdJson for string;

// Addresses to be loaded from deployment artifacts
address systemConfigProxy;
address optimismPortalProxy;
address l1BlockAddress;
address deployerAddress;
address okbTokenAddress;

// Deployed contracts
MockOKB okbToken;
OKBBurner burnerImplementation;
IOKB okbToken;
DepositedOKBAdapter adapter;

// Configuration
bytes32 constant TOKEN_NAME = bytes32("Mock OKB");
bytes32 constant TOKEN_SYMBOL = bytes32("OKB");
uint8 constant TOKEN_DECIMALS = 18;

function setUp() public {
// Get deployer address from msg.sender (set by forge script --private-key)
deployerAddress = msg.sender;
Expand All @@ -78,122 +52,123 @@ contract SetupCustomGasToken is Script {
// Parse addresses from environment variables
systemConfigProxy = vm.envAddress("SYSTEM_CONFIG_PROXY_ADDRESS");
optimismPortalProxy = vm.envAddress("OPTIMISM_PORTAL_PROXY_ADDRESS");
okbTokenAddress = vm.envAddress("OKB_TOKEN_ADDRESS");

console.log("SystemConfig Proxy:", systemConfigProxy);
console.log("OptimismPortal Proxy:", optimismPortalProxy);
console.log("OKB Token Address:", okbTokenAddress);

// L1Block is a predeploy on L2 at a fixed address
l1BlockAddress = Predeploys.L1_BLOCK_ATTRIBUTES;
console.log("L1Block Address:", l1BlockAddress);
// Initialize OKB token interface
okbToken = IOKB(okbTokenAddress);
}

function run() public {
console.log("\n=== Starting Custom Gas Token Setup ===\n");

vm.startBroadcast(msg.sender);

// Step 1: Deploy Mock OKB Token
console.log("Step 1: Deploying Mock OKB Token...");
deployMockOKB();
preCheck();

// Step 2: Deploy OKBBurner Implementation
console.log("\nStep 2: Deploying OKBBurner Implementation...");
deployBurnerImplementation();
vm.startBroadcast(msg.sender);

// Step 3: Deploy DepositedOKBAdapter
console.log("\nStep 3: Deploying DepositedOKBAdapter...");
deployAdapter();

// Step 4: Set gas paying token in SystemConfig storage
console.log("\nStep 4: Setting gas paying token in SystemConfig storage...");
setupWhitelist();

setGasPayingToken();

vm.stopBroadcast();

// Step 5: Verify all configurations
console.log("\n=== Verification Phase ===\n");
verifyL1Configuration();
postCheck();
}

/// @notice Deploy mock OKB token with 21M supply
function deployMockOKB() internal {
okbToken = new MockOKB();
console.log(" MockOKB deployed at:", address(okbToken));
console.log(" Token name:", okbToken.name());
console.log(" Token symbol:", okbToken.symbol());
console.log(" Token decimals:", okbToken.decimals());
console.log(" Total supply:", okbToken.totalSupply() / 1e18, "OKB");
console.log(" Deployer balance:", okbToken.balanceOf(deployerAddress) / 1e18, "OKB");
/// @notice Pre-check L1 Configuration
function preCheck() internal view {
console.log("Pre-check L1 Configuration...\n");
ISystemConfig systemConfig = ISystemConfig(systemConfigProxy);
bool isCustomGasToken = systemConfig.isCustomGasToken();
require(isCustomGasToken, "FAILED: SystemConfig custom gas token not enabled");

(address tokenAddr, uint8 decimals) = systemConfig.gasPayingToken();
string memory name = systemConfig.gasPayingTokenName();
string memory symbol = systemConfig.gasPayingTokenSymbol();
console.log("SystemConfig.gasPayingToken():");
console.log(" Address:", tokenAddr);
console.log(" Decimals:", decimals);
console.log(" Name:", name);
console.log(" Symbol:", symbol);
require(tokenAddr == Constants.ETHER, "FAILED: GasPayingToken already set");
}

/// @notice Deploy OKBBurner implementation contract
function deployBurnerImplementation() internal {
burnerImplementation = new OKBBurner(address(okbToken)); // adapter address will be set later
console.log(" OKBBurner Implementation deployed at:", address(burnerImplementation));
console.log(" Burner OKB token:", address(burnerImplementation.OKB()));
/// @notice Set up whitelist for authorized depositors
function setupWhitelist() internal {
console.log(" Adding deployer to whitelist...");
address[] memory addresses = new address[](1);
addresses[0] = deployerAddress;
adapter.addToWhitelistBatch(addresses);
console.log(" Deployer whitelisted successfully:", deployerAddress);
}

/// @notice Deploy DepositedOKBAdapter
function deployAdapter() internal {
adapter = new DepositedOKBAdapter(
address(okbToken),
payable(optimismPortalProxy),
address(burnerImplementation)
);
adapter = new DepositedOKBAdapter(okbTokenAddress, payable(optimismPortalProxy), deployerAddress);
console.log(" DepositedOKBAdapter deployed at:", address(adapter));
console.log(" Adapter name:", adapter.name());
console.log(" Adapter symbol:", adapter.symbol());
console.log(" OKB token:", address(adapter.OKB()));
console.log(" Portal:", address(adapter.PORTAL()));
console.log(" Burner implementation:", adapter.BURNER_IMPLEMENTATION());
}

/// @notice Set gas paying token in SystemConfig storage
/// @dev This writes to the GasPayingToken storage slots directly
function setGasPayingToken() internal {
ISystemConfig systemConfig = ISystemConfig(systemConfigProxy);
// adapter is the gas paying token
systemConfig.setGasPayingToken(address(adapter), TOKEN_DECIMALS, TOKEN_NAME, TOKEN_SYMBOL);
// Convert string to bytes32 for SystemConfig function
bytes32 nameBytes32 = bytes32(bytes(okbToken.name()));
bytes32 symbolBytes32 = bytes32(bytes(okbToken.symbol()));
systemConfig.setGasPayingToken(address(adapter), okbToken.decimals(), nameBytes32, symbolBytes32);
}

/// @notice Verify L1 configuration
function verifyL1Configuration() internal view {
console.log("Step 5: Verifying L1 Configuration...\n");
/// @notice Post-check L1 configuration
function postCheck() internal view {
console.log("\nPost-check L1 Configuration...\n");

ISystemConfig systemConfig = ISystemConfig(systemConfigProxy);
// Check 1: SystemConfig isCustomGasToken
bool isCustomGasToken = systemConfig.isCustomGasToken();
console.log(" [CHECK 1] SystemConfig.isCustomGasToken():", isCustomGasToken);
require(isCustomGasToken, "FAILED: SystemConfig custom gas token not enabled");

// Check 2: SystemConfig gasPayingToken
// Check SystemConfig gasPayingToken
(address tokenAddr, uint8 decimals) = systemConfig.gasPayingToken();
console.log(" [CHECK 2] SystemConfig.gasPayingToken():");
string memory name = systemConfig.gasPayingTokenName();
string memory symbol = systemConfig.gasPayingTokenSymbol();
console.log("SystemConfig.gasPayingToken():");
console.log(" Address:", tokenAddr);
console.log(" Decimals:", decimals);
console.log(" Name:", name);
console.log(" Symbol:", symbol);
require(tokenAddr == address(adapter), "FAILED: Token address mismatch");
require(decimals == 18, "FAILED: Token decimals must be 18");
require(decimals == okbToken.decimals(), "FAILED: Token decimals mismatch");
require(
keccak256(abi.encodePacked(name)) == keccak256(abi.encodePacked(okbToken.name())),
"FAILED: Token name mismatch"
);
require(
keccak256(abi.encodePacked(symbol)) == keccak256(abi.encodePacked(okbToken.symbol())),
"FAILED: Token symbol mismatch"
);

// Check 4: DepositedOKBAdapter configuration
console.log(" [CHECK 4] DepositedOKBAdapter configuration:");
console.log(" OKB Token:", address(adapter.OKB()));
console.log(" Portal:", address(adapter.PORTAL()));
require(address(adapter.OKB()) == address(okbToken), "FAILED: Adapter OKB mismatch");
// Check DepositedOKBAdapter configuration
require(address(adapter.OKB()) == okbTokenAddress, "FAILED: Adapter OKB mismatch");
require(address(adapter.PORTAL()) == optimismPortalProxy, "FAILED: Adapter portal mismatch");
require(adapter.owner() == deployerAddress, "FAILED: Adapter owner mismatch");

// Check 5: OKBBurner Implementation configuration
console.log(" [CHECK 5] OKBBurner Implementation configuration:");
console.log(" OKB Token:", address(burnerImplementation.OKB()));
require(address(burnerImplementation.OKB()) == address(okbToken), "FAILED: Burner OKB mismatch");
// Check adapter has preminted total supply
uint256 adapterBalance = adapter.balanceOf(address(adapter));
uint256 expectedBalance = okbToken.totalSupply();
console.log(" [CHECK 6] Adapter balance:", adapterBalance);
console.log(" [CHECK 6] Expected balance (OKB total supply):", expectedBalance);
require(adapterBalance == expectedBalance, "FAILED: Adapter balance should equal OKB total supply");

// Check 6: Adapter burner implementation reference
console.log(" [CHECK 6] Adapter burner implementation:");
console.log(" Burner Implementation:", adapter.BURNER_IMPLEMENTATION());
require(adapter.BURNER_IMPLEMENTATION() == address(burnerImplementation), "FAILED: Adapter burner implementation mismatch");
// Check whitelist configuration
console.log(" [CHECK 7] Verifying deployer whitelist...");
require(adapter.whitelist(deployerAddress), "FAILED: Deployer address not whitelisted");
console.log(" [CHECK 7] Deployer whitelist verified:", deployerAddress);

// Check 7: Adapter approval to portal
// Check Adapter approval to portal (should be zero initially)
uint256 allowance = adapter.allowance(address(adapter), optimismPortalProxy);
console.log(" [CHECK 7] Adapter approval to Portal:", allowance);
require(allowance == type(uint256).max, "FAILED: Adapter should pre-approve portal");
console.log(" [CHECK 8] Adapter approval to Portal:", allowance);
require(allowance == 0, "FAILED: Adapter should not pre-approve portal");
}
}
Loading