diff --git a/Cargo.lock b/Cargo.lock index a98a4caab..63451e2e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,8 +4341,8 @@ dependencies = [ "hex", "rand 0.7.3", "serde", - "tokio", "thiserror 1.0.69", + "tokio", "tokio-stream", "tracing", ] diff --git a/protocol-units/bridge/contracts/src/INativeBridge.sol b/protocol-units/bridge/contracts/src/INativeBridge.sol index 3e21a9d9c..cf079c352 100644 --- a/protocol-units/bridge/contracts/src/INativeBridge.sol +++ b/protocol-units/bridge/contracts/src/INativeBridge.sol @@ -21,7 +21,7 @@ interface INativeBridge { event InsuranceFundUpdated(address insuranceFund); event PauseToggled(bool paused); - event RiskDenominatorUpdated(uint256 riskDenominator); + event InsuranceBudgetDividerUpdated(uint256 insuranceBudgetDivider); error ZeroAmount(); error MOVETransferFailed(); @@ -32,7 +32,7 @@ interface INativeBridge { error InvalidNonce(); error OutboundRateLimitExceeded(); error InboundRateLimitExceeded(); - error InvalidRiskDenominator(); + error InvalidInsuranceBudgetDivider(); /** * @dev Creates a new bridge diff --git a/protocol-units/bridge/contracts/src/NativeBridge.sol b/protocol-units/bridge/contracts/src/NativeBridge.sol index 1b25451d0..9464cad88 100644 --- a/protocol-units/bridge/contracts/src/NativeBridge.sol +++ b/protocol-units/bridge/contracts/src/NativeBridge.sol @@ -16,15 +16,22 @@ contract NativeBridge is AccessControlUpgradeable, PausableUpgradeable, INativeB uint256 amount; } + // Inbound: Circulating token is released on the destination chain (unlock or mint). + // Outbound: Circulating token is taken from the source chain (lock or burn). mapping(uint256 nonce => OutboundTransfer) public noncesToOutboundTransfers; mapping(bytes32 bridgeTransferId => uint256 nonce) public idsToInboundNonces; mapping(uint256 day => uint256 amount) public inboundRateLimitBudget; + mapping(uint256 day => uint256 amount) public outboundRateLimitBudget; bytes32 public constant RELAYER_ROLE = keccak256(abi.encodePacked("RELAYER_ROLE")); - uint256 public constant MINIMUM_RISK_DENOMINATOR = 3; + bytes32 public constant PAUSER_ROLE = keccak256(abi.encodePacked("PAUSER_ROLE")); + + // The insuranceBudgetDivider determines the fraction of the insurance fund that can be used for the per day budget for a given transfer direction. + // inusrance budget divider must be equal or above 4 + uint256 public constant INSURANCE_BUDGET_DIVIDER_LOWER_BOUND = 3; IERC20 public moveToken; address public insuranceFund; - uint256 public riskDenominator; + uint256 public insuranceBudgetDivider; uint256 private _nonce; // Prevents initialization of implementation contract exploits @@ -38,6 +45,7 @@ contract NativeBridge is AccessControlUpgradeable, PausableUpgradeable, INativeB * @param _admin The address of the admin role * @param _relayer The address of the relayer role * @param _maintainer The address of the maintainer role + * @param _insuranceFund The address of the insurance fund */ function initialize( address _moveToken, @@ -54,10 +62,12 @@ contract NativeBridge is AccessControlUpgradeable, PausableUpgradeable, INativeB // Set insurance fund insuranceFund = _insuranceFund; - riskDenominator = MINIMUM_RISK_DENOMINATOR + 1; + insuranceBudgetDivider = INSURANCE_BUDGET_DIVIDER_LOWER_BOUND + 1; // Maintainer is optional - _grantRole(RELAYER_ROLE, _maintainer); + if (_maintainer != address(0)) { + _grantRole(RELAYER_ROLE, _maintainer); + } } /** @@ -71,9 +81,12 @@ contract NativeBridge is AccessControlUpgradeable, PausableUpgradeable, INativeB external whenNotPaused returns (bytes32 bridgeTransferId) - { - // Ensure there is a valid amount + { + // Ensure there is a valid amount` require(amount > 0, ZeroAmount()); + + _rateLimitOutbound(amount); + address initiator = msg.sender; // Transfer the MOVE tokens from the user to the contract @@ -172,35 +185,48 @@ contract NativeBridge is AccessControlUpgradeable, PausableUpgradeable, INativeB } /** - * @dev Sets the risk denominator for the bridge - * @param _riskDenominator The new risk denominator + * @dev Sets the insurance budget divider for the bridge + * @param _insuranceBudgetDivider The new insurance budget divider */ - function setRiskDenominator(uint256 _riskDenominator) external onlyRole(DEFAULT_ADMIN_ROLE) { - // risk denominator must be at least 4 - require(_riskDenominator > MINIMUM_RISK_DENOMINATOR, InvalidRiskDenominator()); - riskDenominator = _riskDenominator; - emit RiskDenominatorUpdated(_riskDenominator); + function setInsuranceBudgetDivider(uint256 _insuranceBudgetDivider) external onlyRole(DEFAULT_ADMIN_ROLE) { + // insurance budget divider must be at least 4 + require(_insuranceBudgetDivider > INSURANCE_BUDGET_DIVIDER_LOWER_BOUND, InvalidInsuranceBudgetDivider()); + insuranceBudgetDivider = _insuranceBudgetDivider; + emit InsuranceBudgetDividerUpdated(_insuranceBudgetDivider); } /** * @dev Toggles the paused state of the contract */ - function togglePause() external onlyRole(DEFAULT_ADMIN_ROLE) { + function togglePause() external onlyRole(PAUSER_ROLE) { paused() ? _pause() : _unpause(); emit PauseToggled(paused()); } /** - * @dev Rate limits the inbound transfers based on the insurance fund and risk denominator + * @dev Rate limits the outboud transfers based on the insurance fund and insurance budget divider * @param amount The amount to rate limit */ + function _rateLimitOutbound(uint256 amount) public { + uint256 day = block.timestamp / 1 days; + outboundRateLimitBudget[day] += amount; + require( + outboundRateLimitBudget[day] < moveToken.balanceOf(insuranceFund) / insuranceBudgetDivider, + OutboundRateLimitExceeded() + ); + } + /** + * @dev Rate limits the inbound transfers based on the insurance fund and insurance budget divider + * @param amount The amount to rate limit + */ function _rateLimitInbound(uint256 amount) public { uint256 day = block.timestamp / 1 days; inboundRateLimitBudget[day] += amount; require( - inboundRateLimitBudget[day] < moveToken.balanceOf(insuranceFund) / riskDenominator, + inboundRateLimitBudget[day] < moveToken.balanceOf(insuranceFund) / insuranceBudgetDivider, InboundRateLimitExceeded() ); } + } diff --git a/protocol-units/bridge/contracts/test/NativeBridge.t.sol b/protocol-units/bridge/contracts/test/NativeBridge.t.sol index 2604c64b7..d7043a899 100644 --- a/protocol-units/bridge/contracts/test/NativeBridge.t.sol +++ b/protocol-units/bridge/contracts/test/NativeBridge.t.sol @@ -51,7 +51,7 @@ contract NativeBridgeTest is Test { vm.assume(_originator != address(0)); vm.assume(_originator != deployer); - _amount = bound(_amount, 1, 1000000 * 1e8 - 1); + _amount = bound(_amount, 1, 1000000 * 1e8 / 4 - 1); moveToken.transfer(_originator, _amount); vm.startPrank(_originator); // require approval diff --git a/protocol-units/settlement/mcr/contracts/lib/v2-periphery b/protocol-units/settlement/mcr/contracts/lib/v2-periphery new file mode 160000 index 000000000..0335e8f7e --- /dev/null +++ b/protocol-units/settlement/mcr/contracts/lib/v2-periphery @@ -0,0 +1 @@ +Subproject commit 0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f