Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x3eD5AfADC677834247BB5CBf7B86f762Ae4a5C3D
Balance 0 ETH
Nonce 1
Code Size 6720 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

6720 bytes
0x6080604052600436101561001257600080fd5b60003560e01c806302befd24146102175780630622874914610212578063067bd07a1461020d578063087cbd40146102085780630d3c34e8146102035780631844d9c8146101fe5780631c4ba3ed146101f9578063204229b8146101f4578063257f9e7b146101ef57806326190b47146101ea5780632f3ffb9f146101e5578063315a095d146101e057806337d15139146101db5780633b1a2819146101d65780633ca587d6146101d15780634626402b146101cc57806352238fdd146101c7578063543f66a4146101c25780636a622089146101bd578063715018a6146101b8578063809dab6a146101b357806381e078df146101ae5780638da5cb5b146101a95780638dd14802146101a457806391274f681461019f578063a001ecdd1461019a578063a58dd54814610195578063a8602fea14610190578063d309a4ce1461018b578063f2fde38b14610186578063f968e6f6146101815763fcbb71201461017c57600080fd5b610e56565b610e27565b610d9b565b610d6c565b610ce1565b610ca6565b610c8a565b610c10565b610ba0565b610b77565b610b2c565b610a8f565b610a34565b610a0b565b6109a3565b61095e565b610931565b61088d565b610814565b6107a6565b61070d565b6106e7565b6106be565b610679565b610646565b6105ba565b610523565b610331565b6102c1565b610278565b61024f565b61022c565b600091031261022757565b600080fd5b3461022757600036600319011261022757602060ff600554166040519015158152f35b34610227576000366003190112610227576007546040516001600160a01b039091168152602090f35b346102275760003660031901126102275760206040516000805160206119eb8339815191528152f35b6001600160a01b0381160361022757565b600435906102bf826102a1565b565b34610227576020366003190112610227577ff686f7dd62d0eba08e0a836ed78aac51f795cd215a0286632834128004a476f26020600435610301816102a1565b610309610e7f565b600180546001600160a01b0319166001600160a01b03929092169182179055604051908152a1005b346102275760403660031901126102275760043560243561035460055460ff1690565b610511576040516323b872dd60e01b81523360048201523060248201526044810183905260009290602081606481876001600160a01b037f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c165af180156104da576104e3575b506002546103d8906001600160a01b03165b6001600160a01b031690565b803b156104df57604051628eebf960e41b8152600481018390526000805160206119eb83398151915260248201529084908290604490829084905af180156104da577f1a771fe656018364a9369da21954bb3081cb08b0196c27e43ca59c7cae872737926104bb926104a1926104c1575b5061045e61045682610ec1565b612710900490565b906104698282611088565b33600090815260036020526040902061049a906104929089905b90600052602052604060002090565b918254610fad565b9055611088565b604080519182526020820194909452339390918291820190565b0390a280f35b806104ce6104d492610f3b565b8061021c565b38610449565b610fa1565b8380fd5b6105039060203d811161050a575b6104fb8183610f70565b810190611070565b50386103ba565b503d6104f1565b60405163035edea360e41b8152600490fd5b34610227576000806003193601126105b75760025460405163095ea7b360e01b81526001600160a01b0391821660048201526000196024820152906020908290604490829086907f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c165af180156104da5761059c575080f35b6105b39060203d811161050a576104fb8183610f70565b5080f35b80fd5b34610227576020366003190112610227576004356105d7816102a1565b6105df610e7f565b6001600160a01b03168015610634576020817fe253ce77edf5d0a5243820400c2f78cbff85c731730e2189d73e066e8d927833926bffffffffffffffffffffffff60a01b6007541617600755604051908152a1005b60405163d92e233d60e01b8152600490fd5b34610227576040366003190112610227576020610671600435610668816102a1565b60243590610fba565b604051908152f35b34610227576000366003190112610227576040517f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c6001600160a01b03168152602090f35b34610227576000366003190112610227576006546040516001600160a01b039091168152602090f35b3461022757600036600319011261022757602060ff60055460081c166040519015158152f35b3461022757602036600319011261022757610726610e7f565b60405163a9059cbb60e01b81523360048083019190915235602482015260208160448160006001600160a01b037f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c165af180156104da5761078357005b61079a9060203d811161050a576104fb8183610f70565b005b8015150361022757565b34610227576020366003190112610227577fac1664632d6278432aa05a6afc0aa83e5ea08e8df9a1436736daa5d6283211c060406004356107e68161079c565b6107ee610e7f565b151560055461ff008260081b169061ff00191617600555815190600082526020820152a1005b3461022757604036600319011261022757600435610831816102a1565b60018060a01b0316600052600360205260406000206024356000526020526020604060002054604051908152f35b9181601f840112156102275782359167ffffffffffffffff8311610227576020838186019501011161022757565b3461022757610100366003190112610227576108a76102b2565b67ffffffffffffffff90608435828111610227576108c990369060040161085f565b60a492919235848111610227576108e490369060040161085f565b60c492919235868111610227576108ff90369060040161085f565b93909260e4359788116102275761091d61079a98369060040161085f565b979096606435906044359060243590611095565b346102275760003660031901126102275760055460405160109190911c6001600160a01b03168152602090f35b346102275760203660031901126102275761099f61097d600435610edc565b6040805194855260208501939093529183015260608201529081906080820190565b0390f35b34610227576020366003190112610227577fac1664632d6278432aa05a6afc0aa83e5ea08e8df9a1436736daa5d6283211c060406004356109e38161079c565b6109eb610e7f565b151560ff196005541660ff821617600555815190600182526020820152a1005b34610227576000366003190112610227576001546040516001600160a01b039091168152602090f35b34610227576000806003193601126105b757610a4e610e7f565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b34610227576000366003190112610227576040516370a0823160e01b81523060048201526020816024817f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c6001600160a01b03165afa80156104da57602091600091610aff575b50604051908152f35b610b1f9150823d8111610b25575b610b178183610f70565b810190610f92565b38610af6565b503d610b0d565b3461022757604036600319011261022757600435610b49816102a1565b60018060a01b0316600052600460205260406000206024356000526020526020604060002054604051908152f35b34610227576000366003190112610227576000546040516001600160a01b039091168152602090f35b34610227576020366003190112610227577f9775531310b2880b61484ed85cbb0b491c8fde3a07f289c63b925517827944976020600435610be0816102a1565b610be8610e7f565b600280546001600160a01b0319166001600160a01b03929092169182179055604051908152a1005b3461022757602036600319011261022757600435610c2d816102a1565b610c35610e7f565b6001600160a01b03168015610634576020817f16f546f509a2f5f6611d1f900d0a414b30a09b07a5ec7dd178119bb1ead9e279926bffffffffffffffffffffffff60a01b6006541617600655604051908152a1005b3461022757600036600319011261022757602060405160c88152f35b346102275760003660031901126102275760206040517f28c08aca28c4b3c828009af9dc26970abaab1368844b23ee2aadb38175458c968152f35b3461022757602036600319011261022757600435610cfe816102a1565b610d06610e7f565b6001600160a01b038116908115610634576005805462010000600160b01b03191660109290921b62010000600160b01b03169190911790556040519081527f2551960305e8f85b09658bb3075878e3e3cef37a5f7b5d43261f5e6f36b3d6a490602090a1005b3461022757600036600319011261022757602060405173db3b2f0ffe05d35953d727a82f7a41f78e5f3ea18152f35b3461022757602036600319011261022757600435610db8816102a1565b610dc0610e7f565b6001600160a01b039081168015610e0e57600080546001600160a01b03198116831782559092167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b604051631e4fbdf760e01b815260006004820152602490fd5b34610227576060366003190112610227576020610671600435610e49816102a1565b6044359060243590611253565b34610227576000366003190112610227576002546040516001600160a01b039091168152602090f35b6000546001600160a01b03163303610e9357565b60405163118cdaa760e01b8152336004820152602490fd5b634e487b7160e01b600052601160045260246000fd5b9060c882029180830460c81490151715610ed757565b610eab565b9060c882029180159181840460c814831715610ed7576032820293828504603214841715610ed75761271080950493606484029384046064141715610ed7578490049383920490565b634e487b7160e01b600052604160045260246000fd5b67ffffffffffffffff8111610f4f57604052565b610f25565b6080810190811067ffffffffffffffff821117610f4f57604052565b90601f8019910116810190811067ffffffffffffffff821117610f4f57604052565b90816020910312610227575190565b6040513d6000823e3d90fd5b91908201809211610ed757565b90602061101d91610fdd8460018060a01b03166000526004602052604060002090565b60008281529083526040908190205490516381e078df60e01b81526001600160a01b03909516600486015260248501919091529291829081906044820190565b038173db3b2f0ffe05d35953d727a82f7a41f78e5f3ea15afa9081156104da57600091611052575b508101809111610ed75790565b61106a915060203d8111610b2557610b178183610f70565b38611045565b9081602091031261022757516110858161079c565b90565b91908203918211610ed757565b9a959299969390989491976005549860ff8a60081c16611241576001600160a01b039960101c8a16158015611225575b8015611209575b6111f7578c8b6111058e6110ff6110ea60025460018060a01b031690565b6001549094906001600160a01b031695610fba565b90611088565b9a8b156111e5578f9a8f918f9b8e97611127946111309f9e61112c9f886117eb565b61166f565b1590565b611140575b50506102bf92611306565b8061114d61115692610edc565b50505090610fad565b6040516370a0823160e01b81523060048201529091602090829060249082907f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c165afa9081156104da576000916111c7575b50106111b5573880611135565b60405163e028c6cd60e01b8152600490fd5b6111df915060203d8111610b2557610b178183610f70565b386111a8565b50505050505050505050505050505050565b60405163c5b8557560e01b8152600490fd5b5060075461121f906001600160a01b03166103cc565b156110cc565b5060065461123b906001600160a01b03166103cc565b156110c5565b60405163e0a3980360e01b8152600490fd5b90916040519260208401927f28c08aca28c4b3c828009af9dc26970abaab1368844b23ee2aadb38175458c96845260018060a01b03166040850152606084015260808301526080825260a082019082821067ffffffffffffffff831117610f4f57816040528251902060e260c084019361190160f01b85527fdcbb318cd695682f7c81fac71ca0be8782e7726d59a6753424527294c0ee518060c282015201526042815261130081610f54565b51902090565b61137091926113389161132261131c8683610fba565b83611088565b9161132c83610edc565b93919690928096611088565b604080516381e078df60e01b81526001600160a01b0387166004820152602481018b9052602099919390918a90839081906044820190565b038173db3b2f0ffe05d35953d727a82f7a41f78e5f3ea15afa9081156104da576113a2926000926115e8575b50611088565b6001600160a01b03861660009081526004602052604090206113c5908b90610483565b55815163a9059cbb60e01b8082526001600160a01b038781166004840152602483018490529096917f000000000000000000000000411099c0b413f4feddb10edf6a8be63bd321311c82168b826044816000855af19182156104da578b926115cb575b5060075486518a81526001600160a01b03909116600482015260248101929092528b826044816000855af19182156104da5787926115ae575b5060065486518a81526001600160a01b03909116600482015260248101929092528b826044816000855af19081156104da576114dc998d938a93611591575b5060055460009060101c6001600160a01b031689519283526001600160a01b031660048301526024820193909352998a92839182906044820190565b03925af19182156104da577fe5f74c1935f87f5b3c2d5f1529e001ae6fedd21cce61a31cf833e909aff18ae39a61156e987f4d911754a3efbbc2e0463de4f6bff32ed24421d1c89c11dce59a4935f327afff94611573575b50508451938452602084019b909b52909916988991604090a251948594859094939260609260808301968352602083015260408201520152565b0390a2565b8161158992903d1061050a576104fb8183610f70565b503880611534565b6115a790853d871161050a576104fb8183610f70565b50386114a0565b6115c4908d803d1061050a576104fb8183610f70565b5038611461565b6115e1908d803d1061050a576104fb8183610f70565b5038611428565b6116009192508b3d8d11610b2557610b178183610f70565b903861139c565b908060209392818452848401376000828201840152601f01601f1916010190565b9492909361166192611085979587526000805160206119eb8339815191526020880152604087015260a0606087015260a0860191611607565b926080818503910152611607565b604051635cbe13f160e11b81523060048201526000805160206119eb8339815191526024820152959794959294926001600160a01b03926020908290604490829087165afa9081156104da57600091611771575b508581101561172457501692833b15610227576116fc600096928793604051998a98899788966357fcd00d60e01b885260048801611628565b03925af180156104da57611711575b50600190565b806104ce61171e92610f3b565b3861170b565b6040805191825260208201969096527fa4e43cf8c8c43e7909023621d0cd86a862b59aeb9645048e40568a09dd8651e598509650869550505091830191506117699050565b0390a1600090565b611789915060203d8111610b2557610b178183610f70565b386116c3565b908160209103126102275751611085816102a1565b92919267ffffffffffffffff8211610f4f57604051916117ce601f8201601f191660200184610f70565b829481845281830111610227578281602093846000960137010152565b939694959290916117fb92611253565b6040516305c3fa1b60e21b81526001600160a01b0390931695602095919390919086816004818b5afa9081156104da576118509361112c9387926000946118f1575b509061184a9136916117a4565b91611916565b6118df578360049560405196878092636d3d6a9160e01b82525afa9283156104da576118909561112c956000956118a8575b50509061184a9136916117a4565b61189657565b60405163138af2dd60e21b8152600490fd5b61184a9392955090816118cf92903d106118d8575b6118c78183610f70565b81019061178f565b93909138611882565b503d6118bd565b604051632c28119f60e21b8152600490fd5b61184a9291945061190e908b3d8d116118d8576118c78183610f70565b93909161183d565b909160418151036119e257602081015160606040830151920151906000948592831a917f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a085116119ce57601b831415806119d7575b6119ce5760209461199c91604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156104da5781516001600160a01b039081169290919083156119c75750161490565b9250505090565b50505050505090565b50601c83141561196b565b50505060009056fe6eef29ebb03aa2144a1a6b6212ce74f504a34db799b8161a21140017e80d3d8aa2646970667358221220719bbf9cda88a68a4f8b97314598faae21d6731577a51f9a3c1bbc1a31bfaa7f64736f6c63430008140033

Verified Source Code Full Match

Compiler: v0.8.20+commit.a1b79de6 EVM: paris Optimization: Yes (200 runs)
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
HelloBridge.sol 516 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {HelloBridgeStore} from "./HelloBridgeStore.sol";

/* -------------------------------------------------------------------------- */
/*                                   errors                                   */
/* -------------------------------------------------------------------------- */

error SignerNotWithdrawSigner();
error NoAmountToWithdraw();
error CannotBridgeToUnsupportedChain();
error Paused();
error NotPaused();
error ZeroAddress();
error InvalidSignature();
error InvalidFeePercentage();
error FeeWalletsNotSet();

///@notice The owner will always be a multisig wallet.

/* -------------------------------------------------------------------------- */
/*                                 HelloBridge                                */
/* -------------------------------------------------------------------------- */
/**
 * @title A cross-chain bridge for HelloToken
 * @author 0xSimon
 * @notice It is recommended to use the provided UI to bridge your tokens from chain A to chain B
 * @dev Assumptions:
 *     - On chains that this contract is deployed to (besides the mainnet), the entire supply of HelloToken will be minted and sent to this contract.
 */
contract HelloBridge is Ownable(msg.sender) {
    /* -------------------------------------------------------------------------- */
    /*                                   events                                   */
    /* -------------------------------------------------------------------------- */
    event Deposit(address indexed sender, uint256 amount, uint256 chainId);
    event Claim(
        address indexed sender,
        uint256 totalDepositedOnOtherChain,
        uint256 otherChainId
    );
    event SupportedChainChanged(uint256 chainID, bool isSupported);
    event SupportedChainsChanged(uint256[] chainIDs, bool isSupported);
    event WithdrawSigner1Changed(address signer);
    event WithdrawSigner2Changed(address signer);
    event PausedChanged(bool depositPaused, bool claimPaused);
    event BridgeStoreChanged(address store);
    event TreasuryWalletChanged(address newTreasuryWallet);
    event StakingPoolWalletChanged(address newStakingPoolWallet);
    event BurnWalletChanged(address newBurnWallet);
    event FeePercentageChanged(uint256 newFeePercentage);
    event FeeCollected(
        address indexed user,
        uint256 totalFeeAmount,
        uint256 burnAmount,
        uint256 stakingAmount,
        uint256 treasuryAmount
    );

    /* -------------------------------------------------------------------------- */
    /*                                  constants                                 */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice The HelloToken contract
     */
    IERC20 public immutable HELLO_TOKEN;
    uint256 private constant MAX_FEE_PERCENTAGE = 500; // 5% max fee
    uint256 private constant BASIS_POINTS = 10000; // For fee calculations

    /* -------------------------------------------------------------------------- */
    /*                                   states                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice The signer providing signatures for claiming tokens on the destination chain
     */
    address public withdrawSigner1;
    address public withdrawSigner2;

    /**
     * @notice Storage contract for deposits and withdrawals numbers
     */
    HelloBridgeStore public store;

    /**
     * @notice A mapping to store which destination chains are supported
     */
    mapping(uint256 => bool) public supportedChains;

    bool public depositPaused;
    bool public claimPaused;

    // Fee related states
    address public treasuryWallet; // 1% goes here
    address public stakingPoolWallet; // 0.5% goes here
    address public burnWallet; // 0.5% goes here
    uint256 public feePercentage; // In basis points (e.g., 200 = 2%)
    address public adaptor;

    /**
     * @notice Deploys the contract and saves the HelloToken contract address
     * @dev `msg.sender` is assigned to the owner, pay attention if this contract is deployed via another contract
     * @param _helloToken The address of HelloToken
     */
    constructor(address _helloToken) {
        HELLO_TOKEN = IERC20(_helloToken);
        feePercentage = 200; // Default 2% fee
        burnWallet = 0x000000000000000000000000000000000000dEaD;
    }

    /* -------------------------------------------------------------------------- */
    /*                                Fee Functions                               */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Calculates the fee amount for a given total amount
     * @param amount The amount to calculate fee for
     * @return totalFee The total fee amount
     * @return burnAmount The amount to be sent to burn wallet (0.5%)
     * @return stakingAmount The amount to be sent to staking pool (0.5%)
     * @return treasuryAmount The amount to be sent to treasury (1%)
     */
    function calculateFees(
        uint256 amount
    )
        public
        view
        returns (
            uint256 totalFee,
            uint256 burnAmount,
            uint256 stakingAmount,
            uint256 treasuryAmount
        )
    {
        totalFee = (amount * feePercentage) / BASIS_POINTS;

        // Calculate individual fee components:
        // 0.5% to burn, 0.5% to staking, 1% to treasury
        burnAmount = (amount * 50) / BASIS_POINTS; // 0.5%
        stakingAmount = (amount * 50) / BASIS_POINTS; // 0.5%
        treasuryAmount = (amount * 100) / BASIS_POINTS; // 1%

        return (totalFee, burnAmount, stakingAmount, treasuryAmount);
    }

    /**
     * @notice Updates the treasury wallet address
     * @param newTreasuryWallet The new address to collect treasury fees (1%)
     */
    function setTreasuryWallet(address newTreasuryWallet) external onlyOwner {
        if (newTreasuryWallet == address(0)) _revert(ZeroAddress.selector);
        treasuryWallet = newTreasuryWallet;
        emit TreasuryWalletChanged(newTreasuryWallet);
    }

    /**
     * @notice Updates the staking pool wallet address
     * @param newStakingPoolWallet The new address to collect staking pool fees (0.5%)
     */
    function setStakingPoolWallet(
        address newStakingPoolWallet
    ) external onlyOwner {
        if (newStakingPoolWallet == address(0)) _revert(ZeroAddress.selector);
        stakingPoolWallet = newStakingPoolWallet;
        emit StakingPoolWalletChanged(newStakingPoolWallet);
    }

    /**
     * @notice Updates the burn wallet address
     * @param newBurnWallet The new address to send tokens for burning (0.5%)
     */
    function setBurnWallet(address newBurnWallet) external onlyOwner {
        if (newBurnWallet == address(0)) _revert(ZeroAddress.selector);
        burnWallet = newBurnWallet;
        emit BurnWalletChanged(newBurnWallet);
    }
    function setAdaptorContract(address newAdaptor) external onlyOwner {
        if (newAdaptor == address(0)) _revert(ZeroAddress.selector);
        adaptor = newAdaptor;
    }

    /* -------------------------------------------------------------------------- */
    /*                              ECDSA Functions                              */
    /* -------------------------------------------------------------------------- */

    /**
     * @dev Recover signer address from a message by using their signature
     * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
     * @param signature bytes signature, the signature is generated using web3.eth.sign()
     */
    function recoverSigner(
        bytes32 hash,
        bytes memory signature
    ) internal pure returns (address) {
        if (signature.length != 65) {
            revert InvalidSignature();
        }

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }

        if (
            uint256(s) >
            0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
        ) {
            revert InvalidSignature();
        }

        if (v != 27 && v != 28) {
            revert InvalidSignature();
        }

        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            revert InvalidSignature();
        }

        return signer;
    }

    /**
     * @dev Returns the hash of a message that can be signed by external wallets.
     * @param message bytes32 message to be signed
     * @return bytes32 The Ethereum signed message hash
     */
    function hashEthSignedMessage(
        bytes32 message
    ) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked("\x19Ethereum Signed Message:\n32", message)
            );
    }

    /* -------------------------------------------------------------------------- */
    /*                                  external                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Bridge HelloToken to another chain. HelloToken will be transferred to this contract.
     * @notice Ensure approvals for HelloToken has been set and the destination chain is supported.
     * @param amount Amount of HelloToken to bridge to another chain
     * @param chainId The chain ID of the destination chain
     * @dev Reverts if the destination chain is not supported
     */
    function sendToChain(uint256 amount, uint256 chainId) external {
        if (depositPaused) _revert(Paused.selector);
        if (!supportedChains[chainId])
            _revert(CannotBridgeToUnsupportedChain.selector);

        // Check that all fee wallets are set
        if (
            treasuryWallet == address(0) ||
            stakingPoolWallet == address(0) ||
            burnWallet == address(0)
        ) _revert(FeeWalletsNotSet.selector);

        // Calculate fee distribution
        (
            uint256 totalFee,
            uint256 burnAmount,
            uint256 stakingAmount,
            uint256 treasuryAmount
        ) = calculateFees(amount);
        uint256 netAmount = amount - totalFee;

        // Update state with net amount
        uint256 _currentDeposit = store.totalCrossChainDeposits(
            msg.sender,
            chainId
        );
        store.setTotalCrossChainDeposits(
            msg.sender,
            chainId,
            _currentDeposit + netAmount
        );

        // Transfer tokens - net amount to bridge
        HELLO_TOKEN.transferFrom(msg.sender, address(this), netAmount);

        // Transfer fees to respective destinations
        HELLO_TOKEN.transferFrom(msg.sender, burnWallet, burnAmount);
        HELLO_TOKEN.transferFrom(msg.sender, stakingPoolWallet, stakingAmount);
        HELLO_TOKEN.transferFrom(msg.sender, treasuryWallet, treasuryAmount);

        emit Deposit(msg.sender, netAmount, chainId);
        emit FeeCollected(
            msg.sender,
            totalFee,
            burnAmount,
            stakingAmount,
            treasuryAmount
        );
    }

    /**
     * @notice Claims tokens deposited in the source chain. Two signatures are required.
     * @notice HelloToken will be transferred from this account to the caller.
     * @param totalDepositedOnOtherChain The total amount you have deposited in the source chain
     * @param otherChainId The chain ID of the source chain
     * @param signature1 The first signature for verification
     * @param signature2 The second signature for verification
     * @dev Reverts if there's no amount to claim.
     * @dev Reverts if either signature is invalid
     */
    function claimFromChain(
        uint256 totalDepositedOnOtherChain,
        uint256 otherChainId,
        bytes calldata signature1,
        bytes calldata signature2
    ) external {
        if (claimPaused) _revert(Paused.selector);

        uint256 amountToWithdraw = totalDepositedOnOtherChain -
            store.totalCrossChainWithdrawals(msg.sender, otherChainId);

        if (amountToWithdraw == 0) _revert(NoAmountToWithdraw.selector);

        bytes32 hash = keccak256(
            abi.encodePacked(
                msg.sender,
                totalDepositedOnOtherChain,
                otherChainId,
                block.chainid,
                address(this)
            )
        );

        bytes32 ethSignedHash = hashEthSignedMessage(hash);

        if (recoverSigner(ethSignedHash, signature1) != withdrawSigner1) {
            _revert(SignerNotWithdrawSigner.selector);
        }

        if (recoverSigner(ethSignedHash, signature2) != withdrawSigner2) {
            _revert(SignerNotWithdrawSigner.selector);
        }

        uint256 netAmount = amountToWithdraw;

        store.setTotalCrossChainWithdrawals(
            msg.sender,
            otherChainId,
            totalDepositedOnOtherChain
        );

        // Transfer tokens to user
        HELLO_TOKEN.transfer(msg.sender, netAmount);

        emit Claim(msg.sender, totalDepositedOnOtherChain, otherChainId);
    }

    function claimFromChainSolana(
        uint256 totalDepositedOnOtherChain,
        uint256 otherChainId,
        uint256 totalUserAmountWithdrawable,
        bytes calldata signature1,
        bytes calldata signature2
    ) external {
        require(msg.sender == adaptor, "Only adaptor can call");
        if (claimPaused) _revert(Paused.selector);

        uint256 amountToWithdraw = totalDepositedOnOtherChain -
            store.totalCrossChainWithdrawals(msg.sender, otherChainId);

        if (amountToWithdraw == 0) _revert(NoAmountToWithdraw.selector);

        bytes32 hash = keccak256(
            abi.encodePacked(
                msg.sender,
                totalDepositedOnOtherChain,
                otherChainId,
                block.chainid,
                address(this)
            )
        );

        bytes32 ethSignedHash = hashEthSignedMessage(hash);

        if (recoverSigner(ethSignedHash, signature1) != withdrawSigner1) {
            _revert(SignerNotWithdrawSigner.selector);
        }

        if (recoverSigner(ethSignedHash, signature2) != withdrawSigner2) {
            _revert(SignerNotWithdrawSigner.selector);
        }

        uint256 netAmount = totalUserAmountWithdrawable;

        store.setTotalCrossChainWithdrawals(
            msg.sender,
            otherChainId,
            totalDepositedOnOtherChain
        );

        // Transfer tokens to user
        HELLO_TOKEN.transfer(msg.sender, netAmount);

        emit Claim(msg.sender, totalDepositedOnOtherChain, otherChainId);
    }

    /* -------------------------------------------------------------------------- */
    /*                                   owners                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Owner only - Updates the address of the withdrawal signer
     * @param _withdrawSigner Address of the withdrawal signer
     */
    function setWithdrawSigner(address _withdrawSigner) external onlyOwner {
        if (_withdrawSigner == address(0)) {
            _revert(ZeroAddress.selector);
        }
        withdrawSigner1 = _withdrawSigner;
        emit WithdrawSigner1Changed(_withdrawSigner);
    }

    function setWithdrawSigner2(address _withdrawSigner2) external onlyOwner {
        if (_withdrawSigner2 == address(0)) {
            _revert(ZeroAddress.selector);
        }
        withdrawSigner2 = _withdrawSigner2;
        emit WithdrawSigner2Changed(_withdrawSigner2);
    }

    /**
     * @notice Owner only - Updates a supported destination chain
     * @param chainId Chain ID of a destination chain
     * @param isSupported Whether the destination chain is supported
     */
    function setSupportedChain(
        uint256 chainId,
        bool isSupported
    ) external onlyOwner {
        _setSupportedChain(chainId, isSupported);
        emit SupportedChainChanged(chainId, isSupported);
    }

    /**
     * @notice Owner only - Batch update supported destination chains
     * @param chainIds Chain IDs of the destination chains
     * @param isSupported Whether the destination chains are supported
     */
    function setSupportedChains(
        uint256[] calldata chainIds,
        bool isSupported
    ) external onlyOwner {
        for (uint256 i; i < chainIds.length; ) {
            _setSupportedChain(chainIds[i], isSupported);
            unchecked {
                ++i;
            }
        }
        emit SupportedChainsChanged(chainIds, isSupported);
    }

    /**
     * @notice Pause / unpause deposit & claim
     * @param depositPaused_ Whether to pause deposit
     * @param claimPaused_ Whether to pause claim
     */
    function setPaused(
        bool depositPaused_,
        bool claimPaused_
    ) external onlyOwner {
        depositPaused = depositPaused_;
        claimPaused = claimPaused_;
        emit PausedChanged(depositPaused_, claimPaused_);
    }

    function setBridgeStore(address a_) external onlyOwner {
        if (a_ == address(0)) {
            _revert(ZeroAddress.selector);
        }
        store = HelloBridgeStore(a_);
        emit BridgeStoreChanged(a_);
    }

    /**
     * @notice Migrates the bridge contract to another. Tokens in this contract will be transferred to the new contract.
     * @dev The new bridge contract should use the data stored in `store`.
     * @param bridgeAddress Address of the new contract
     */
    function migrateBridge(address bridgeAddress) external onlyOwner {
        if (bridgeAddress == address(0)) {
            _revert(ZeroAddress.selector);
        }
        if (!depositPaused || !claimPaused) {
            _revert(NotPaused.selector);
        }
        HELLO_TOKEN.transfer(
            bridgeAddress,
            HELLO_TOKEN.balanceOf(address(this))
        );
    }

    /* -------------------------------------------------------------------------- */
    /*                                  internal                                  */
    /* -------------------------------------------------------------------------- */
    function _setSupportedChain(uint256 chainId, bool isSupported) internal {
        supportedChains[chainId] = isSupported;
    }

    function _revert(bytes4 selector) internal pure {
        assembly {
            mstore(0x0, selector)
            revert(0x0, 0x04)
        }
    }
}
HelloBridgeSolanaAdapter.sol 518 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {HelloBridgeStore} from "./HelloBridgeStore.sol";
import {HelloBridge} from "./HelloBridge.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title Hello Labs Bridge Adapter for Solana
 * @notice This contract is the adapter for the Solana chain
 * @notice Owner should always be a multisig wallet
 * @dev It interacts with the bridge contract to send and receive tokens from the Solana chain
 */
contract HelloBridgeSolanaAdapter is Ownable(msg.sender) {
    error DepositPaused();
    error WithdrawPaused();
    error InvalidSolanaSignature1();
    error InvalidSolanaSignature2();
    error InvalidSignature();
    error FeeWalletsNotSet();
    error InvalidFeePercentage();
    error InsufficientAdapterBalance(); // New error for Solution 2

    // EIP712 Domain
    bytes32 private immutable DOMAIN_SEPARATOR;

    // EIP712 TypeHash
    bytes32 private constant EIP712_DOMAIN_TYPEHASH =
        keccak256(
            "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
        );

    HelloBridgeStore public $store;
    HelloBridge public $bridge;
    IERC20 public immutable HELLO_TOKEN;

    // Reference to the old Solana adapter contract
    HelloBridgeSolanaAdapter public constant oldSolanaAdapter =
        HelloBridgeSolanaAdapter(0xdB3b2f0ffe05d35953D727A82F7a41f78e5F3EA1);

    //cast keccak "solana"
    uint256 public constant SOLANA_CHAIN_ID =
        0x6eef29ebb03aa2144a1a6b6212ce74f504a34db799b8161a21140017e80d3d8a;
    bytes32 public constant CLAIM_FROM_SOLANA_TYPEHASH =
        keccak256(
            "ClaimFromSolana(address ethAddress,bytes32 solanaAddress,uint256 totalUserAmountWithdrawable)"
        );

    mapping(address => mapping(bytes32 => uint256))
        public amountDepositedOnSolana;
    mapping(address => mapping(bytes32 => uint256))
        public amountWithdrawnFromSolana;

    bool public depositPaused;
    bool public withdrawPaused;

    // Fee related states
    address public treasuryWallet; // 1% goes here
    address public stakingPoolWallet; // 0.5% goes here
    address public burnWallet; // 0.5% goes here
    uint256 public constant feePercentage = 200; // In basis points (e.g., 200 = 2%)
    uint256 private constant MAX_FEE_PERCENTAGE = 500; // 5% max fee
    uint256 private constant BASIS_POINTS = 10000; // For fee calculations

    event Deposit(address indexed user, uint256 amount, bytes32 solanaAddress);
    event Withdraw(address indexed user, uint256 amount, bytes32 solanaAddress);
    event PauseChanged(bool depositChanged, bool withdrawChanged);
    event BridgeChanged(address newBridge);
    event StoreChanged(address newStore);
    event TreasuryWalletChanged(address newTreasuryWallet);
    event StakingPoolWalletChanged(address newStakingPoolWallet);
    event BurnWalletChanged(address newBurnWallet);
    event FeeCollected(
        address indexed user,
        uint256 totalFeeAmount,
        uint256 burnAmount,
        uint256 stakingAmount,
        uint256 treasuryAmount
    );

    event BridgeClaimSkipped(
        uint256 totalCrossChainWithdrawals,
        uint256 totalAmountDepositedToSolanaAdapter
    ); // New event for Solution 2

    constructor(address _store, address _bridge, address _helloToken) {
        $store = HelloBridgeStore(_store);
        $bridge = HelloBridge(_bridge);
        HELLO_TOKEN = IERC20(_helloToken);

        // Initialize domain separator
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                EIP712_DOMAIN_TYPEHASH,
                keccak256("HelloBridgeSolanaAdapter"),
                keccak256("1.0"),
                block.chainid,
                address(this)
            )
        );

        //Set the allownace to the bridge to type(uint256).max
        //To avoid extra sstores and gas fees in the deposit function
        HELLO_TOKEN.approve(address($bridge), type(uint256).max);
    }

    /* -------------------------------------------------------------------------- */
    /*                                Fee Functions                               */
    /* -------------------------------------------------------------------------- */

    /**
     * @notice Calculates the fee amount for a given total amount
     * @param amount The amount to calculate fee for
     * @return totalFee The total fee amount
     * @return burnAmount The amount to be sent to burn wallet (0.5%)
     * @return stakingAmount The amount to be sent to staking pool (0.5%)
     * @return treasuryAmount The amount to be sent to treasury (1%)
     */
    function calculateFees(
        uint256 amount
    )
        public
        view
        returns (
            uint256 totalFee,
            uint256 burnAmount,
            uint256 stakingAmount,
            uint256 treasuryAmount
        )
    {
        totalFee = (amount * feePercentage) / BASIS_POINTS;

        // Calculate individual fee components:
        // 0.5% to burn, 0.5% to staking, 1% to treasury
        burnAmount = (amount * 50) / BASIS_POINTS; // 0.5%
        stakingAmount = (amount * 50) / BASIS_POINTS; // 0.5%
        treasuryAmount = (amount * 100) / BASIS_POINTS; // 1%

        return (totalFee, burnAmount, stakingAmount, treasuryAmount);
    }

    /**
     * @notice Updates the treasury wallet address
     * @param newTreasuryWallet The new address to collect treasury fees (1%)
     */
    function setTreasuryWallet(address newTreasuryWallet) external onlyOwner {
        if (newTreasuryWallet == address(0)) revert ZeroAddress();
        treasuryWallet = newTreasuryWallet;
        emit TreasuryWalletChanged(newTreasuryWallet);
    }

    /**
     * @notice Updates the staking pool wallet address
     * @param newStakingPoolWallet The new address to collect staking pool fees (0.5%)
     */
    function setStakingPoolWallet(
        address newStakingPoolWallet
    ) external onlyOwner {
        if (newStakingPoolWallet == address(0)) revert ZeroAddress();
        stakingPoolWallet = newStakingPoolWallet;
        emit StakingPoolWalletChanged(newStakingPoolWallet);
    }

    /**
     * @notice Updates the burn wallet address
     * @param newBurnWallet The new address to send tokens for burning (0.5%)
     */
    function setBurnWallet(address newBurnWallet) external onlyOwner {
        if (newBurnWallet == address(0)) revert ZeroAddress();
        burnWallet = newBurnWallet;
        emit BurnWalletChanged(newBurnWallet);
    }

    /**
     * @notice Get the total withdrawn amount including both old and current contracts
     * @param ethAddress The Ethereum address of the user
     * @param solanaAddress The Solana address of the user
     * @return total Total withdrawn amount across both contracts
     */
    function getTotalWithdrawnAmount(
        address ethAddress,
        bytes32 solanaAddress
    ) public view returns (uint256) {
        uint256 currentContractWithdrawn = amountWithdrawnFromSolana[
            ethAddress
        ][solanaAddress];
        uint256 oldContractWithdrawn = address(oldSolanaAdapter) != address(0)
            ? oldSolanaAdapter.amountWithdrawnFromSolana(
                ethAddress,
                solanaAddress
            )
            : 0;

        return currentContractWithdrawn + oldContractWithdrawn;
    }

    function depositOnSolana(uint256 amount, bytes32 solanaAddress) external {
        if (depositPaused) {
            revert DepositPaused();
        }

        // Transfer tokens - net amount to this contract
        HELLO_TOKEN.transferFrom(msg.sender, address(this), amount);

        // Send to Solana chain
        $bridge.sendToChain(amount, SOLANA_CHAIN_ID);

        uint256 totalFee = (amount * feePercentage) / BASIS_POINTS;

        // Update deposits tracking
        amountDepositedOnSolana[msg.sender][solanaAddress] += amount - totalFee;

        emit Deposit(msg.sender, amount - totalFee, solanaAddress);
    }

    /**
     * @notice Withdraw from Solana
     * @param ethAddress The address of the user on Ethereum
     * @param solanaAddress The address of the user on Solana
     * @param totalAmountDepositedToSolanaAdapter The total amount deposited on the Solana Bridge for this chain
     * @param totalUserAmountWithdrawable The total amount that the user can withdraw
     * @param bridgeSig1 The signature to use in the claim from the bridge
     * @param bridgeSig2 The signature to use in the claim from the bridge
     * @param adapterSig1 The signature to check in the adapter
     * @param adapterSig2 The signature to check in the adapter
     */
    function withdrawFromSolana(
        address ethAddress,
        bytes32 solanaAddress,
        uint256 totalAmountDepositedToSolanaAdapter,
        uint256 totalUserAmountWithdrawable,
        bytes calldata bridgeSig1,
        bytes calldata bridgeSig2,
        bytes calldata adapterSig1,
        bytes calldata adapterSig2
    ) external {
        if (withdrawPaused) {
            revert WithdrawPaused();
        }

        // Check that all fee wallets are set
        if (
            treasuryWallet == address(0) ||
            stakingPoolWallet == address(0) ||
            burnWallet == address(0)
        ) revert FeeWalletsNotSet();

        HelloBridge _bridge = $bridge;
        HelloBridgeStore _store = $store;

        // Calculate total withdrawn amount from both old and current contracts
        uint256 totalWithdrawn = getTotalWithdrawnAmount(
            ethAddress,
            solanaAddress
        );
        uint256 amountToWithdraw = totalUserAmountWithdrawable - totalWithdrawn;

        // Exit early if nothing to withdraw
        if (amountToWithdraw == 0) return;

        _checkAdapterSignatures(
            _bridge,
            ethAddress,
            solanaAddress,
            totalUserAmountWithdrawable,
            adapterSig1,
            adapterSig2
        );

        bool bridgeClaimPerformed = _claimFromBridge(
            _bridge,
            _store,
            totalAmountDepositedToSolanaAdapter,
            amountToWithdraw,
            bridgeSig1,
            bridgeSig2
        );

        // SOLUTION 2: Add check for sufficient adapter balance before proceeding
        if (!bridgeClaimPerformed) {
            // Calculate total amount needed (including fees)
            (
                uint256 totalFee,
                uint256 burnAmount,
                uint256 stakingAmount,
                uint256 treasuryAmount
            ) = calculateFees(amountToWithdraw);
            uint256 totalTokensNeeded = amountToWithdraw + totalFee;

            // Check if adapter has sufficient balance
            if (HELLO_TOKEN.balanceOf(address(this)) < totalTokensNeeded) {
                revert InsufficientAdapterBalance();
            }
        }

        _handleWithdraw(ethAddress, solanaAddress, totalUserAmountWithdrawable);
    }

    /// @dev anyone can call this function once the allowance has run low
    /// @dev this contract should never hold tokens, so this function is safe
    function approveBridge() external {
        HELLO_TOKEN.approve(address($bridge), type(uint256).max);
    }

    function setWithdrawPaused(bool _paused) external onlyOwner {
        withdrawPaused = _paused;
        emit PauseChanged(false, _paused);
    }

    function setDepositPaused(bool _paused) external onlyOwner {
        depositPaused = _paused;
        emit PauseChanged(true, _paused);
    }

    function setBridge(address _bridge) external onlyOwner {
        $bridge = HelloBridge(_bridge);
        emit BridgeChanged(_bridge);
    }

    function setStore(address _store) external onlyOwner {
        $store = HelloBridgeStore(_store);
        emit StoreChanged(_store);
    }

    // New function for Solution 2: Get adapter's available balance
    function getAvailableBalance() external view returns (uint256) {
        return HELLO_TOKEN.balanceOf(address(this));
    }

    function getClaimFromSolanaDigest(
        address ethAddress,
        bytes32 solanaAddress,
        uint256 totalUserAmountWithdrawable
    ) public view returns (bytes32) {
        bytes32 structHash = keccak256(
            abi.encode(
                CLAIM_FROM_SOLANA_TYPEHASH,
                ethAddress,
                solanaAddress,
                totalUserAmountWithdrawable
            )
        );
        return
            keccak256(
                abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)
            );
    }

    //------------------ Internal functions ------------------//

    function _handleWithdraw(
        address ethAddress,
        bytes32 solanaAddress,
        uint256 totalAmountWithdrawable
    ) internal {
        // Calculate total withdrawn amount from both old and current contracts
        uint256 totalWithdrawn = getTotalWithdrawnAmount(
            ethAddress,
            solanaAddress
        );
        uint256 amountToWithdraw = totalAmountWithdrawable - totalWithdrawn;

        // Calculate fees on the amount to withdraw
        (
            uint256 totalFee,
            uint256 burnAmount,
            uint256 stakingAmount,
            uint256 treasuryAmount
        ) = calculateFees(amountToWithdraw);
        uint256 netAmount = amountToWithdraw - totalFee;

        // Update withdrawal tracking - assign only the difference to the current contract
        // We deduct old contract withdrawal amount from totalAmountWithdrawable
        uint256 oldContractWithdrawn = address(oldSolanaAdapter) != address(0)
            ? oldSolanaAdapter.amountWithdrawnFromSolana(
                ethAddress,
                solanaAddress
            )
            : 0;

        amountWithdrawnFromSolana[ethAddress][solanaAddress] =
            totalAmountWithdrawable -
            oldContractWithdrawn;

        // Transfer net amount to user
        HELLO_TOKEN.transfer(ethAddress, netAmount);

        // Transfer fees to respective destinations
        HELLO_TOKEN.transfer(burnWallet, burnAmount);
        HELLO_TOKEN.transfer(stakingPoolWallet, stakingAmount);
        HELLO_TOKEN.transfer(treasuryWallet, treasuryAmount);

        emit Withdraw(ethAddress, netAmount, solanaAddress);
        emit FeeCollected(
            ethAddress,
            totalFee,
            burnAmount,
            stakingAmount,
            treasuryAmount
        );
    }

    /**
     * @dev Attempt to claim tokens from the bridge
     * @return bridgeClaimPerformed Whether the tokens were claimed from the bridge
     */
    function _claimFromBridge(
        HelloBridge _bridge,
        HelloBridgeStore _store,
        uint256 totalAmountDepositedToSolanaAdapter,
        uint256 totalUserAmountWithdrawable,
        bytes calldata bridgeSig1,
        bytes calldata bridgeSig2
    ) internal returns (bool bridgeClaimPerformed) {
        uint256 currentWithdrawals = _store.totalCrossChainWithdrawals(
            address(this),
            SOLANA_CHAIN_ID
        );

        //If a transaction has front-run the claim, the bridge will revert
        //So we only claim if the total amount deposited (approved by signers) is less than the total amount withdrawn
        if (currentWithdrawals < totalAmountDepositedToSolanaAdapter) {
            _bridge.claimFromChainSolana(
                totalAmountDepositedToSolanaAdapter, // totalDepositedOnOtherChain
                SOLANA_CHAIN_ID, // otherChainId
                totalUserAmountWithdrawable,
                bridgeSig1, // signature1
                bridgeSig2 // signature2
            );
            return true;
        } else {
            // SOLUTION 2: Log when bridge claim is skipped
            emit BridgeClaimSkipped(
                currentWithdrawals,
                totalAmountDepositedToSolanaAdapter
            );
            return false;
        }
    }

    function _checkAdapterSignatures(
        HelloBridge _bridge,
        address ethAddress,
        bytes32 solanaAddress,
        uint256 totalUserAmountWithdrawable,
        bytes calldata adapterSig1,
        bytes calldata adapterSig2
    ) internal view {
        bytes32 digest = getClaimFromSolanaDigest(
            ethAddress,
            solanaAddress,
            totalUserAmountWithdrawable
        );

        if (
            !_isValidSignature(_bridge.withdrawSigner1(), digest, adapterSig1)
        ) {
            revert InvalidSolanaSignature1();
        }
        if (
            !_isValidSignature(_bridge.withdrawSigner2(), digest, adapterSig2)
        ) {
            revert InvalidSolanaSignature2();
        }
    }

    function withdrawTokens(uint256 _amount) external onlyOwner {
        HELLO_TOKEN.transfer(msg.sender, _amount);
    }

    /**
     * @dev Checks if a signature is valid for a signer and digest
     * @param signer The address that should have signed the digest
     * @param digest The digest that was signed
     * @param signature The signature to check
     */
    function _isValidSignature(
        address signer,
        bytes32 digest,
        bytes memory signature
    ) internal pure returns (bool) {
        if (signature.length != 65) {
            return false;
        }

        bytes32 r;
        bytes32 s;
        uint8 v;

        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }

        if (
            uint256(s) >
            0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
        ) {
            return false;
        }

        if (v != 27 && v != 28) {
            return false;
        }

        address recoveredSigner = ecrecover(digest, v, r, s);
        if (recoveredSigner == address(0)) {
            return false;
        }

        return recoveredSigner == signer;
    }

    // Custom error for zero address checks
    error ZeroAddress();
}
HelloBridgeStore.sol 108 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

///@notice The contract is a storage contract for the bridge contract.
///@notice The owner will always be a multisig wallet.

/* -------------------------------------------------------------------------- */
/*                                   errors                                   */
/* -------------------------------------------------------------------------- */
error SignerNotWithdrawSigner();
error NoAmountToWithdraw();
error CannotBridgeToUnsupportedChain();
error Paused();
error NotPaused();
error ZeroAddress();

contract HelloBridgeStore is Ownable(msg.sender) {
    /* -------------------------------------------------------------------------- */
    /*                                   errors                                   */
    /* -------------------------------------------------------------------------- */
    error ErrUnauthorized();
    error ErrZeroAddress();

    /* -------------------------------------------------------------------------- */
    /*                                   events                                   */
    /* -------------------------------------------------------------------------- */
    event BridgeContractChanged(address bridgeContract);

    /* -------------------------------------------------------------------------- */
    /*                                   states                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice A mapping that stores how much HelloToken a user has deposited to bridge to a destination chain
     * @dev Maps from userAddress -> chainID -> amount
     */
    mapping(address => mapping(uint256 => uint256))
        public totalCrossChainDeposits;

    /**
     * @notice A mapping that stores how much HelloToken a user has withdrawn from a destination chain
     * @dev Maps from userAddress -> chainID -> amount
     */
    mapping(address => mapping(uint256 => uint256))
        public totalCrossChainWithdrawals;

    /**
     * @notice The bridge contract associated with this storage contract.
     * This is the only contract authorized to update `totalCrossChainDeposits` & `totalCrossChainWithdrawals`
     */
    address public bridgeContract;

    /* -------------------------------------------------------------------------- */
    /*                                    owner                                   */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Updates the bridge contract associated with this storage contract
     */
    function setBridgeContract(address b_) external onlyOwner {
        if (b_ == address(0)) {
            revert ErrZeroAddress();
        }

        bridgeContract = b_;

        emit BridgeContractChanged(b_);
    }

    /* -------------------------------------------------------------------------- */
    /*                                  external                                  */
    /* -------------------------------------------------------------------------- */
    /**
     * @notice Updates totalCrossChainDeposits of a user to a destination chain
     * @param address_ The address to update
     * @param chainID_ The destination chainID of the deposit
     * @param amount_ The amount of token deposited
     */
    function setTotalCrossChainDeposits(
        address address_,
        uint256 chainID_,
        uint256 amount_
    ) external {
        if (msg.sender != bridgeContract) {
            revert ErrUnauthorized();
        }

        totalCrossChainDeposits[address_][chainID_] = amount_;
    }

    /**
     * @notice Updates totalCrossChainWithdrawals of a user from a source chain
     * @param address_ The address to update
     * @param chainID_ The source chainID of the withdrawal
     * @param amount_ The amount of token withdrawn
     */
    function setTotalCrossChainWithdrawals(
        address address_,
        uint256 chainID_,
        uint256 amount_
    ) external {
        if (msg.sender != bridgeContract) {
            revert ErrUnauthorized();
        }

        totalCrossChainWithdrawals[address_][chainID_] = amount_;
    }
}

Read Contract

$bridge 0xfcbb7120 → address
$store 0x6a622089 → address
CLAIM_FROM_SOLANA_TYPEHASH 0xa58dd548 → bytes32
HELLO_TOKEN 0x257f9e7b → address
SOLANA_CHAIN_ID 0x067bd07a → uint256
amountDepositedOnSolana 0x3b1a2819 → uint256
amountWithdrawnFromSolana 0x81e078df → uint256
burnWallet 0x06228749 → address
calculateFees 0x52238fdd → uint256, uint256, uint256, uint256
depositPaused 0x02befd24 → bool
feePercentage 0xa001ecdd → uint256
getAvailableBalance 0x809dab6a → uint256
getClaimFromSolanaDigest 0xf968e6f6 → bytes32
getTotalWithdrawnAmount 0x204229b8 → uint256
oldSolanaAdapter 0xd309a4ce → address
owner 0x8da5cb5b → address
stakingPoolWallet 0x26190b47 → address
treasuryWallet 0x4626402b → address
withdrawPaused 0x2f3ffb9f → bool

Write Contract 13 functions

These functions modify contract state and require a wallet transaction to execute.

approveBridge 0x1844d9c8
No parameters
depositOnSolana 0x0d3c34e8
uint256 amount
bytes32 solanaAddress
renounceOwnership 0x715018a6
No parameters
setBridge 0x8dd14802
address _bridge
setBurnWallet 0x1c4ba3ed
address newBurnWallet
setDepositPaused 0x543f66a4
bool _paused
setStakingPoolWallet 0x91274f68
address newStakingPoolWallet
setStore 0x087cbd40
address _store
setTreasuryWallet 0xa8602fea
address newTreasuryWallet
setWithdrawPaused 0x37d15139
bool _paused
transferOwnership 0xf2fde38b
address newOwner
withdrawFromSolana 0x3ca587d6
address ethAddress
bytes32 solanaAddress
uint256 totalAmountDepositedToSolanaAdapter
uint256 totalUserAmountWithdrawable
bytes bridgeSig1
bytes bridgeSig2
bytes adapterSig1
bytes adapterSig2
withdrawTokens 0x315a095d
uint256 _amount

Recent Transactions

No transactions found for this address