Address Contract Verified
Address
0x3eD5AfADC677834247BB5CBf7B86f762Ae4a5C3D
Balance
0 ETH
Nonce
1
Code Size
6720 bytes
Creator
0xd2d63724...2df0 at tx 0xc6cd16a0...2fe731
Indexed Transactions
0
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