Converting the OP stack hooks to transient storage version (#2632)
- Updated the OP Stack tests for the Mailbox V3 transient storage version - Allowing OPStackHook to send msg.value at the time of message delivery (uses bit masking) - Added LibBit library, will be useful for all the auth hooks which can send `msg.value` - None - Fixes breaking OP Stack tests for https://github.com/hyperlane-xyz/issues/issues/513 - Also fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2410 No Unit tests --------- Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz> Remove e2e and add yarn build to CI Adding protocol fees (#2640) - Adding protocol fee as a hook - None V3 Yes Fuzz tests --------- Co-authored-by: Yorke Rhodes <yorke@hyperlane.xyz>pull/2736/head
parent
f38660e70a
commit
760dce657b
@ -0,0 +1,125 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/*@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@ HYPERLANE @@@@@@@ |
||||
@@@@@@@@@@@@@@@@@@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@@ |
||||
@@@@@@@@@ @@@@@@@@*/ |
||||
|
||||
// ============ Internal Imports ============ |
||||
import {Message} from "../libs/Message.sol"; |
||||
import {IPostDispatchHook} from "../interfaces/hooks/IPostDispatchHook.sol"; |
||||
// ============ External Imports ============ |
||||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; |
||||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
||||
|
||||
/** |
||||
* @title StaticProtocolFee |
||||
* @notice Collects a static protocol fee from the sender. |
||||
* @dev V3 WIP |
||||
*/ |
||||
contract StaticProtocolFee is IPostDispatchHook, Ownable { |
||||
using Address for address payable; |
||||
using Message for bytes; |
||||
|
||||
// ============ Constants ============ |
||||
|
||||
/// @notice The maximum protocol fee that can be set. |
||||
uint256 public immutable MAX_PROTOCOL_FEE; |
||||
|
||||
// ============ Public Storage ============ |
||||
|
||||
/// @notice The current protocol fee. |
||||
uint256 public protocolFee; |
||||
/// @notice The beneficiary of protocol fees. |
||||
address public beneficiary; |
||||
|
||||
// ============ Constructor ============ |
||||
|
||||
constructor( |
||||
uint256 _maxProtocolFee, |
||||
uint256 _protocolFee, |
||||
address _beneficiary, |
||||
address _owner |
||||
) { |
||||
MAX_PROTOCOL_FEE = _maxProtocolFee; |
||||
_setProtocolFee(_protocolFee); |
||||
_setBeneficiary(_beneficiary); |
||||
_transferOwnership(_owner); |
||||
} |
||||
|
||||
// ============ External Functions ============ |
||||
|
||||
/** |
||||
* @notice Collects the protocol fee from the sender. |
||||
*/ |
||||
function postDispatch(bytes calldata, bytes calldata message) |
||||
external |
||||
payable |
||||
override |
||||
{ |
||||
require( |
||||
msg.value >= protocolFee, |
||||
"StaticProtocolFee: insufficient protocol fee" |
||||
); |
||||
|
||||
uint256 refund = msg.value - protocolFee; |
||||
if (refund > 0) payable(message.senderAddress()).sendValue(refund); |
||||
} |
||||
|
||||
/** |
||||
* @notice Sets the protocol fee. |
||||
* @param _protocolFee The new protocol fee. |
||||
*/ |
||||
function setProtocolFee(uint256 _protocolFee) external onlyOwner { |
||||
_setProtocolFee(_protocolFee); |
||||
} |
||||
|
||||
/** |
||||
* @notice Sets the beneficiary of protocol fees. |
||||
* @param _beneficiary The new beneficiary. |
||||
*/ |
||||
function setBeneficiary(address _beneficiary) external onlyOwner { |
||||
_setBeneficiary(_beneficiary); |
||||
} |
||||
|
||||
/** |
||||
* @notice Collects protocol fees from the contract. |
||||
*/ |
||||
function collectProtocolFees() external { |
||||
payable(beneficiary).sendValue(address(this).balance); |
||||
} |
||||
|
||||
// ============ Internal Functions ============ |
||||
|
||||
/** |
||||
* @notice Sets the protocol fee. |
||||
* @param _protocolFee The new protocol fee. |
||||
*/ |
||||
function _setProtocolFee(uint256 _protocolFee) internal { |
||||
require( |
||||
_protocolFee <= MAX_PROTOCOL_FEE, |
||||
"StaticProtocolFee: exceeds max protocol fee" |
||||
); |
||||
protocolFee = _protocolFee; |
||||
} |
||||
|
||||
/** |
||||
* @notice Sets the beneficiary of protocol fees. |
||||
* @param _beneficiary The new beneficiary. |
||||
*/ |
||||
function _setBeneficiary(address _beneficiary) internal { |
||||
require( |
||||
_beneficiary != address(0), |
||||
"StaticProtocolFee: invalid beneficiary" |
||||
); |
||||
beneficiary = _beneficiary; |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.8.0; |
||||
|
||||
/// @notice Library for bit shifting and masking |
||||
library LibBit { |
||||
function setBit(uint256 _value, uint256 _index) |
||||
internal |
||||
pure |
||||
returns (uint256) |
||||
{ |
||||
return _value | (1 << _index); |
||||
} |
||||
|
||||
function clearBit(uint256 _value, uint256 _index) |
||||
internal |
||||
pure |
||||
returns (uint256) |
||||
{ |
||||
return _value & ~(1 << _index); |
||||
} |
||||
|
||||
function isBitSet(uint256 _value, uint256 _index) |
||||
internal |
||||
pure |
||||
returns (bool) |
||||
{ |
||||
return (_value >> _index) & 1 == 1; |
||||
} |
||||
} |
@ -0,0 +1,146 @@ |
||||
// SPDX-License-Identifier: Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import {Test} from "forge-std/Test.sol"; |
||||
import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; |
||||
import {MessageUtils} from "../isms/IsmTestUtils.sol"; |
||||
|
||||
import {StaticProtocolFee} from "../../contracts/hooks/StaticProtocolFee.sol"; |
||||
|
||||
contract StaticProtocolFeeTest is Test { |
||||
using TypeCasts for address; |
||||
StaticProtocolFee internal fees; |
||||
|
||||
address internal alice = address(0x1); // alice the user |
||||
address internal bob = address(0x2); // bob the beneficiary |
||||
address internal charlie = address(0x3); // charlie the crock |
||||
|
||||
uint32 internal constant TEST_ORIGIN_DOMAIN = 1; |
||||
uint32 internal constant TEST_DESTINATION_DOMAIN = 2; |
||||
|
||||
bytes internal testMessage; |
||||
|
||||
function setUp() public { |
||||
fees = new StaticProtocolFee(1e16, 1e15, bob, address(this)); |
||||
|
||||
testMessage = _encodeTestMessage(); |
||||
} |
||||
|
||||
function testConstructor() public { |
||||
assertEq(fees.protocolFee(), 1e15); |
||||
} |
||||
|
||||
function testSetProtocolFee(uint256 fee) public { |
||||
fee = bound(fee, 0, fees.MAX_PROTOCOL_FEE()); |
||||
fees.setProtocolFee(fee); |
||||
assertEq(fees.protocolFee(), fee); |
||||
} |
||||
|
||||
function testSetProtocolFee_revertsWhen_notOwner() public { |
||||
uint256 fee = 1e17; |
||||
|
||||
vm.prank(charlie); |
||||
vm.expectRevert("Ownable: caller is not the owner"); |
||||
fees.setProtocolFee(fee); |
||||
|
||||
assertEq(fees.protocolFee(), 1e15); |
||||
} |
||||
|
||||
function testSetProtocolFee_revertWhen_exceedsMax(uint256 fee) public { |
||||
fee = bound( |
||||
fee, |
||||
fees.MAX_PROTOCOL_FEE() + 1, |
||||
10 * fees.MAX_PROTOCOL_FEE() |
||||
); |
||||
|
||||
vm.expectRevert("StaticProtocolFee: exceeds max protocol fee"); |
||||
fees.setProtocolFee(fee); |
||||
|
||||
assertEq(fees.protocolFee(), 1e15); |
||||
} |
||||
|
||||
function testSetBeneficiary_revertWhen_notOwner() public { |
||||
vm.prank(charlie); |
||||
|
||||
vm.expectRevert("Ownable: caller is not the owner"); |
||||
fees.setBeneficiary(charlie); |
||||
assertEq(fees.beneficiary(), bob); |
||||
} |
||||
|
||||
function testFuzz_postDispatch_inusfficientFees( |
||||
uint256 feeRequired, |
||||
uint256 feeSent |
||||
) public { |
||||
feeRequired = bound(feeRequired, 1, fees.MAX_PROTOCOL_FEE()); |
||||
// bound feeSent to be less than feeRequired |
||||
feeSent = bound(feeSent, 0, feeRequired - 1); |
||||
vm.deal(alice, feeSent); |
||||
|
||||
fees.setProtocolFee(feeRequired); |
||||
|
||||
uint256 balanceBefore = alice.balance; |
||||
|
||||
vm.prank(alice); |
||||
vm.expectRevert("StaticProtocolFee: insufficient protocol fee"); |
||||
fees.postDispatch{value: feeSent}("", ""); |
||||
|
||||
assertEq(alice.balance, balanceBefore); |
||||
} |
||||
|
||||
function testFuzz_postDispatch_sufficientFees( |
||||
uint256 feeRequired, |
||||
uint256 feeSent |
||||
) public { |
||||
feeRequired = bound(feeRequired, 1, fees.MAX_PROTOCOL_FEE()); |
||||
feeSent = bound(feeSent, feeRequired, 10 * feeRequired); |
||||
vm.deal(alice, feeSent); |
||||
|
||||
fees.setProtocolFee(feeRequired); |
||||
uint256 aliceBalanceBefore = alice.balance; |
||||
vm.prank(alice); |
||||
|
||||
fees.postDispatch{value: feeSent}("", testMessage); |
||||
|
||||
assertEq(alice.balance, aliceBalanceBefore - feeRequired); |
||||
} |
||||
|
||||
function testFuzz_collectProtocolFee( |
||||
uint256 feeRequired, |
||||
uint256 dispatchCalls |
||||
) public { |
||||
feeRequired = bound(feeRequired, 1, fees.MAX_PROTOCOL_FEE()); |
||||
// no of postDispatch calls to be made |
||||
dispatchCalls = bound(dispatchCalls, 1, 1000); |
||||
vm.deal(alice, feeRequired * dispatchCalls); |
||||
|
||||
fees.setProtocolFee(feeRequired); |
||||
|
||||
uint256 balanceBefore = bob.balance; |
||||
|
||||
for (uint256 i = 0; i < dispatchCalls; i++) { |
||||
vm.prank(alice); |
||||
fees.postDispatch{value: feeRequired}("", ""); |
||||
} |
||||
|
||||
fees.collectProtocolFees(); |
||||
|
||||
assertEq(bob.balance, balanceBefore + feeRequired * dispatchCalls); |
||||
} |
||||
|
||||
// ============ Helper Functions ============ |
||||
|
||||
function _encodeTestMessage() internal view returns (bytes memory) { |
||||
return |
||||
MessageUtils.formatMessage( |
||||
uint8(0), |
||||
uint32(1), |
||||
TEST_ORIGIN_DOMAIN, |
||||
alice.addressToBytes32(), |
||||
TEST_DESTINATION_DOMAIN, |
||||
alice.addressToBytes32(), |
||||
abi.encodePacked("Hello World") |
||||
); |
||||
} |
||||
|
||||
receive() external payable {} |
||||
} |
@ -0,0 +1,34 @@ |
||||
// SPDX-License-Identifier: MIT or Apache-2.0 |
||||
pragma solidity ^0.8.13; |
||||
|
||||
import {Test} from "forge-std/Test.sol"; |
||||
|
||||
import {LibBit} from "../../contracts/libs/LibBit.sol"; |
||||
|
||||
contract LibBitTest is Test { |
||||
using LibBit for uint256; |
||||
|
||||
uint256 testValue; |
||||
uint256 MAX_INT = |
||||
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; |
||||
|
||||
function setUp() public { |
||||
testValue = 0; |
||||
} |
||||
|
||||
function testSetBit(uint8 index) public { |
||||
testValue = testValue.setBit(index); |
||||
assertEq(testValue, 2**index); |
||||
} |
||||
|
||||
function testClearBit(uint8 index) public { |
||||
testValue = MAX_INT; |
||||
testValue = testValue.clearBit(index); |
||||
assertEq(testValue + 2**index, MAX_INT); |
||||
} |
||||
|
||||
function testIsBitSet(uint8 index) public { |
||||
testValue = 2**index; |
||||
assertTrue(testValue.isBitSet(index)); |
||||
} |
||||
} |
Loading…
Reference in new issue