Skip to content

Commit

Permalink
- deposit function handles bridging of assets
Browse files Browse the repository at this point in the history
- added gas estimates for l2 and l1 actions
- added fees to handle l1 transactions
- added data sent by bridge to be used to identify actions on l1
- added comments
- added tests
  • Loading branch information
0xtiki committed Aug 6, 2024
1 parent a716195 commit 65c5445
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 199 deletions.
99 changes: 0 additions & 99 deletions packages/hardhat/contracts/FloxiSfraxEth.sol

This file was deleted.

238 changes: 238 additions & 0 deletions packages/hardhat/contracts/FloxiSfrxEth.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.20;

import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
// import "hardhat/console.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IL2StandardBridge {
function bridgeERC20To(
address _localToken,
address _remoteToken,
address __to,
uint256 _amount,
uint32 _minGasLimit,
bytes calldata _extraData
) external;
}

/**
* @title FloxiSfrxEth
* @dev ERC4626 vault that handles deposits, minting, and bridging of ERC20 tokens to another chain.
* Fees expressed in basis points (bp).
*/
contract FloxiSfrxEth is ERC4626, ReentrancyGuard {
using Math for uint256;

// === Constants and immutables ===

// The vaults underlying asset on local chain (sfrxEth)
IERC20 private immutable _asset;

// Remote asset address on the destination chain (sfrxEth)
address private immutable _remoteAsset;

// Floxi vault contract address on the destination chain
address private immutable _remoteContract;

// Treasury address for collecting fees
address private immutable _treasury;

// Address of the L2 Standard Bridge proxy
address private immutable _l2StandardBridgeProxy;

// Function selector for the L1 deposit function (on remote Floxi vault contract)
bytes4 private immutable _l1Selector;

// Scale for basis point calculations
uint256 private constant _BASIS_POINT_SCALE = 1e4;

// Conversion factor from gwei to wei (for gas estimates)
uint256 private constant _WEI_PER_GWEI = 1e9;

// Estimated gas for L1 transactions
uint256 private constant _L1_GAS_ESTIMATE = 78500;

// Placeholder gas price, intended to be fetched from an oracle in a production setup
uint256 private constant _GAS_PRICE = 30;

constructor(
IERC20 asset_,
address remoteAsset_,
address remoteContract_,
address treasury_,
address l2StandardBridgeProxy_,
bytes4 l1Selector_
)
ERC20("Floxi Staked Frax ETH", "fsfrxEth")
ERC4626(asset_)
{
_asset = asset_;
_remoteAsset = remoteAsset_;
_remoteContract = remoteContract_;
_treasury = treasury_;
_l2StandardBridgeProxy = l2StandardBridgeProxy_;
_l1Selector = l1Selector_;
}

// === Variables ===

// Tracks assets on L1
uint256 private _l1Assets = 0;

// === Overrides ===

/// @dev Make more resistant against inflation attacks by overriding default offset
// function _decimalsOffset() internal pure override returns (uint8) {
// return 10;
// }

/**
* @dev Preview taking an entry fee on deposit. Overrides {IERC4626-previewDeposit}.
* @param assets The amount of assets to deposit.
* @return The number of shares corresponding to the deposited assets after fees.
*/
function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
return super.previewDeposit(assets - fee);
}

/**
* @dev Preview adding an entry fee on mint. Overrides {IERC4626-previewMint}.
* @param shares The number of shares to mint.
* @return The number of assets required to mint the shares, including fees.
*/
function previewMint(uint256 shares) public view virtual override returns (uint256) {
uint256 assets = super.previewMint(shares);
return assets + _feeOnRaw(assets, _entryFeeBasisPoints());
}

/**
* @dev Handles deposits into the vault, charges an entry fee, and bridges assets to L1.
* @param caller The address initiating the deposit.
* @param receiver The address receiving the shares.
* @param assets The amount of assets being deposited.
* @param shares The amount of shares being minted.
*/
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override nonReentrant {

uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
address recipient = _entryFeeRecipient();

super._deposit(caller, receiver, assets, shares);

if (fee > 0 && recipient != address(this)) {
SafeERC20.safeTransfer(IERC20(asset()), recipient, fee);
}

bytes memory extraData = abi.encodeWithSelector(
_l1Selector,
assets - fee,
_remoteContract
);

try IL2StandardBridge(_l2StandardBridgeProxy).bridgeERC20To(
address(_asset),
_remoteAsset,
_remoteContract,
assets - fee,
120000,
extraData
) {
_l1Assets += assets - fee;
} catch {
revert("Bridge transfer failed");
}
}

// /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}.
// function _withdraw(
// address caller,
// address receiver,
// address owner,
// uint256 assets,
// uint256 shares
// ) internal virtual override {
// uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints());
// address recipient = _exitFeeRecipient();

// super._withdraw(caller, receiver, owner, assets, shares);

// if (fee > 0 && recipient != address(this)) {
// SafeERC20.safeTransfer(IERC20(asset()), recipient, fee);
// }
// }

/**
* @dev Overrides {IERC4626-totalAssets} to include assets on L1.
* @return The total assets managed by this vault.
*/
function totalAssets() public view override returns (uint256) {
return _asset.balanceOf(address(this)) + _l1Assets;
}

/**
* @dev Convenience function to see L1 assets managed by this vault.
* @return L1 assets managed by this vault.
*/
function getL1Assets() external view returns (uint256) {
return _l1Assets;
}

// === Fee configuration ===

/**
* @dev Returns the entry fee in basis points.
* @return The entry fee in basis points.
*/
function _entryFeeBasisPoints() internal pure returns (uint256) {
return 50; // 0.5%
}

/**
* @dev Returns the address receiving the entry fee.
* @return The address receiving the entry fee.
*/
function _entryFeeRecipient() internal view returns (address) {
return _treasury;
}

/**
* @dev Calculates the fee in wei based on a given gas price in gwei.
* Used as estimate for L1 transaction fees which are not charged by the bridgecontract
* (i.e. claiming from bridge contract and depositing into L1 strategy)
* @param gasPriceGwei The gas price in gwei.
* @return The estimated fee in wei.
*/
function calculateL1GasFeeInWei(uint256 gasPriceGwei) public pure returns (uint256) {
uint256 gasPriceWei = gasPriceGwei * _WEI_PER_GWEI;
uint256 totalFee = _L1_GAS_ESTIMATE * gasPriceWei;
return totalFee;
}

/**
* @dev Calculates the fees that should be added to an amount `assets` that does not already include fees.
* Used in {IERC4626-mint} and/or {IERC4626-withdraw} operations.
* @param assets The amount of assets.
* @param feeBasisPoints The fee in basis points.
* @return The fee to be added.
*/
function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil) + calculateL1GasFeeInWei(_GAS_PRICE);
}

/**
* @dev Calculates the fee part of an amount `assets` that already includes fees.
* Used in {IERC4626-deposit} and/or {IERC4626-redeem} operations.
* @param assets The amount of assets.
* @param feeBasisPoints The fee in basis points.
* @return The fee part of the assets.
*/
function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil) + calculateL1GasFeeInWei(_GAS_PRICE);
}
}
File renamed without changes.
17 changes: 12 additions & 5 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";
import sfraxEthAbi from "../contracts/sfraxEthAbi.json";
import sfraxEthAbi from "../contracts/sfrxEthAbi.json";

// const sFraxEthHoleskyMain = "0xa63f56985F9C7F3bc9fFc5685535649e0C1a55f3";
// const sFrxEthHoleskyMain = "0xa63f56985F9C7F3bc9fFc5685535649e0C1a55f3";
const sfrxEthEthereumMainnet = "0xac3E018457B222d93114458476f3E3416Abbe38F";
const sFraxEthFraxtal = "0xfc00000000000000000000000000000000000005";
const bigSFraxHolderFraxtal = "0x66d9AF69E6845E8666f355676a267a726c04Ea4e";
const burner = "0x2aa499509b9c9F9ac8086ff0e8Dd39c54b1e63aA";
const l2StandardBridge = "0x4200000000000000000000000000000000000010";
const treasury = "0x2aa499509b9c9F9ac8086ff0e8Dd39c54b1e63aA";
const floxiMainnet = "0x0000000000000000000000000000000000000000";

/**
* Deploys a contract named "YourContract" using the deployer account and
Expand All @@ -28,10 +32,13 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("FloxiSfraxEth", {
const functionSignature = "_deposit(address,uint256,uint256)";
const selector = hre.ethers.keccak256(hre.ethers.toUtf8Bytes(functionSignature)).slice(0, 10);

await deploy("FloxiSfrxEth", {
from: deployer,
// Contract constructor arguments
args: [sFraxEthFraxtal, "0x2aa499509b9c9F9ac8086ff0e8Dd39c54b1e63aA"],
args: [sFraxEthFraxtal, sfrxEthEthereumMainnet, floxiMainnet, treasury, l2StandardBridge, selector],
log: true,
// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
// automatically mining the contract deployment transaction. There is no effect on live networks.
Expand Down Expand Up @@ -64,4 +71,4 @@ export default deployYourContract;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags YourContract
deployYourContract.tags = ["FloxiSfraxEth"];
deployYourContract.tags = ["FloxiSfrxEth"];
3 changes: 3 additions & 0 deletions packages/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const config: HardhatUserConfig = {
default: 0,
},
},
mocha: {
timeout: 200000, // 200 seconds max for running tests
},
networks: {
// View the networks that are pre-configured.
// If the network you are looking for is not here you can add new network settings
Expand Down
Loading

0 comments on commit 65c5445

Please sign in to comment.