From 7fe21bbfc01c654ea0c26403924d56f8dbf23073 Mon Sep 17 00:00:00 2001 From: Rookmate <14072042+rookmate@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:30:45 +0000 Subject: [PATCH] [WIP] Add SuperToken example --- src/superToken/ISuperToken.sol | 27 +++++ src/superToken/SuperToken.sol | 83 ++++++++++++++++ src/superToken/SuperTokenAppGateway.sol | 127 ++++++++++++++++++++++++ src/superToken/SuperTokenDeployer.sol | 68 +++++++++++++ src/superToken/Vault.sol | 93 +++++++++++++++++ 5 files changed, 398 insertions(+) create mode 100644 src/superToken/ISuperToken.sol create mode 100644 src/superToken/SuperToken.sol create mode 100644 src/superToken/SuperTokenAppGateway.sol create mode 100644 src/superToken/SuperTokenDeployer.sol create mode 100644 src/superToken/Vault.sol diff --git a/src/superToken/ISuperToken.sol b/src/superToken/ISuperToken.sol new file mode 100644 index 0000000..96aa53d --- /dev/null +++ b/src/superToken/ISuperToken.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +interface ISuperToken { + function burn(address user_, uint256 amount_) external; + + function mint(address receiver_, uint256 amount_) external; + + function balanceOf(address account) external; + + function totalSupply() external view returns (uint256); + + function transfer(address to, uint256 value) external returns (bool); + + function approve(address spender, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function allowance( + address owner, + address spender + ) external view returns (uint256); +} diff --git a/src/superToken/SuperToken.sol b/src/superToken/SuperToken.sol new file mode 100644 index 0000000..c3627ea --- /dev/null +++ b/src/superToken/SuperToken.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "solady/tokens/ERC20.sol"; + +/** + * @title SuperToken + * @notice An ERC20 contract which enables bridging a token to its sibling chains. + * @dev Implements a custom ERC20 token with minting and burning capabilities restricted to a socket address + */ +contract SuperToken is ERC20 { + string private _name; + string private _symbol; + uint8 private _decimals; + address public _SOCKET; + + // Custom Errors + error NotSOCKET(); + + modifier onlySOCKET() { + if (msg.sender != _SOCKET) revert NotSOCKET(); + _; + } + + /** + * @notice Initialize the token with name, symbol, and decimals + * @param name_ The name of the token + * @param symbol_ The symbol of the token + * @param decimals_ The number of decimals for the token + * @dev Sets the token parameters and sets the initial socket address to the contract deployer + */ + constructor(string memory name_, string memory symbol_, uint8 decimals_) { + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _SOCKET = msg.sender; + } + + /** + * @notice Mint tokens to a specified address + * @dev Can only be called by the SOCKET address + * @param to_ The address to mint tokens to + * @param amount_ The amount of tokens to mint + * @custom:modifier onlySOCKET Ensures only the SOCKET can call this function + */ + function mint(address to_, uint256 amount_) external onlySOCKET { + _mint(to_, amount_); + } + + /** + * @notice Burn tokens from the caller's balance + * @dev Can only be called by the SOCKET address + * @param amount_ The amount of tokens to burn + * @custom:modifier onlySOCKET Ensures only the SOCKET can call this function + */ + function burn(uint256 amount_) external onlySOCKET { + _burn(msg.sender, amount_); + } + + /** + * @notice Returns the name of the token + * @return The token's name + */ + function name() public view override returns (string memory) { + return _name; + } + + /** + * @notice Returns the symbol of the token + * @return The token's symbol + */ + function symbol() public view override returns (string memory) { + return _symbol; + } + + /** + * @notice Returns the number of decimals for the token + * @return The token's decimal places + */ + function decimals() public view override returns (uint8) { + return _decimals; + } +} diff --git a/src/superToken/SuperTokenAppGateway.sol b/src/superToken/SuperTokenAppGateway.sol new file mode 100644 index 0000000..944160a --- /dev/null +++ b/src/superToken/SuperTokenAppGateway.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import "socket-protocol/contracts/base/AppGatewayBase.sol"; +import "solady/auth/Ownable.sol"; +import {ISuperToken} from "./ISuperToken.sol"; + +/** + * @title SuperTokenApp + * @notice A cross-chain application for bridging tokens + * @dev Extends AppGatewayBase and Ownable to provide a chain abstracted token bridging functionality + */ +contract SuperTokenApp is AppGatewayBase, Ownable { + /** + * @notice Counter to track unique transaction IDs + * @dev Incremented with each bridging operation + */ + uint256 public idCounter; + + /** + * @notice Emitted when a token bridging operation is initiated + * @param asyncId Unique identifier for the asynchronous cross-chain transaction + */ + event Bridged(bytes32 asyncId); + + /** + * @notice Represents a user's token bridging order + * @dev Contains details of the token transfer across different chains + */ + struct UserOrder { + /// @notice Source token contract address + address srcToken; + /// @notice Destination token contract address + address dstToken; + /// @notice User initiating the bridge transaction + address user; + /// @notice Amount of tokens to be bridged from source chain + uint256 srcAmount; + /// @notice Deadline for the bridge transaction + uint256 deadline; + } + + /** + * @notice Constructor to initialize the SuperTokenApp + * @param _addressResolver Address of the cross-chain address resolver + * @param deployerContract_ Address of the contract deployer + * @param feesData_ Struct containing fee-related data for bridging + * @dev Sets up the contract, initializes ownership, and configures gateways + */ + constructor( + address _addressResolver, + address deployerContract_, + FeesData memory feesData_ + ) AppGatewayBase(_addressResolver) Ownable() { + _initializeOwner(msg.sender); + addressResolver.setContractsToGateways(deployerContract_); + _setFeesData(feesData_); + } + + /** + * @notice Validates user's token balance for a cross-chain transaction + * @param data Encoded user order and async transaction ID + * @param returnData Balance data returned from the source chain + * @dev Checks if user has sufficient balance to complete the bridge transaction + * @custom:modifier onlyPromises Ensures the function can only be called by the promises system + */ + function checkBalance( + bytes memory data, + bytes memory returnData + ) external onlyPromises { + (UserOrder memory order, bytes32 asyncId) = abi.decode( + data, + (UserOrder, bytes32) + ); + + uint256 balance = abi.decode(returnData, (uint256)); + if (balance < order.srcAmount) { + _revertTx(asyncId); + return; + } + } + + /** + * @notice Initiates a cross-chain token bridge transaction + * @param _order Encoded user order details + * @return asyncId Unique identifier for the asynchronous cross-chain transaction + * @dev Handles token bridging logic across different chains + */ + function bridge( + bytes memory _order + ) external async returns (bytes32 asyncId) { + UserOrder memory order = abi.decode(_order, (UserOrder)); + asyncId = _getCurrentAsyncId(); + /* TODO: + 1. Check user balance on src chain + 2. Check if it was a already deployed contract + if original contract, + transferFrom user to Vault + mint to user on dst chain + if supertoken, + burn from user + transferFrom Vault to user + */ + + emit Bridged(asyncId); + } + + /** + * @notice Allows the owner to withdraw fee tokens from a specific chain + * @param chainSlug_ Unique identifier of the blockchain + * @param token_ Address of the token to withdraw + * @param amount_ Amount of tokens to withdraw + * @param receiver_ Address receiving the withdrawn tokens + * @dev Restricted to contract owner + * @custom:modifier onlyOwner Ensures only the contract owner can withdraw fees + */ + function withdrawFeeTokens( + uint32 chainSlug_, + address token_, + uint256 amount_, + address receiver_ + ) external onlyOwner { + _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); + } + + // TODO: Add rescue tokens from Vault in chainSlug +} diff --git a/src/superToken/SuperTokenDeployer.sol b/src/superToken/SuperTokenDeployer.sol new file mode 100644 index 0000000..c2d1c46 --- /dev/null +++ b/src/superToken/SuperTokenDeployer.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import "socket-protocol/contracts/base/AppDeployerBase.sol"; +import "solady/auth/Ownable.sol"; +import "./SuperToken.sol"; + +/** + * @title SuperTokenDeployer + * @notice A contract for deploying SuperToken across multiple chains + * @dev Extends AppDeployerBase and Ownable to provide cross-chain token deployment functionality + */ +contract SuperTokenDeployer is AppDeployerBase, Ownable { + /** + * @notice Unique identifier for the SuperToken contract + * @dev Used to track and manage the SuperToken contract across different chains + */ + bytes32 public superToken = _createContractId("superToken"); + + /** + * @notice Constructor to initialize the SuperTokenDeployer + * @param addressResolver_ Address of the address resolver contract + * @param owner_ Address of the contract owner + * @param name_ Name of the token to be deployed + * @param symbol_ Symbol of the token to be deployed + * @param decimals_ Number of decimals for the token + * @param feesData_ Struct containing fee-related data for deployment + * @dev Sets up the contract with token creation code and initializes ownership + */ + constructor( + address addressResolver_, + address owner_, + string memory name_, + string memory symbol_, + uint8 decimals_, + FeesData memory feesData_ + ) AppDeployerBase(addressResolver_) Ownable() { + _initializeOwner(owner_); + + creationCodeWithArgs[superToken] = abi.encodePacked( + type(SuperToken).creationCode, + abi.encode(name_, symbol_, decimals_) + ); + + _setFeesData(feesData_); + } + + /** + * @notice Deploys the SuperToken contract on a specified chain + * @param chainSlug The unique identifier of the target blockchain + * @dev Triggers the deployment of the SuperToken contract + * @custom:modifier Accessible to contract owner or authorized deployers + */ + function deployContracts(uint32 chainSlug) external async { + // TODO: Add logic to process if token is already deployed on a chain + _deploy(superToken, chainSlug); + } + + /** + * @notice Initialization function for post-deployment setup + * @param chainSlug The unique identifier of the blockchain + * @dev Overrides the initialize function from AppDeployerBase + * @notice This function is automatically called after all contracts are deployed + * @dev Currently implemented as a no-op, can be extended for additional initialization logic + * @custom:note Automatically triggered via AppDeployerBase.allPayloadsExecuted or AppGateway.queueAndDeploy + */ + function initialize(uint32 chainSlug) public override async {} +} diff --git a/src/superToken/Vault.sol b/src/superToken/Vault.sol new file mode 100644 index 0000000..4cf3ec7 --- /dev/null +++ b/src/superToken/Vault.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import "solady/utils/SafeTransferLib.sol"; +import "solady/tokens/ERC20.sol"; +import "solady/auth/Ownable.sol"; + +contract Vault is Ownable { + using SafeTransferLib for address; + + // Custom Errors + error ZeroDepositAmount(); + error ZeroWithdrawAmount(); + error InsufficientBalance(); + error NotSOCKET(); + + // Events + event Deposited(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + + mapping(address => uint256) public balances; + address public _SOCKET; + address public token; + + modifier onlySOCKET() { + if (msg.sender != _SOCKET) revert NotSOCKET(); + _; + } + + constructor(address owner_, address token_) Ownable() { + _initializeOwner(owner_); + token = token_; + _SOCKET = msg.sender; + } + + /** + * @notice Deposit ERC20 tokens into the vault + * @param amount Amount of tokens to deposit + */ + function deposit(uint256 amount) external onlySOCKET { + if (amount == 0) revert ZeroDepositAmount(); + + balances[msg.sender] += amount; + + SafeTransferLib.safeTransferFrom( + token, + msg.sender, + address(this), + amount + ); + + emit Deposited(msg.sender, amount); + } + + /** + * @notice Withdraw ERC20 tokens from the vault + * @param amount Amount of tokens to withdraw + */ + function withdraw(uint256 amount) external onlySOCKET { + if (amount == 0) revert ZeroWithdrawAmount(); + + if (balances[msg.sender] < amount) revert InsufficientBalance(); + + balances[msg.sender] -= amount; + + SafeTransferLib.safeTransfer(token, msg.sender, amount); + + emit Withdrawn(msg.sender, amount); + } + + /** + * @notice Get user's balance for the token + * @param user Address of the user + * @return User's balance of the specified token + */ + function getBalance(address user) external view returns (uint256) { + return balances[user]; + } + + /** + * @notice Allows the owner to rescue tokens accidentally sent to the contract + * @param token_ Address of the token to rescue + * @param amount Amount of tokens to rescue + * @param recipient Address to send rescued tokens to + */ + function rescueTokens( + address token_, + uint256 amount, + address recipient + ) external onlyOwner { + SafeTransferLib.safeTransfer(token_, recipient, amount); + } +}