-
Notifications
You must be signed in to change notification settings - Fork 10
feat: fee collector #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
03dfe4c
70c8d3a
c5f845f
ed8ae39
1f23e98
5a94ad1
e8893e7
0aae77b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: Contracts CI | ||
|
|
||
| on: | ||
| push: | ||
| paths: | ||
| - 'contracts/**' | ||
| pull_request: | ||
| paths: | ||
| - 'contracts/**' | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| FOUNDRY_PROFILE: ci | ||
|
|
||
| jobs: | ||
| check: | ||
| name: Foundry project | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: contracts | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| submodules: recursive | ||
|
|
||
| - name: Install Foundry | ||
| uses: foundry-rs/foundry-toolchain@v1 | ||
|
|
||
| - name: Show Forge version | ||
| run: | | ||
| forge --version | ||
|
|
||
| - name: Run Forge fmt | ||
| run: | | ||
| forge fmt --check | ||
| id: fmt | ||
|
|
||
| - name: Run Forge build | ||
| run: | | ||
| forge build --sizes | ||
| id: build | ||
|
|
||
| - name: Run Forge tests | ||
| run: | | ||
| forge test -vvv | ||
| id: test |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [submodule "contracts/lib/forge-std"] | ||
| path = contracts/lib/forge-std | ||
| url = https://github.com/foundry-rs/forge-std |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Compiler files | ||
| cache/ | ||
| out/ | ||
|
|
||
| # Ignores development broadcast logs | ||
| !/broadcast | ||
| /broadcast/*/31337/ | ||
| /broadcast/**/dry-run/ | ||
|
|
||
| # Docs | ||
| docs/ | ||
|
|
||
| # Dotenv file | ||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| ## Foundry | ||
|
|
||
| **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** | ||
|
|
||
| Foundry consists of: | ||
|
|
||
| - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). | ||
| - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. | ||
| - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. | ||
| - **Chisel**: Fast, utilitarian, and verbose solidity REPL. | ||
|
|
||
| ## Documentation | ||
|
|
||
| https://book.getfoundry.sh/ | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Build | ||
|
|
||
| ```shell | ||
| $ forge build | ||
| ``` | ||
|
|
||
| ### Test | ||
|
|
||
| ```shell | ||
| $ forge test | ||
| ``` | ||
|
|
||
| ### Format | ||
|
|
||
| ```shell | ||
| $ forge fmt | ||
| ``` | ||
|
|
||
| ### Gas Snapshots | ||
|
|
||
| ```shell | ||
| $ forge snapshot | ||
| ``` | ||
|
|
||
| ### Anvil | ||
|
|
||
| ```shell | ||
| $ anvil | ||
| ``` | ||
|
|
||
| ### Deploy | ||
|
|
||
| ```shell | ||
| $ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key> | ||
| ``` | ||
|
|
||
| ### Cast | ||
|
|
||
| ```shell | ||
| $ cast <subcommand> | ||
| ``` | ||
|
|
||
| ### Help | ||
|
|
||
| ```shell | ||
| $ forge --help | ||
| $ anvil --help | ||
| $ cast --help | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [profile.default] | ||
| src = "src" | ||
| out = "out" | ||
| libs = ["lib"] | ||
|
|
||
| # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.24; | ||
|
|
||
| interface IHypNativeMinter { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also consider moving this impl to this repo or somewhere else as we spoke about. I'm happy to do that in a following PR tho if we decide that. Let's wait and see
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to point out that this implementation is maintained here rn https://github.com/celestiaorg/hyperlane-ops/tree/main/solidity |
||
| function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) | ||
| external | ||
| payable | ||
| returns (bytes32 messageId); | ||
| } | ||
|
|
||
| contract FeeVault { | ||
| IHypNativeMinter public immutable hypNativeMinter; | ||
|
|
||
| address public owner; | ||
| uint32 public destinationDomain; | ||
| bytes32 public recipientAddress; | ||
| uint256 public minimumAmount; | ||
| uint256 public callFee; | ||
|
|
||
| // Split accounting | ||
| address public otherRecipient; | ||
| uint256 public bridgeShareBps; // Basis points (0-10000) for bridge share | ||
|
|
||
| event SentToCelestia(uint256 amount, bytes32 recipient, bytes32 messageId); | ||
| event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | ||
| event RecipientUpdated(uint32 destinationDomain, bytes32 recipientAddress); | ||
| event MinimumAmountUpdated(uint256 minimumAmount); | ||
| event CallFeeUpdated(uint256 callFee); | ||
| event BridgeShareUpdated(uint256 bridgeShareBps); | ||
| event OtherRecipientUpdated(address otherRecipient); | ||
| event FundsSplit(uint256 totalNew, uint256 bridgeAmount, uint256 otherAmount); | ||
|
|
||
| modifier onlyOwner() { | ||
| require(msg.sender == owner, "FeeVault: caller is not the owner"); | ||
| _; | ||
| } | ||
|
|
||
| constructor(address _hypNativeMinter, address _owner) { | ||
| hypNativeMinter = IHypNativeMinter(_hypNativeMinter); | ||
| owner = _owner; | ||
| bridgeShareBps = 10000; // Default to 100% bridge | ||
| emit OwnershipTransferred(address(0), _owner); | ||
| } | ||
|
|
||
| receive() external payable {} | ||
|
|
||
| function sendToCelestia() external payable { | ||
| require(msg.value >= callFee, "FeeVault: insufficient fee"); | ||
|
|
||
| uint256 currentBalance = address(this).balance; | ||
|
|
||
| // Calculate split | ||
| uint256 bridgeAmount = (currentBalance * bridgeShareBps) / 10000; | ||
| uint256 otherAmount = currentBalance - bridgeAmount; | ||
|
|
||
| require(bridgeAmount >= minimumAmount, "FeeVault: minimum amount not met"); | ||
|
|
||
| emit FundsSplit(currentBalance, bridgeAmount, otherAmount); | ||
|
|
||
| // Send other amount if any | ||
| if (otherAmount > 0) { | ||
| require(otherRecipient != address(0), "FeeVault: other recipient not set"); | ||
| (bool success,) = otherRecipient.call{value: otherAmount}(""); | ||
| require(success, "FeeVault: transfer failed"); | ||
| } | ||
|
|
||
| // Bridge the bridge amount | ||
| bytes32 messageId = | ||
| hypNativeMinter.transferRemote{value: bridgeAmount}(destinationDomain, recipientAddress, bridgeAmount); | ||
|
|
||
| emit SentToCelestia(bridgeAmount, recipientAddress, messageId); | ||
| } | ||
|
|
||
| // Admin functions | ||
|
|
||
| function transferOwnership(address newOwner) external onlyOwner { | ||
| require(newOwner != address(0), "FeeVault: new owner is the zero address"); | ||
| emit OwnershipTransferred(owner, newOwner); | ||
| owner = newOwner; | ||
| } | ||
|
|
||
| function setRecipient(uint32 _destinationDomain, bytes32 _recipientAddress) external onlyOwner { | ||
| destinationDomain = _destinationDomain; | ||
| recipientAddress = _recipientAddress; | ||
| emit RecipientUpdated(_destinationDomain, _recipientAddress); | ||
| } | ||
|
|
||
| function setMinimumAmount(uint256 _minimumAmount) external onlyOwner { | ||
| minimumAmount = _minimumAmount; | ||
| emit MinimumAmountUpdated(_minimumAmount); | ||
| } | ||
|
|
||
| function setCallFee(uint256 _callFee) external onlyOwner { | ||
| callFee = _callFee; | ||
| emit CallFeeUpdated(_callFee); | ||
| } | ||
|
|
||
| function setBridgeShare(uint256 _bridgeShareBps) external onlyOwner { | ||
| require(_bridgeShareBps <= 10000, "FeeVault: invalid bps"); | ||
| bridgeShareBps = _bridgeShareBps; | ||
| emit BridgeShareUpdated(_bridgeShareBps); | ||
| } | ||
|
|
||
| function setOtherRecipient(address _otherRecipient) external onlyOwner { | ||
| require(_otherRecipient != address(0), "FeeVault: zero address"); | ||
| otherRecipient = _otherRecipient; | ||
| emit OtherRecipientUpdated(_otherRecipient); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, this makes sense to me. Looks like exactly what we described when talking about it 👍🏻