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

L2 pufETH #82

Merged
merged 6 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ jobs:
with:
check_hidden: true
check_filenames: true
ignore_words_list: amountIn
skip: package-lock.json,*.pdf,./.git

2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ optimizer = true
optimizer_runs = 200
evm_version = "cancun" # is live on mainnet
seed = "0x1337"
solc = "0.8.24"
solc = "0.8.26"
# via_ir = true

[fmt]
Expand Down
97 changes: 97 additions & 0 deletions script/DeployL2XPufETH.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

import "forge-std/Script.sol";
import { stdJson } from "forge-std/StdJson.sol";
import { BaseScript } from ".//BaseScript.s.sol";
import { xPufETH } from "../src/l2/xPufETH.sol";
import { Initializable } from "openzeppelin/proxy/utils/Initializable.sol";
import { Timelock } from "../src/Timelock.sol";
import { ERC1967Proxy } from "openzeppelin/proxy/ERC1967/ERC1967Proxy.sol";
import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol";
import { ROLE_ID_DAO, ROLE_ID_OPERATIONS_MULTISIG } from "./Roles.sol";

/**
* @title DeployL2XPufETH
* @author Puffer Finance
* @notice Deploy XPufETH
* @dev
*
*
* NOTE:
*
* If you ran the deployment script, but did not `--broadcast` the transaction, it will still update your local chainId-deployment.json file.
* Other scripts will fail because addresses will be updated in deployments file, but the deployment never happened.
*
* BaseScript.sol holds the private key logic, if you don't have `PK` ENV variable, it will use the default one PK from `makeAddr("pufferDeployer")`
*
* PK=a990c824d7f6928806d93674ef4acd4b240ad60c9ce575777c87b36f9a3c32a8 forge script script/DeployL2XPufETH.s.sol:DeployL2XPufETH -vvvv --rpc-url=https://holesky.gateway.tenderly.co/5ovlGAOeSvuI3UcQD2PoSD --broadcast
*/
contract DeployL2XPufETH is BaseScript {
address operationsMultisig = vm.envOr("OPERATIONS_MULTISIG", makeAddr("operationsMultisig"));
address pauserMultisig = vm.envOr("PAUSER_MULTISIG", makeAddr("pauserMultisig"));
address communityMultisig = vm.envOr("COMMUNITY_MULTISIG", makeAddr("communityMultisig"));

address _CONNEXT = 0x8247ed6d0a344eeae4edBC7e44572F1B70ECA82A; //@todo change for mainnet
uint256 _MINTING_LIMIT = 1000 * 1e18; //@todo
uint256 _BURNING_LIMIT = 1000 * 1e18; //@todo

function run() public broadcast {
AccessManager accessManager = new AccessManager(_broadcaster);

console.log("AccessManager", address(accessManager));

operationsMultisig = _broadcaster;
pauserMultisig = _broadcaster;
communityMultisig = _broadcaster;

Timelock timelock = new Timelock({
accessManager: address(accessManager),
communityMultisig: communityMultisig,
operationsMultisig: operationsMultisig,
pauser: pauserMultisig,
initialDelay: 7 days
});

console.log("Timelock", address(timelock));

xPufETH newImplementation = new xPufETH();
console.log("XERC20PufferVaultImplementation", address(newImplementation));

bytes32 xPufETHSalt = bytes32("xPufETH");

vm.expectEmit(true, true, true, true);
emit Initializable.Initialized(1);
ERC1967Proxy xPufETHProxy = new ERC1967Proxy{ salt: xPufETHSalt }(
address(newImplementation), abi.encodeCall(xPufETH.initialize, (address(accessManager)))
);
console.log("xPufETHProxy", address(xPufETHProxy));

bytes memory data = abi.encodeWithSelector(xPufETH.setLimits.selector, _CONNEXT, _MINTING_LIMIT, _BURNING_LIMIT);

accessManager.execute(address(xPufETHProxy), data);

bytes4[] memory daoSelectors = new bytes4[](2);
daoSelectors[0] = xPufETH.setLockbox.selector;
daoSelectors[1] = xPufETH.setLimits.selector;

bytes4[] memory publicSelectors = new bytes4[](2);
publicSelectors[0] = xPufETH.mint.selector;
publicSelectors[1] = xPufETH.burn.selector;

// Setup Access
// Public selectors
accessManager.setTargetFunctionRole(address(xPufETHProxy), publicSelectors, accessManager.PUBLIC_ROLE());
// Dao selectors
accessManager.setTargetFunctionRole(address(xPufETHProxy), daoSelectors, ROLE_ID_DAO);

accessManager.grantRole(accessManager.ADMIN_ROLE(), address(timelock), 0);

//@todo replace with dao and ops multisigs for mainnet
accessManager.grantRole(ROLE_ID_DAO, _broadcaster, 0);
accessManager.grantRole(ROLE_ID_OPERATIONS_MULTISIG, _broadcaster, 0);

//@todo revoke on mainnet
// accessManager.revokeRole(accessManager.ADMIN_ROLE(), _broadcaster);
}
}
3 changes: 3 additions & 0 deletions script/Roles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ uint64 constant ADMIN_ROLE = 0;

// Allowlister role for AVSContractsRegistry
uint64 constant ROLE_ID_AVS_COORDINATOR_ALLOWLISTER = 5;

// Lockbox role for ETH Mainnet
uint64 constant ROLE_ID_LOCKBOX = 7;
119 changes: 119 additions & 0 deletions src/XERC20Lockbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4 <0.9.0;

import { IXERC20 } from "./interface/IXERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IXERC20Lockbox } from "./interface/IXERC20Lockbox.sol";

contract XERC20Lockbox is IXERC20Lockbox {
using SafeERC20 for IERC20;
using SafeCast for uint256;

/**
* @notice The XERC20 token of this contract
*/
IXERC20 public immutable XERC20;

/**
* @notice The ERC20 token of this contract
*/
IERC20 public immutable ERC20;

/**
* @notice Constructor
*
* @param xerc20 The address of the XERC20 contract
* @param erc20 The address of the ERC20 contract
*/
constructor(address xerc20, address erc20) {
XERC20 = IXERC20(xerc20);
ERC20 = IERC20(erc20);
}

/**
* @notice Deposit ERC20 tokens into the lockbox
*
* @param amount The amount of tokens to deposit
*/
function deposit(uint256 amount) external {
_deposit(msg.sender, amount);
}

/**
* @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user
*
* @param to The user to send the XERC20 to
* @param amount The amount of tokens to deposit
*/
function depositTo(address to, uint256 amount) external {
_deposit(to, amount);
}

/**
* @notice Not a native asset
*/
function depositNativeTo(address) public payable {
revert IXERC20Lockbox_NotNative();
}

/**
* @notice Deposit native tokens into the lockbox
*/
function depositNative() public payable {
revert IXERC20Lockbox_NotNative();
}

/**
* @notice Withdraw ERC20 tokens from the lockbox
*
* @param amount The amount of tokens to withdraw
*/
function withdraw(uint256 amount) external {
_withdraw(msg.sender, amount);
}

/**
* @notice Withdraw tokens from the lockbox
*
* @param to The user to withdraw to
* @param amount The amount of tokens to withdraw
*/
function withdrawTo(address to, uint256 amount) external {
_withdraw(to, amount);
}

/**
* @notice Withdraw tokens from the lockbox
*
* @param to The user to withdraw to
* @param amount The amount of tokens to withdraw
*/
function _withdraw(address to, uint256 amount) internal {
emit Withdraw(to, amount);

XERC20.burn(msg.sender, amount);
ERC20.safeTransfer(to, amount);
}

/**
* @notice Deposit tokens into the lockbox
*
* @param to The address to send the XERC20 to
* @param amount The amount of tokens to deposit
*/
function _deposit(address to, uint256 amount) internal {
ERC20.safeTransferFrom(msg.sender, address(this), amount);
XERC20.mint(to, amount);

emit Deposit(to, amount);
}

/**
* @notice Fallback function to deposit native tokens
*/
receive() external payable {
depositNative();
}
}
125 changes: 125 additions & 0 deletions src/interface/IXERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.4 <0.9.0;

interface IXERC20 {
/**
* @notice Emits when a lockbox is set
*
* @param lockbox The address of the lockbox
*/
event LockboxSet(address lockbox);

/**
* @notice Emits when a limit is set
*
* @param mintingLimit The updated minting limit we are setting to the bridge
* @param burningLimit The updated burning limit we are setting to the bridge
* @param bridge The address of the bridge we are setting the limit too
*/
event BridgeLimitsSet(uint256 mintingLimit, uint256 burningLimit, address indexed bridge);

/**
* @notice Reverts when a user with too low of a limit tries to call mint/burn
*/
error IXERC20_NotHighEnoughLimits();

/**
* @notice Reverts when caller is not the factory
*/
error IXERC20_NotFactory();

/**
* @notice Reverts when limits are too high
*/
error IXERC20_LimitsTooHigh();

/**
* @notice Contains the full minting and burning data for a particular bridge
*
* @param minterParams The minting parameters for the bridge
* @param burnerParams The burning parameters for the bridge
*/
struct Bridge {
BridgeParameters minterParams;
BridgeParameters burnerParams;
}

/**
* @notice Contains the mint or burn parameters for a bridge
*
* @param timestamp The timestamp of the last mint/burn
* @param ratePerSecond The rate per second of the bridge
* @param maxLimit The max limit of the bridge
* @param currentLimit The current limit of the bridge
*/
struct BridgeParameters {
uint256 timestamp;
uint256 ratePerSecond;
uint256 maxLimit;
uint256 currentLimit;
}

/**
* @notice Sets the lockbox address
*
* @param lockbox The address of the lockbox
*/
function setLockbox(address lockbox) external;

/**
* @notice Updates the limits of any bridge
* @dev Can only be called by the owner
* @param mintingLimit The updated minting limit we are setting to the bridge
* @param burningLimit The updated burning limit we are setting to the bridge
* @param bridge The address of the bridge we are setting the limits too
*/
function setLimits(address bridge, uint256 mintingLimit, uint256 burningLimit) external;

/**
* @notice Returns the max limit of a minter
*
* @param minter The minter we are viewing the limits of
* @return limit The limit the minter has
*/
function mintingMaxLimitOf(address minter) external view returns (uint256 limit);

/**
* @notice Returns the max limit of a bridge
*
* @param bridge the bridge we are viewing the limits of
* @return limit The limit the bridge has
*/
function burningMaxLimitOf(address bridge) external view returns (uint256 limit);

/**
* @notice Returns the current limit of a minter
*
* @param minter The minter we are viewing the limits of
* @return limit The limit the minter has
*/
function mintingCurrentLimitOf(address minter) external view returns (uint256 limit);

/**
* @notice Returns the current limit of a bridge
*
* @param bridge the bridge we are viewing the limits of
* @return limit The limit the bridge has
*/
function burningCurrentLimitOf(address bridge) external view returns (uint256 limit);

/**
* @notice Mints tokens for a user
* @dev Can only be called by a minter
* @param user The address of the user who needs tokens minted
* @param amount The amount of tokens being minted
*/
function mint(address user, uint256 amount) external;

/**
* @notice Burns tokens for a user
* @dev Can only be called by a minter
* @param user The address of the user who needs tokens burned
* @param amount The amount of tokens being burned
*/
function burn(address user, uint256 amount) external;
}
Loading
Loading