diff --git a/src/ulysses-omnichain/ArbitrumBranchBridgeAgent.sol b/src/ulysses-omnichain/ArbitrumBranchBridgeAgent.sol index 90587bc..629fd55 100644 --- a/src/ulysses-omnichain/ArbitrumBranchBridgeAgent.sol +++ b/src/ulysses-omnichain/ArbitrumBranchBridgeAgent.sol @@ -115,6 +115,10 @@ contract ArbitrumBranchBridgeAgent is BranchBridgeAgent { IArbPort(localPortAddress).withdrawFromPort(msg.sender, msg.sender, localAddress, amount); } + /// @inheritdoc IBranchBridgeAgent + /// @dev This functionality should be accessed from Root environment + function retrySettlement(uint32 _settlementNonce, uint128 _gasToBoostSettlement) external payable override lock {} + /*/////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/src/ulysses-omnichain/ArbitrumCoreBranchRouter.sol b/src/ulysses-omnichain/ArbitrumCoreBranchRouter.sol index a20fc93..1ac08c1 100644 --- a/src/ulysses-omnichain/ArbitrumCoreBranchRouter.sol +++ b/src/ulysses-omnichain/ArbitrumCoreBranchRouter.sol @@ -56,7 +56,7 @@ contract ArbitrumCoreBranchRouter is CoreBranchRouter { bytes memory packedData = abi.encodePacked(bytes1(0x02), data); //Send Cross-Chain request (System Response/Request) - IBridgeAgent(localBridgeAgentAddress).performCallOut(msg.sender, packedData, 0, 0); + IBridgeAgent(localBridgeAgentAddress).performCallOut(msg.sender, packedData, 0, 0, false); } /*/////////////////////////////////////////////////////////////// @@ -101,7 +101,7 @@ contract ArbitrumCoreBranchRouter is CoreBranchRouter { bytes memory packedData = abi.encodePacked(bytes1(0x04), data); //Send Cross-Chain request - IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, 0, 0); + IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, 0, 0, false); } /*/////////////////////////////////////////////////////////////// diff --git a/src/ulysses-omnichain/BaseBranchRouter.sol b/src/ulysses-omnichain/BaseBranchRouter.sol index 4ef1023..c6618fe 100644 --- a/src/ulysses-omnichain/BaseBranchRouter.sol +++ b/src/ulysses-omnichain/BaseBranchRouter.sol @@ -57,7 +57,7 @@ contract BaseBranchRouter is IBranchRouter, Ownable { /// @inheritdoc IBranchRouter function callOut(bytes calldata params, uint128 remoteExecutionGas) external payable lock { IBridgeAgent(localBridgeAgentAddress).performCallOut{value: msg.value}( - msg.sender, params, 0, remoteExecutionGas + msg.sender, params, 0, remoteExecutionGas, true ); } @@ -68,7 +68,7 @@ contract BaseBranchRouter is IBranchRouter, Ownable { lock { IBridgeAgent(localBridgeAgentAddress).performCallOutAndBridge{value: msg.value}( - msg.sender, params, dParams, 0, remoteExecutionGas + msg.sender, params, dParams, 0, remoteExecutionGas, true ); } @@ -79,7 +79,7 @@ contract BaseBranchRouter is IBranchRouter, Ownable { uint128 remoteExecutionGas ) external payable lock { IBridgeAgent(localBridgeAgentAddress).performCallOutAndBridgeMultiple{value: msg.value}( - msg.sender, params, dParams, 0, remoteExecutionGas + msg.sender, params, dParams, 0, remoteExecutionGas, true ); } diff --git a/src/ulysses-omnichain/BranchBridgeAgent.sol b/src/ulysses-omnichain/BranchBridgeAgent.sol index 929c621..be8caab 100644 --- a/src/ulysses-omnichain/BranchBridgeAgent.sol +++ b/src/ulysses-omnichain/BranchBridgeAgent.sol @@ -109,7 +109,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { /// @notice Address for Local Port Address where funds deposited from this chain are kept, managed and supplied to different Port Strategies. address public immutable localPortAddress; - address public bridgeAgentExecutorAddress; + address public immutable bridgeAgentExecutorAddress; /*/////////////////////////////////////////////////////////////// DEPOSITS STATE @@ -119,14 +119,14 @@ contract BranchBridgeAgent is IBranchBridgeAgent { uint32 public depositNonce; /// @notice Mapping from Pending deposits hash to Deposit Struct. - mapping(uint32 => Deposit) public getDeposit; + mapping(uint256 depositNonce => Deposit deposit) public getDeposit; /*/////////////////////////////////////////////////////////////// EXECUTOR STATE //////////////////////////////////////////////////////////////*/ /// @notice If true, bridge agent has already served a request with this nonce from a given chain. Chain -> Nonce -> Bool - mapping(uint32 => bool) public executionHistory; + mapping(uint256 settlementNonce => uint256 state) public executionState; /*/////////////////////////////////////////////////////////////// GAS MANAGEMENT STATE @@ -189,30 +189,35 @@ contract BranchBridgeAgent is IBranchBridgeAgent { } /// @inheritdoc IBranchBridgeAgent - function callOutAndBridge(bytes calldata _params, DepositInput memory _dParams, uint128 _remoteExecutionGas) - external - payable - lock - requiresFallbackGas - { + function callOutAndBridge( + bytes calldata _params, + DepositInput memory _dParams, + uint128 _remoteExecutionGas, + bool _hasFallbackToggled + ) external payable lock requiresFallbackGas { //Wrap the gas allocated for omnichain execution. wrappedNativeToken.deposit{value: msg.value}(); //Perform Call with deposit - _callOutAndBridge(msg.sender, _params, _dParams, msg.value.toUint128(), _remoteExecutionGas); + _callOutAndBridge( + msg.sender, _params, _dParams, msg.value.toUint128(), _remoteExecutionGas, _hasFallbackToggled + ); } /// @inheritdoc IBranchBridgeAgent function callOutAndBridgeMultiple( bytes calldata _params, DepositMultipleInput memory _dParams, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) external payable lock requiresFallbackGas { //Wrap the gas allocated for omnichain execution. wrappedNativeToken.deposit{value: msg.value}(); //Perform Call with multiple deposits - _callOutAndBridgeMultiple(msg.sender, _params, _dParams, msg.value.toUint128(), _remoteExecutionGas); + _callOutAndBridgeMultiple( + msg.sender, _params, _dParams, msg.value.toUint128(), _remoteExecutionGas, _hasFallbackToggled + ); } /// @inheritdoc IBranchBridgeAgent @@ -235,15 +240,15 @@ contract BranchBridgeAgent is IBranchBridgeAgent { } /// @inheritdoc IBranchBridgeAgent - function callOutSignedAndBridge(bytes calldata _params, DepositInput memory _dParams, uint128 _remoteExecutionGas) - external - payable - lock - requiresFallbackGas - { + function callOutSignedAndBridge( + bytes calldata _params, + DepositInput memory _dParams, + uint128 _remoteExecutionGas, + bool _hasFallbackToggled + ) external payable lock requiresFallbackGas { //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked( - bytes1(0x05), + _hasFallbackToggled ? bytes1(0x05) & 0x0F : bytes1(0x05), msg.sender, depositNonce, _dParams.hToken, @@ -275,7 +280,8 @@ contract BranchBridgeAgent is IBranchBridgeAgent { function callOutSignedAndBridgeMultiple( bytes calldata _params, DepositMultipleInput memory _dParams, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) external payable lock requiresFallbackGas { //Normalize Deposits uint256[] memory _deposits = new uint256[](_dParams.hTokens.length); @@ -286,7 +292,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked( - bytes1(0x06), + _hasFallbackToggled ? bytes1(0x06) & 0x0F : bytes1(0x06), msg.sender, uint8(_dParams.hTokens.length), depositNonce, @@ -321,7 +327,8 @@ contract BranchBridgeAgent is IBranchBridgeAgent { uint32 _depositNonce, bytes calldata _params, uint128 _remoteExecutionGas, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable lock requiresFallbackGas { //Check if deposit belongs to message sender if (getDeposit[_depositNonce].owner != msg.sender) revert NotDepositOwner(); @@ -331,16 +338,15 @@ contract BranchBridgeAgent is IBranchBridgeAgent { if (uint8(getDeposit[_depositNonce].hTokens.length) == 1) { if (_isSigned) { + //Pack new Data packedData = abi.encodePacked( - bytes1(0x05), + _hasFallbackToggled ? bytes1(0x85) : bytes1(0x05), msg.sender, _depositNonce, getDeposit[_depositNonce].hTokens[0], getDeposit[_depositNonce].tokens[0], getDeposit[_depositNonce].amounts[0], - _normalizeDecimals( - getDeposit[_depositNonce].deposits[0], ERC20(getDeposit[_depositNonce].tokens[0]).decimals() - ), + getDeposit[_depositNonce].deposits[0], _toChain, _params, msg.value.toUint128(), @@ -348,14 +354,12 @@ contract BranchBridgeAgent is IBranchBridgeAgent { ); } else { packedData = abi.encodePacked( - bytes1(0x02), + _hasFallbackToggled ? bytes1(0x82) : bytes1(0x02), _depositNonce, getDeposit[_depositNonce].hTokens[0], getDeposit[_depositNonce].tokens[0], getDeposit[_depositNonce].amounts[0], - _normalizeDecimals( - getDeposit[_depositNonce].deposits[0], ERC20(getDeposit[_depositNonce].tokens[0]).decimals() - ), + getDeposit[_depositNonce].deposits[0], _toChain, _params, msg.value.toUint128(), @@ -363,19 +367,17 @@ contract BranchBridgeAgent is IBranchBridgeAgent { ); } } else if (uint8(getDeposit[_depositNonce].hTokens.length) > 1) { - //Nonce - uint32 nonce = _depositNonce; - if (_isSigned) { + //Pack new Data packedData = abi.encodePacked( - bytes1(0x06), + _hasFallbackToggled ? bytes1(0x86) : bytes1(0x06), msg.sender, uint8(getDeposit[_depositNonce].hTokens.length), - nonce, - getDeposit[nonce].hTokens, - getDeposit[nonce].tokens, - getDeposit[nonce].amounts, - _normalizeDecimalsMultiple(getDeposit[nonce].deposits, getDeposit[nonce].tokens), + _depositNonce, + getDeposit[_depositNonce].hTokens, + getDeposit[_depositNonce].tokens, + getDeposit[_depositNonce].amounts, + getDeposit[_depositNonce].deposits, _toChain, _params, msg.value.toUint128(), @@ -383,13 +385,13 @@ contract BranchBridgeAgent is IBranchBridgeAgent { ); } else { packedData = abi.encodePacked( - bytes1(0x03), - uint8(getDeposit[nonce].hTokens.length), + _hasFallbackToggled ? bytes1(0x83) : bytes1(0x03), + uint8(getDeposit[_depositNonce].hTokens.length), _depositNonce, - getDeposit[nonce].hTokens, - getDeposit[nonce].tokens, - getDeposit[nonce].amounts, - _normalizeDecimalsMultiple(getDeposit[nonce].deposits, getDeposit[nonce].tokens), + getDeposit[_depositNonce].hTokens, + getDeposit[_depositNonce].tokens, + getDeposit[_depositNonce].amounts, + getDeposit[_depositNonce].deposits, _toChain, _params, msg.value.toUint128(), @@ -418,12 +420,13 @@ contract BranchBridgeAgent is IBranchBridgeAgent { function retrySettlement(uint32 _settlementNonce, uint128 _gasToBoostSettlement) external payable + virtual lock requiresFallbackGas { //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked( - bytes1(0x07), depositNonce++, _settlementNonce, msg.value.toUint128(), _gasToBoostSettlement + bytes1(0x07), depositNonce++, _settlementNonce, msg.sender, msg.value.toUint128(), _gasToBoostSettlement ); //Update State and Perform Call _sendRetrieveOrRetry(packedData); @@ -431,6 +434,9 @@ contract BranchBridgeAgent is IBranchBridgeAgent { /// @inheritdoc IBranchBridgeAgent function retrieveDeposit(uint32 _depositNonce) external payable lock requiresFallbackGas { + //Check if deposit belongs to message sender + if (getDeposit[_depositNonce].owner != msg.sender) revert NotDepositOwner(); + //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked(bytes1(0x08), _depositNonce, msg.value.toUint128(), uint128(0)); @@ -511,7 +517,8 @@ contract BranchBridgeAgent is IBranchBridgeAgent { bytes calldata _params, DepositInput memory _dParams, uint128 _gasToBridgeOut, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) external payable lock requiresRouter { //Get remote call execution deposited gas. (uint128 gasToBridgeOut, bool isRemote) = @@ -524,7 +531,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { _requiresFallbackGas(gasToBridgeOut); //Perform Call - _callOutAndBridge(_depositor, _params, _dParams, gasToBridgeOut, _remoteExecutionGas); + _callOutAndBridge(_depositor, _params, _dParams, gasToBridgeOut, _remoteExecutionGas, _hasFallbackToggled); } /// @inheritdoc IBranchBridgeAgent @@ -533,7 +540,8 @@ contract BranchBridgeAgent is IBranchBridgeAgent { bytes calldata _params, DepositMultipleInput memory _dParams, uint128 _gasToBridgeOut, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) external payable lock requiresRouter { //Get remote call execution deposited gas. (uint128 gasToBridgeOut, bool isRemote) = @@ -546,7 +554,9 @@ contract BranchBridgeAgent is IBranchBridgeAgent { _requiresFallbackGas(gasToBridgeOut); //Perform Call - _callOutAndBridgeMultiple(_depositor, _params, _dParams, gasToBridgeOut, _remoteExecutionGas); + _callOutAndBridgeMultiple( + _depositor, _params, _dParams, gasToBridgeOut, _remoteExecutionGas, _hasFallbackToggled + ); } /*/////////////////////////////////////////////////////////////// @@ -667,6 +677,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { * @param _dParams additional token deposit parameters. * @param _gasToBridgeOut gas allocated for the cross-chain call. * @param _remoteExecutionGas gas allocated for branch chain execution. + * @param _hasFallbackToggled if true, fallback is toggled on. * @dev ACTION ID: 2 (Call with single deposit) * */ @@ -675,11 +686,12 @@ contract BranchBridgeAgent is IBranchBridgeAgent { bytes calldata _params, DepositInput memory _dParams, uint128 _gasToBridgeOut, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) internal { //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked( - bytes1(0x02), + _hasFallbackToggled ? bytes1(0x02) & 0x0F : bytes1(0x02), depositNonce, _dParams.hToken, _dParams.token, @@ -711,7 +723,8 @@ contract BranchBridgeAgent is IBranchBridgeAgent { bytes calldata _params, DepositMultipleInput memory _dParams, uint128 _gasToBridgeOut, - uint128 _remoteExecutionGas + uint128 _remoteExecutionGas, + bool _hasFallbackToggled ) internal { //Normalize Deposits uint256[] memory deposits = new uint256[](_dParams.hTokens.length); @@ -722,7 +735,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { //Encode Data for cross-chain call. bytes memory packedData = abi.encodePacked( - bytes1(0x03), + _hasFallbackToggled ? bytes1(0x03) & 0x0F : bytes1(0x03), uint8(_dParams.hTokens.length), depositNonce, _dParams.hTokens, @@ -1137,7 +1150,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { address recipient = address(uint160(bytes20(data[PARAMS_START:PARAMS_START_SIGNED]))); //Get Action Flag - bytes1 flag = bytes1(data[0]); + bytes1 flag = data[0] & 0x7F; //DEPOSIT FLAG: 0 (No settlement) if (flag == 0x00) { @@ -1145,21 +1158,19 @@ contract BranchBridgeAgent is IBranchBridgeAgent { uint32 nonce = uint32(bytes4(data[PARAMS_START_SIGNED:25])); //Check if tx has already been executed - if (executionHistory[nonce]) { + if (executionState[nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } - try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoSettlement(localRouterAddress, data) - returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[nonce] = true; + //Try to execute remote request + //Flag 0 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoSettlement(localRouterAddress, data) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + abi.encodeWithSelector(BranchBridgeAgentExecutor.executeNoSettlement.selector, localRouterAddress, data) + ); //DEPOSIT FLAG: 1 (Single Asset Settlement) } else if (flag == 0x01) { @@ -1167,23 +1178,21 @@ contract BranchBridgeAgent is IBranchBridgeAgent { uint32 nonce = uint32(bytes4(data[PARAMS_START_SIGNED:25])); //Check if tx has already been executed - if (executionHistory[nonce]) { + if (executionState[nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement( - recipient, localRouterAddress, data - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[nonce] = true; + //Flag 1 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlement(recipient, localRouterAddress, data) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + abi.encodeWithSelector( + BranchBridgeAgentExecutor.executeWithSettlement.selector, recipient, localRouterAddress, data + ) + ); //DEPOSIT FLAG: 2 (Multiple Settlement) } else if (flag == 0x02) { @@ -1191,24 +1200,44 @@ contract BranchBridgeAgent is IBranchBridgeAgent { uint32 nonce = uint32(bytes4(data[22:26])); //Check if tx has already been executed - if (executionHistory[nonce]) { + if (executionState[nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlementMultiple( - recipient, localRouterAddress, data - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[nonce] = true; + // Flag 2 - BranchBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithSettlementMultiple(recipient, localRouterAddress, data) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + abi.encodeWithSelector( + BranchBridgeAgentExecutor.executeWithSettlementMultiple.selector, + recipient, + localRouterAddress, + data + ) + ); + //DEPOSIT FLAG: 3 (Retrieve Settlement) + } else if (flag == 0x03) { + //Get nonce + uint32 nonce = uint32(bytes4(data[1:5])); + + //Check if settlement is in retrieve mode + if (executionState[nonce] == 2) { + //Trigger fallback / Retry failed fallback + (success, result) = (false, ""); + } else if (executionState[nonce] == 1) { + //Set settlement to retrieve mode + executionState[nonce] = 2; + //Trigger fallback / Retry failed fallback + (success, result) = (false, ""); + } else { + _forceRevert(); + //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure + return (true, "not retrievable"); + } //Unrecognized Function Selector } else { emit LogCallin(flag, data, rootChainId); @@ -1223,6 +1252,28 @@ contract BranchBridgeAgent is IBranchBridgeAgent { _payExecutionGas(recipient, initialGas); } + function _execute(bool _hasFallbackToggled, uint256 _depositNonce, bytes memory _data) + private + returns (bool success, bytes memory reason) + { + //Try to execute remote request + (success, reason) = bridgeAgentExecutorAddress.call(_data); + + if (success) { + //Update tx state as executed + executionState[_depositNonce] = 1; + } else { + //Read fallback bit and perform fallback if necessary. If not, allow for retrying deposit. + if (_hasFallbackToggled) { + //Update tx state as retrieve only + executionState[_depositNonce] = 2; + } else { + //Interaction failure allow for retrying deposit + success = true; + } + } + } + /// @inheritdoc IApp function anyFallback(bytes calldata data) external @@ -1302,6 +1353,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { //Unrecognized Function Selector } else { + _forceRevert(); return (false, "unknown selector"); } } @@ -1376,7 +1428,7 @@ contract BranchBridgeAgent is IBranchBridgeAgent { _; } - /// @notice Modifier verifies the caller is the Anycall Executor. + /// @notice Modifier verifies the caller is the Anycall Executor. modifier requiresExecutor() { _requiresExecutor(); _; diff --git a/src/ulysses-omnichain/CoreBranchRouter.sol b/src/ulysses-omnichain/CoreBranchRouter.sol index 8d86a23..619edbb 100644 --- a/src/ulysses-omnichain/CoreBranchRouter.sol +++ b/src/ulysses-omnichain/CoreBranchRouter.sol @@ -52,7 +52,7 @@ contract CoreBranchRouter is BaseBranchRouter { //Send Cross-Chain request (System Response/Request) IBridgeAgent(localBridgeAgentAddress).performCallOut{value: msg.value}( - msg.sender, packedData, 0, _remoteExecutionGas + msg.sender, packedData, 0, _remoteExecutionGas, false ); } @@ -75,7 +75,7 @@ contract CoreBranchRouter is BaseBranchRouter { bytes memory packedData = abi.encodePacked(bytes1(0x02), data); //Send Cross-Chain request (System Response/Request) - IBridgeAgent(localBridgeAgentAddress).performCallOut{value: msg.value}(msg.sender, packedData, 0, 0); + IBridgeAgent(localBridgeAgentAddress).performCallOut{value: msg.value}(msg.sender, packedData, 0, 0, false); } /*/////////////////////////////////////////////////////////////// @@ -108,7 +108,7 @@ contract CoreBranchRouter is BaseBranchRouter { bytes memory packedData = abi.encodePacked(bytes1(0x03), data); //Send Cross-Chain request - IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, _rootExecutionGas, 0); + IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, _rootExecutionGas, 0, false); } /** @@ -151,7 +151,7 @@ contract CoreBranchRouter is BaseBranchRouter { bytes memory packedData = abi.encodePacked(bytes1(0x04), data); //Send Cross-Chain request - IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, _remoteExecutionGas, 0); + IBridgeAgent(localBridgeAgentAddress).performSystemCallOut(address(this), packedData, _remoteExecutionGas, 0, false); } /** diff --git a/src/ulysses-omnichain/CoreRootRouter.sol b/src/ulysses-omnichain/CoreRootRouter.sol index 1397722..92191b5 100644 --- a/src/ulysses-omnichain/CoreRootRouter.sol +++ b/src/ulysses-omnichain/CoreRootRouter.sol @@ -112,7 +112,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x02), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain, false); } /** @@ -163,7 +163,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x01), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut(_gasReceiver, packedData, _toChain, false); } /** @@ -241,7 +241,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x03), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain, false); } /** @@ -262,7 +262,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x04), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain, false); } /** @@ -285,7 +285,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x05), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain, false); } /** @@ -312,7 +312,7 @@ contract CoreRootRouter is IRootRouter, Ownable { bytes memory packedData = abi.encodePacked(bytes1(0x06), data); //Add new global token to branch chain - IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain); + IBridgeAgent(bridgeAgentAddress).callOut{value: msg.value}(_gasReceiver, packedData, _toChain, false); } /*/////////////////////////////////////////////////////////////// diff --git a/src/ulysses-omnichain/MulticallRootRouter.sol b/src/ulysses-omnichain/MulticallRootRouter.sol index d4f47b1..703be8a 100644 --- a/src/ulysses-omnichain/MulticallRootRouter.sol +++ b/src/ulysses-omnichain/MulticallRootRouter.sol @@ -121,7 +121,7 @@ contract MulticallRootRouter is IRootRouter, Ownable { //Move output hTokens from Root to Branch and call 'clearToken'. IBridgeAgent(bridgeAgentAddress).callOutAndBridge{value: msg.value}( - owner, recipient, "", outputToken, amountOut, depositOut, toChain + owner, recipient, "", outputToken, amountOut, depositOut, toChain, true ); } @@ -153,7 +153,7 @@ contract MulticallRootRouter is IRootRouter, Ownable { //Move output hTokens from Root to Branch and call 'clearTokens'. IBridgeAgent(bridgeAgentAddress).callOutAndBridgeMultiple{value: msg.value}( - owner, recipient, "", outputTokens, amountsOut, depositsOut, toChain + owner, recipient, "", outputTokens, amountsOut, depositsOut, toChain, true ); } diff --git a/src/ulysses-omnichain/RootBridgeAgent.sol b/src/ulysses-omnichain/RootBridgeAgent.sol index 34f4286..6d406ed 100644 --- a/src/ulysses-omnichain/RootBridgeAgent.sol +++ b/src/ulysses-omnichain/RootBridgeAgent.sol @@ -164,14 +164,14 @@ contract RootBridgeAgent is IRootBridgeAgent { uint32 public settlementNonce; /// @notice Mapping from Settlement nonce to Deposit Struct. - mapping(uint32 => Settlement) public getSettlement; + mapping(uint256 settlementNonce => Settlement settlement) public getSettlement; /*/////////////////////////////////////////////////////////////// EXECUTOR STATE //////////////////////////////////////////////////////////////*/ /// @notice If true, bridge agent has already served a request with this nonce from a given chain. Chain -> Nonce -> Bool - mapping(uint256 => mapping(uint32 => bool)) public executionHistory; + mapping(uint256 chainId => mapping(uint256 depositNonce => uint256 state)) public executionState; /*/////////////////////////////////////////////////////////////// GAS MANAGEMENT STATE @@ -241,8 +241,22 @@ contract RootBridgeAgent is IRootBridgeAgent { //////////////////////////////////////////////////////////////*/ /// @inheritdoc IRootBridgeAgent - function retrySettlement(uint32 _settlementNonce, uint128 _remoteExecutionGas) external payable { - //Update User Gas available. + function retrySettlement(uint32 _settlementNonce, uint128 _remoteExecutionGas) external payable lock { + //Avoid checks if coming form executor + if (msg.sender != bridgeAgentExecutorAddress) { + //Get deposit owner + address depositOwner = getSettlement[_settlementNonce].owner; + + //Check deposit owner + if ( + msg.sender != depositOwner + && msg.sender != address(IPort(localPortAddress).getUserAccount(depositOwner)) + ) { + revert NotSettlementOwner(); + } + } + + //Update User Gas available. if (initialGas == 0) { userFeeInfo.depositedGas = uint128(msg.value); userFeeInfo.gasToBridgeOut = _remoteExecutionGas; @@ -251,6 +265,31 @@ contract RootBridgeAgent is IRootBridgeAgent { _retrySettlement(_settlementNonce); } + function retrieveSettlement(uint32 _settlementNonce) external payable lock { + //Update User Gas available for retrieve. + if (initialGas == 0) { + userFeeInfo.depositedGas = uint128(msg.value); + userFeeInfo.gasToBridgeOut = 0; + } else { + //Function is not remote callable + return; + } + + //Get deposit owner. + address settlementOwner = getSettlement[_settlementNonce].owner; + + //Update Deposit + if (getSettlement[_settlementNonce].status != SettlementStatus.Failed || settlementOwner == address(0)) { + revert SettlementRetrieveUnavailable(); + } else if ( + msg.sender != settlementOwner + && msg.sender != address(IPort(localPortAddress).getUserAccount(settlementOwner)) + ) { + revert NotSettlementOwner(); + } + _retrieveSettlement(_settlementNonce); + } + /// @inheritdoc IRootBridgeAgent function redeemSettlement(uint32 _depositNonce) external lock { //Get deposit owner. @@ -272,10 +311,20 @@ contract RootBridgeAgent is IRootBridgeAgent { //////////////////////////////////////////////////////////////*/ /// @inheritdoc IRootBridgeAgent - function callOut(address _recipient, bytes memory _data, uint24 _toChain) external payable lock requiresRouter { + function callOut(address _recipient, bytes memory _data, uint24 _toChain, bool _hasFallbackToggled) + external + payable + lock + requiresRouter + { //Encode Data for call. - bytes memory data = - abi.encodePacked(bytes1(0x00), _recipient, settlementNonce++, _data, _manageGasOut(_toChain)); + bytes memory data = abi.encodePacked( + _hasFallbackToggled ? bytes1(0x00) & 0x0F : bytes1(0x00), + _recipient, + settlementNonce++, + _data, + _manageGasOut(_toChain) + ); //Perform Call to clear hToken balance on destination branch chain. _performCall(data, _toChain); @@ -289,7 +338,8 @@ contract RootBridgeAgent is IRootBridgeAgent { address _globalAddress, uint256 _amount, uint256 _deposit, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable lock requiresRouter { //Get destination Local Address from Global Address. address localAddress = IPort(localPortAddress).getLocalTokenFromGlobal(_globalAddress, _toChain); @@ -304,7 +354,7 @@ contract RootBridgeAgent is IRootBridgeAgent { //Prepare data for call bytes memory data = abi.encodePacked( - bytes1(0x01), + _hasFallbackToggled ? bytes1(0x01) & 0x0F : bytes1(0x01), _recipient, settlementNonce, localAddress, @@ -335,7 +385,8 @@ contract RootBridgeAgent is IRootBridgeAgent { address[] memory _globalAddresses, uint256[] memory _amounts, uint256[] memory _deposits, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable lock requiresRouter { address[] memory hTokens = new address[](_globalAddresses.length); address[] memory tokens = new address[](_globalAddresses.length); @@ -355,9 +406,12 @@ contract RootBridgeAgent is IRootBridgeAgent { } } + //Avoid stack too deep + bytes memory __data = _data; + //Prepare data for call with settlement of multiple assets bytes memory data = abi.encodePacked( - bytes1(0x02), + _hasFallbackToggled ? bytes1(0x02) & 0x0F : bytes1(0x02), _recipient, uint8(hTokens.length), settlementNonce, @@ -365,7 +419,7 @@ contract RootBridgeAgent is IRootBridgeAgent { tokens, _amounts, _deposits, - _data, + __data, _manageGasOut(_toChain) ); @@ -583,6 +637,20 @@ contract RootBridgeAgent is IRootBridgeAgent { return true; } + function _retrieveSettlement(uint32 _settlementNonce) internal { + //Get settlement storage reference + Settlement storage settlementReference = getSettlement[_settlementNonce]; + + //Save toChain in memory + uint24 toChain = settlementReference.toChain; + + //Encode Data for cross-chain call. + bytes memory packedData = abi.encodePacked(bytes1(0x03), _settlementNonce, _manageGasOut(toChain), uint128(0)); + + //Retrieve Deposit + _performCall(settlementReference.callData, toChain); + } + /** * @notice Function to retry a user's Settlement balance. * @param _settlementNonce Identifier for token settlement. @@ -908,7 +976,7 @@ contract RootBridgeAgent is IRootBridgeAgent { userFeeInfo = _userFeeInfo; //Read Bridge Agent Action Flag attached from cross-chain message header. - bytes1 flag = data[0]; + bytes1 flag = data[0] & 0x7F; //DEPOSIT FLAG: 0 (System request / response) if (flag == 0x00) { @@ -916,24 +984,22 @@ contract RootBridgeAgent is IRootBridgeAgent { uint32 nonce = uint32(bytes4(data[PARAMS_START:PARAMS_TKN_START])); //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSystemRequest( - localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - //Interaction failure trigger fallback - (success, result) = (false, reason); - } - - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; + // Flag 0 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSystemRequest(localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeSystemRequest.selector, localRouterAddress, data, fromChainId + ) + ); //DEPOSIT FLAG: 1 (Call without Deposit) } else if (flag == 0x01) { @@ -941,24 +1007,22 @@ contract RootBridgeAgent is IRootBridgeAgent { uint32 nonce = uint32(bytes4(data[PARAMS_START:PARAMS_TKN_START])); //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoDeposit( - localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - //No new asset deposit no need to trigger fallback - (success, result) = (true, reason); - } - - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; + // Flag 1 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeNoDeposit(localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeNoDeposit.selector, localRouterAddress, data, fromChainId + ) + ); //DEPOSIT FLAG: 2 (Call with Deposit) } else if (flag == 0x02) { @@ -966,23 +1030,22 @@ contract RootBridgeAgent is IRootBridgeAgent { uint32 nonce = uint32(bytes4(data[PARAMS_START:PARAMS_TKN_START])); //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDeposit( - localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; + // Flag 2 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDeposit(localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeWithDeposit.selector, localRouterAddress, data, fromChainId + ) + ); //DEPOSIT FLAG: 3 (Call with multiple asset Deposit) } else if (flag == 0x03) { @@ -990,31 +1053,27 @@ contract RootBridgeAgent is IRootBridgeAgent { uint32 nonce = uint32(bytes4(data[2:6])); //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDepositMultiple( - localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; + // Flag 3 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeWithDepositMultiple(localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeWithDepositMultiple.selector, localRouterAddress, data, fromChainId + ) + ); //DEPOSIT FLAG: 4 (Call without Deposit + msg.sender) } else if (flag == 0x04) { - //Get deposit nonce associated with request being processed - uint32 nonce = uint32(bytes4(data[PARAMS_START_SIGNED:25])); - //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][uint32(bytes4(data[PARAMS_START_SIGNED:25]))] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); @@ -1029,28 +1088,27 @@ contract RootBridgeAgent is IRootBridgeAgent { IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedNoDeposit( - address(userAccount), localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - //No new asset deposit no need to trigger fallback - (success, result) = (true, reason); - } + //Flag 4 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedNoDeposit(address(userAccount), localRouterAddress, data, fromChainId + (success, result) = _execute( + data[0] & 0x80 == 0x80, + uint32(bytes4(data[PARAMS_START_SIGNED:25])), + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeSignedNoDeposit.selector, + address(userAccount), + localRouterAddress, + data, + fromChainId + ) + ); //Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; - //DEPOSIT FLAG: 5 (Call with Deposit + msg.sender) } else if (flag == 0x05) { - //Get deposit nonce associated with request being processed - uint32 nonce = uint32(bytes4(data[PARAMS_START_SIGNED:25])); - //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][uint32(bytes4(data[PARAMS_START_SIGNED:25]))] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); @@ -1065,27 +1123,27 @@ contract RootBridgeAgent is IRootBridgeAgent { IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDeposit( - address(userAccount), localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } + //Flag 5 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDeposit(address(userAccount), localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + uint32(bytes4(data[PARAMS_START_SIGNED:25])), + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeSignedWithDeposit.selector, + address(userAccount), + localRouterAddress, + data, + fromChainId + ) + ); //Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; - //DEPOSIT FLAG: 6 (Call with multiple asset Deposit + msg.sender) } else if (flag == 0x06) { - //Get nonce - uint32 nonce = uint32(bytes4(data[PARAMS_START_SIGNED:25])); - //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][uint32(bytes4(data[PARAMS_START_SIGNED:25]))] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); @@ -1100,59 +1158,66 @@ contract RootBridgeAgent is IRootBridgeAgent { IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDepositMultiple( - address(userAccount), localRouterAddress, data, fromChainId - ) returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } + //Flag 6 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeSignedWithDepositMultiple(address(userAccount), localRouterAddress, data, fromChainId) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + uint32(bytes4(data[PARAMS_START_SIGNED:25])), + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeSignedWithDepositMultiple.selector, + address(userAccount), + localRouterAddress, + data, + fromChainId + ) + ); //Toggle Router Virtual Account use for tx execution IPort(localPortAddress).toggleVirtualAccountApproved(userAccount, localRouterAddress); - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; - /// DEPOSIT FLAG: 7 (retrySettlement) } else if (flag == 0x07) { //Get nonce uint32 nonce = uint32(bytes4(data[1:5])); //Check if tx has already been executed - if (executionHistory[fromChainId][nonce]) { + if (executionState[fromChainId][nonce] != 0) { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure return (true, "already executed tx"); } //Try to execute remote request - try RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeRetrySettlement(uint32(bytes4(data[5:9]))) - returns (bool, bytes memory res) { - (success, result) = (true, res); - } catch (bytes memory reason) { - result = reason; - } - - //Update tx state as executed - executionHistory[fromChainId][nonce] = true; + //Flag 7 - RootBridgeAgentExecutor(bridgeAgentExecutorAddress).executeRetrySettlement(uint32(bytes4(data[5:9]))) + (success, result) = _execute( + data[0] & 0x80 == 0x80, + nonce, + fromChainId, + abi.encodeWithSelector( + RootBridgeAgentExecutor.executeRetrySettlement.selector, + uint32(bytes4(data[5:9])), + address(bytes20(data[9:29])) + ) + ); /// DEPOSIT FLAG: 8 (retrieveDeposit) } else if (flag == 0x08) { //Get nonce uint32 nonce = uint32(bytes4(data[1:5])); - //Check if tx has already been executed - if (!executionHistory[fromChainId][uint32(bytes4(data[1:5]))]) { - //Toggle Nonce as executed - executionHistory[fromChainId][nonce] = true; - - //Retry failed fallback + //Check if deposit is in retrieve mode + if (executionState[fromChainId][nonce] == 2) { + //Trigger fallback / Retry failed fallback + (success, result) = (false, ""); + } else if (executionState[fromChainId][nonce] == 1) { + //Set deposit to retrieve mode + executionState[fromChainId][nonce] = 2; + //Trigger fallback / Retry failed fallback (success, result) = (false, ""); } else { _forceRevert(); //Return true to avoid triggering anyFallback in case of `_forceRevert()` failure - return (true, "already executed tx"); + return (true, "not retrievable"); } //Unrecognized Function Selector @@ -1173,6 +1238,33 @@ contract RootBridgeAgent is IRootBridgeAgent { } } + function _execute(bool _hasFallbackToggled, uint256 _depositNonce, uint256 _fromChainId, bytes memory _data) + private + returns (bool success, bytes memory reason) + { + //Set tx state as executed to prevent reentrancy + executionState[_fromChainId][_depositNonce] = 1; + + //Try to execute remote request + (success, reason) = bridgeAgentExecutorAddress.call(_data); + + if (success) { + //Update tx state as executed + executionState[_fromChainId][_depositNonce] = 1; + } else { + //Read fallback bit and perform fallback if necessary. If not, allow for retrying deposit. + if (_hasFallbackToggled) { + //Update tx state as retrieve only + executionState[_fromChainId][_depositNonce] = 2; + } else { + //Ensure tx is set as unexecuted + executionState[_fromChainId][_depositNonce] = 0; + //Interaction failure but allow for retrying deposit + success = true; + } + } + } + /// @inheritdoc IApp function anyFallback(bytes calldata data) external @@ -1187,8 +1279,8 @@ contract RootBridgeAgent is IRootBridgeAgent { (, uint256 _fromChainId) = _getContext(); uint24 fromChainId = _fromChainId.toUint24(); - //Save Flag - bytes1 flag = data[0]; + //Read Bridge Agent Action Flag attached from cross-chain message header. + bytes1 flag = data[0] & 0x7F; //Deposit nonce uint32 _settlementNonce; @@ -1210,6 +1302,7 @@ contract RootBridgeAgent is IRootBridgeAgent { } emit LogCalloutFail(flag, data, fromChainId); + //Pay Fallback Gas _payFallbackGas(_settlementNonce, _initialGas); return (true, ""); diff --git a/src/ulysses-omnichain/RootBridgeAgentExecutor.sol b/src/ulysses-omnichain/RootBridgeAgentExecutor.sol index f49aac0..b911808 100644 --- a/src/ulysses-omnichain/RootBridgeAgentExecutor.sol +++ b/src/ulysses-omnichain/RootBridgeAgentExecutor.sol @@ -305,13 +305,22 @@ contract RootBridgeAgentExecutor is Ownable { * @return result The result of the request * @dev DEPOSIT FLAG: 7 (Retry Settlement) */ - function executeRetrySettlement(uint32 _settlementNonce) + function executeRetrySettlement(uint32 _settlementNonce, address _user) external onlyOwner returns (bool success, bytes memory result) { + //Get settlement owner + address settlementOwner = RootBridgeAgent(payable(msg.sender)).getSettlementEntry(_settlementNonce).owner; + + //Check settlement owner + if (_user != settlementOwner) { + revert NotSettlementOwner(); + } + //Execute remote request RootBridgeAgent(payable(msg.sender)).retrySettlement(_settlementNonce, 0); + //Trigger retry success (no guarantees about settlement success at this point) (success, result) = (true, ""); } @@ -425,4 +434,10 @@ contract RootBridgeAgentExecutor is Ownable { RootBridgeAgent(payable(msg.sender)).bridgeInMultiple(_recipient, dParams, _fromChain); } + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + error NotSettlementOwner(); } diff --git a/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol b/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol index 574d075..4e75616 100644 --- a/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol +++ b/src/ulysses-omnichain/interfaces/IBranchBridgeAgent.sol @@ -172,25 +172,31 @@ interface IBranchBridgeAgent is IApp { * @param params enconded parameters to execute on the root chain router. * @param dParams additional token deposit parameters. * @param remoteExecutionGas gas allocated for remote branch execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 2 (Call with single deposit) * */ - function callOutAndBridge(bytes calldata params, DepositInput memory dParams, uint128 remoteExecutionGas) - external - payable; + function callOutAndBridge( + bytes calldata params, + DepositInput memory dParams, + uint128 remoteExecutionGas, + bool hasFallbackToggled + ) external payable; /** * @notice Function to perform a call to the Root Omnichain Router while depositing two or more assets. * @param params enconded parameters to execute on the root chain router. * @param dParams additional token deposit parameters. * @param remoteExecutionGas gas allocated for remote branch execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 3 (Call with multiple deposit) * */ function callOutAndBridgeMultiple( bytes calldata params, DepositMultipleInput memory dParams, - uint128 remoteExecutionGas + uint128 remoteExecutionGas, + bool hasFallbackToggled ) external payable; /** @@ -207,25 +213,31 @@ interface IBranchBridgeAgent is IApp { * @param params enconded parameters to execute on the root chain router. * @param dParams additional token deposit parameters. * @param remoteExecutionGas gas allocated for remote branch execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 5 (Call with single deposit and verified sender) * */ - function callOutSignedAndBridge(bytes calldata params, DepositInput memory dParams, uint128 remoteExecutionGas) - external - payable; + function callOutSignedAndBridge( + bytes calldata params, + DepositInput memory dParams, + uint128 remoteExecutionGas, + bool hasFallbackToggled + ) external payable; /** * @notice Function to perform a call to the Root Omnichain Router while depositing two or more assets with msg.sender. * @param params enconded parameters to execute on the root chain router. * @param dParams additional token deposit parameters. * @param remoteExecutionGas gas allocated for remote branch execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 6 (Call with multiple deposit and verified sender) * */ function callOutSignedAndBridgeMultiple( bytes calldata params, DepositMultipleInput memory dParams, - uint128 remoteExecutionGas + uint128 remoteExecutionGas, + bool hasFallbackToggled ) external payable; /** @@ -235,13 +247,15 @@ interface IBranchBridgeAgent is IApp { * @param _params parameters to execute on the root chain router. * @param _remoteExecutionGas gas allocated for remote branch execution. * @param _toChain Destination chain for interaction. + * @param _hasFallbackToggled flag to indicate if the fallback function was toggled. */ function retryDeposit( bool _isSigned, uint32 _depositNonce, bytes calldata _params, uint128 _remoteExecutionGas, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable; /** @@ -299,6 +313,7 @@ interface IBranchBridgeAgent is IApp { * @param depositor address of user depositing assets. * @param gasToBridgeOut gas allocated for the cross-chain call. * @param remoteExecutionGas gas allocated for omnichain execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 0 (System Call / Response) * @dev 0x00 flag allows for identifying system emitted request/responses. * @@ -307,7 +322,8 @@ interface IBranchBridgeAgent is IApp { address depositor, bytes memory params, uint128 gasToBridgeOut, - uint128 remoteExecutionGas + uint128 remoteExecutionGas, + bool hasFallbackToggled ) external payable; /** @@ -317,12 +333,17 @@ interface IBranchBridgeAgent is IApp { * @param depositor address of user depositing assets. * @param gasToBridgeOut gas allocated for the cross-chain call. * @param remoteExecutionGas gas allocated for omnichain execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 1 (Call without Deposit) * */ - function performCallOut(address depositor, bytes memory params, uint128 gasToBridgeOut, uint128 remoteExecutionGas) - external - payable; + function performCallOut( + address depositor, + bytes memory params, + uint128 gasToBridgeOut, + uint128 remoteExecutionGas, + bool hasFallbackToggled + ) external payable; /** * @notice Function to perform a call to the Root Omnichain Router while depositing a single asset. @@ -331,6 +352,7 @@ interface IBranchBridgeAgent is IApp { * @param dParams additional token deposit parameters. * @param gasToBridgeOut gas allocated for the cross-chain call. * @param remoteExecutionGas gas allocated for omnichain execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 2 (Call with single asset Deposit) * */ @@ -339,7 +361,8 @@ interface IBranchBridgeAgent is IApp { bytes calldata params, DepositInput memory dParams, uint128 gasToBridgeOut, - uint128 remoteExecutionGas + uint128 remoteExecutionGas, + bool hasFallbackToggled ) external payable; /** @@ -349,6 +372,7 @@ interface IBranchBridgeAgent is IApp { * @param dParams additional token deposit parameters. * @param gasToBridgeOut gas allocated for the cross-chain call. * @param remoteExecutionGas gas allocated for omnichain execution. + * @param hasFallbackToggled flag to indicate if the fallback function was toggled. * @dev DEPOSIT ID: 3 (Call with multiple deposit) * */ @@ -357,7 +381,8 @@ interface IBranchBridgeAgent is IApp { bytes calldata params, DepositMultipleInput memory dParams, uint128 gasToBridgeOut, - uint128 remoteExecutionGas + uint128 remoteExecutionGas, + bool hasFallbackToggled ) external payable; /*/////////////////////////////////////////////////////////////// diff --git a/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol b/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol index 34e71b6..fd06ce5 100644 --- a/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol +++ b/src/ulysses-omnichain/interfaces/IRootBridgeAgent.sol @@ -208,9 +208,12 @@ interface IRootBridgeAgent is IApp { * @param _recipient address to receive any outstanding gas on the destination chain. * @param _calldata Calldata for function call. * @param _toChain Chain to bridge to. + * @param _hasFallbackToggled Flag to toggle fallback function. * @dev Internal function performs call to AnycallProxy Contract for cross-chain messaging. */ - function callOut(address _recipient, bytes memory _calldata, uint24 _toChain) external payable; + function callOut(address _recipient, bytes memory _calldata, uint24 _toChain, bool _hasFallbackToggled) + external + payable; /** * @notice External function to move assets from root chain to branch omnichain envirsonment. @@ -221,6 +224,7 @@ interface IRootBridgeAgent is IApp { * @param _amount amount of ´token´. * @param _deposit amount of native / underlying token. * @param _toChain chain to bridge to. + * @param _hasFallbackToggled Flag to toggle fallback function. * */ function callOutAndBridge( @@ -230,7 +234,8 @@ interface IRootBridgeAgent is IApp { address _globalAddress, uint256 _amount, uint256 _deposit, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable; /** @@ -242,7 +247,7 @@ interface IRootBridgeAgent is IApp { * @param _amounts amounts of token. * @param _deposits amounts of underlying / token. * @param _toChain chain to bridge to. - * + * @param _hasFallbackToggled Flag to toggle fallback function. * */ function callOutAndBridgeMultiple( @@ -252,7 +257,8 @@ interface IRootBridgeAgent is IApp { address[] memory _globalAddresses, uint256[] memory _amounts, uint256[] memory _deposits, - uint24 _toChain + uint24 _toChain, + bool _hasFallbackToggled ) external payable; /*/////////////////////////////////////////////////////////////// @@ -401,6 +407,7 @@ interface IRootBridgeAgent is IApp { error UnrecognizedAddressInDestination(); error SettlementRedeemUnavailable(); + error SettlementRetrieveUnavailable(); error NotSettlementOwner(); error InsufficientBalanceForSettlement(); diff --git a/test/ulysses-omnichain/ArbitrumBranchTest.t.sol b/test/ulysses-omnichain/ArbitrumBranchTest.t.sol index aac8339..7c44227 100644 --- a/test/ulysses-omnichain/ArbitrumBranchTest.t.sol +++ b/test/ulysses-omnichain/ArbitrumBranchTest.t.sol @@ -709,7 +709,7 @@ contract ArbitrumBranchTest is DSTestPlus { ); //Call Deposit function - arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); // Test If Deposit was successful testCreateDepositSingle( @@ -813,7 +813,7 @@ contract ArbitrumBranchTest is DSTestPlus { hevm.startPrank(_user); arbitrumNativeToken.approve(address(localPortAddress), _deposit); ERC20hTokenRoot(newArbitrumAssetGlobalAddress).approve(address(rootPort), _amount - _deposit); - arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); hevm.stopPrank(); // Test If Deposit was successful diff --git a/test/ulysses-omnichain/BranchBridgeAgentTest.t.sol b/test/ulysses-omnichain/BranchBridgeAgentTest.t.sol index 506b06d..14ac8ab 100644 --- a/test/ulysses-omnichain/BranchBridgeAgentTest.t.sol +++ b/test/ulysses-omnichain/BranchBridgeAgentTest.t.sol @@ -570,7 +570,7 @@ contract BranchBridgeAgentTest is Test { vm.deal(address(this), 1 ether); //Call redeemDeposit - bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24()); + bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24(), true); require(bAgent.getDepositEntry(1).depositedGas == 0.5 ether, "Gas should be updated"); } @@ -629,7 +629,7 @@ contract BranchBridgeAgentTest is Test { vm.expectRevert(abi.encodeWithSignature("NotDepositOwner()")); //Call redeemDeposit - bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24()); + bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24(), true); } function testRetryDepositFailCanAlwaysRetry() public { @@ -686,7 +686,7 @@ contract BranchBridgeAgentTest is Test { vm.deal(address(this), 1 ether); //Call redeemDeposit - bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24()); + bAgent.retryDeposit{value: 0.5 ether}(true, 1, "", 0, localChainId.toUint24(), true); } function testFuzzExecuteWithSettlement(address, uint256 _amount, uint256 _deposit, uint24 _toChain) public { diff --git a/test/ulysses-omnichain/MulticallRootRouterTest.t.sol b/test/ulysses-omnichain/MulticallRootRouterTest.t.sol index 94a4348..2f9b255 100644 --- a/test/ulysses-omnichain/MulticallRootRouterTest.t.sol +++ b/test/ulysses-omnichain/MulticallRootRouterTest.t.sol @@ -35,6 +35,7 @@ import {Deposit, DepositStatus, DepositMultipleInput, DepositInput} from "@omni/ import {WETH9 as WETH} from "./mocks/WETH9.sol"; import {Multicall2} from "./mocks/Multicall2.sol"; + contract MulticallRootRouterTest is DSTestPlus { uint32 nonce; @@ -623,7 +624,7 @@ contract MulticallRootRouterTest is DSTestPlus { avaxChainId ); - require((multicallBridgeAgent).executionHistory(avaxChainId, currentNonce), "Nonce should be executed"); + require((multicallBridgeAgent).executionState(avaxChainId, currentNonce) == 1, "Nonce should be executed"); } function testMulticallSignedNoOutputDepositSingle() public { @@ -919,7 +920,7 @@ contract MulticallRootRouterTest is DSTestPlus { Multicall2.Call[] memory calls = new Multicall2.Call[](1); - //Prepare call to transfer 100 hAVAX form virtual account to Mock App + //Prepare call to transfer 100 hAVAX form virtual account to Mock App calls[0] = Multicall2.Call({target: 0x0000000000000000000000000000000000000000, callData: ""}); //Output Params diff --git a/test/ulysses-omnichain/RootTest.t.sol b/test/ulysses-omnichain/RootTest.t.sol index ea88453..0e70040 100644 --- a/test/ulysses-omnichain/RootTest.t.sol +++ b/test/ulysses-omnichain/RootTest.t.sol @@ -1059,7 +1059,7 @@ contract RootTest is DSTestPlus { }); //Call Deposit function - arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); // Test If Deposit was successful testCreateDepositSingle( @@ -1159,7 +1159,7 @@ contract RootTest is DSTestPlus { hevm.startPrank(_user); arbitrumMockToken.approve(address(arbitrumPort), _deposit); ERC20hTokenRoot(newArbitrumAssetGlobalAddress).approve(address(rootPort), _amount - _deposit); - arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + arbitrumMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); hevm.stopPrank(); // Test If Deposit was successful @@ -1269,7 +1269,7 @@ contract RootTest is DSTestPlus { //Call Deposit function avaxMockAssetToken.approve(address(avaxPort), 100 ether); ERC20hTokenRoot(avaxMockAssethToken).approve(address(avaxPort), 50 ether); - avaxMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + avaxMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); //Set MockAnycall AnyFallback mode OFF MockAnycall(localAnyCallAddress).toggleFallback(0); @@ -1363,7 +1363,7 @@ contract RootTest is DSTestPlus { //Call Deposit function avaxMockAssetToken.approve(address(avaxPort), 100 ether); ERC20hTokenRoot(avaxMockAssethToken).approve(address(avaxPort), 50 ether); - avaxMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether); + avaxMulticallBridgeAgent.callOutSignedAndBridge{value: 1 ether}(packedData, depositInput, 0.5 ether, false); //Set MockAnycall AnyFallback mode OFF MockAnycall(localAnyCallAddress).toggleFallback(0);