diff --git a/contracts/core/.gas-snapshot b/contracts/core/.gas-snapshot index 3c90975bd..a267abf78 100644 --- a/contracts/core/.gas-snapshot +++ b/contracts/core/.gas-snapshot @@ -23,23 +23,23 @@ FeeOracleV2_Test:test_setManager() (gas: 45775) FeeOracleV2_Test:test_setProtocolFee() (gas: 32226) FeeOracleV2_Test:test_setToNativeRate() (gas: 43640) Inbox_accept_Test:test_accept_one_request() (gas: 388610) -Inbox_accept_Test:test_accept_reverts() (gas: 1026513) +Inbox_accept_Test:test_accept_reverts() (gas: 1027273) Inbox_accept_Test:test_accept_skip_first() (gas: 694910) Inbox_accept_Test:test_accept_two_requests() (gas: 718480) -Inbox_cancel_Test:test_cancel_multiToken() (gas: 542138) -Inbox_cancel_Test:test_cancel_nativeMultiToken() (gas: 631074) -Inbox_cancel_Test:test_cancel_oldest_request() (gas: 686650) -Inbox_cancel_Test:test_cancel_one_request() (gas: 389923) -Inbox_cancel_Test:test_cancel_rejected_nativeMultiToken_request() (gas: 639600) -Inbox_cancel_Test:test_cancel_rejected_nativeToken_request() (gas: 397962) -Inbox_cancel_Test:test_cancel_reverts() (gas: 1052678) -Inbox_cancel_Test:test_cancel_singleToken() (gas: 421456) -Inbox_cancel_Test:test_cancel_two_requests() (gas: 697764) -Inbox_reject_Test:test_reject_nativeMultiToken() (gas: 594705) -Inbox_reject_Test:test_reject_oldest_request() (gas: 658739) -Inbox_reject_Test:test_reject_one_request() (gas: 361505) -Inbox_reject_Test:test_reject_reverts() (gas: 731046) -Inbox_reject_Test:test_reject_two_requests() (gas: 662097) +Inbox_cancel_Test:test_cancel_multiToken() (gas: 542454) +Inbox_cancel_Test:test_cancel_nativeMultiToken() (gas: 631438) +Inbox_cancel_Test:test_cancel_oldest_request() (gas: 686856) +Inbox_cancel_Test:test_cancel_one_request() (gas: 390129) +Inbox_cancel_Test:test_cancel_rejected_nativeMultiToken_request() (gas: 640518) +Inbox_cancel_Test:test_cancel_rejected_nativeToken_request() (gas: 398722) +Inbox_cancel_Test:test_cancel_reverts() (gas: 1053644) +Inbox_cancel_Test:test_cancel_singleToken() (gas: 421693) +Inbox_cancel_Test:test_cancel_two_requests() (gas: 698176) +Inbox_reject_Test:test_reject_nativeMultiToken() (gas: 595259) +Inbox_reject_Test:test_reject_oldest_request() (gas: 659293) +Inbox_reject_Test:test_reject_one_request() (gas: 362059) +Inbox_reject_Test:test_reject_reverts() (gas: 731850) +Inbox_reject_Test:test_reject_two_requests() (gas: 663205) Inbox_request_Test:test_request_multiToken() (gas: 545679) Inbox_request_Test:test_request_nativeMultiToken() (gas: 603853) Inbox_request_Test:test_request_reverts() (gas: 890398) @@ -140,11 +140,11 @@ OmniPortal_xsubmit_gas_Test:test_xsubmit_guzzle5_10validators_succeeds() (gas: 2 OmniPortal_xsubmit_gas_Test:test_xsubmit_guzzle5_25validators_succeeds() (gas: 3510011) OmniPortal_xsubmit_gas_Test:test_xsubmit_guzzle5_succeeds() (gas: 948663) Omni_Test:test_constructor() (gas: 1008119) -Outbox_fulfill_Test:test_fulfillFee() (gas: 44052) -Outbox_fulfill_Test:test_fulfill_succeeds() (gas: 274736) -Outbox_fulfill_Test:test_markFulfilled_nativeMultiToken() (gas: 1119775) -Outbox_fulfill_Test:test_markFulfilled_singleNative() (gas: 906052) -Outbox_fulfill_Test:test_markFulfilled_singleToken() (gas: 1028483) +Outbox_fulfill_Test:test_fulfillFee() (gas: 43845) +Outbox_fulfill_Test:test_fulfill_succeeds() (gas: 276427) +Outbox_fulfill_Test:test_markFulfilled_nativeMultiToken() (gas: 1125071) +Outbox_fulfill_Test:test_markFulfilled_singleNative() (gas: 911348) +Outbox_fulfill_Test:test_markFulfilled_singleToken() (gas: 1033779) PortalRegistry_Test:test_register() (gas: 1092038) PortalRegistry_Test:test_stub() (gas: 143) Preinstalls_Test:test_createX_deployCreate2_succeeds() (gas: 132705) diff --git a/contracts/core/src/solve/Inbox.sol b/contracts/core/src/solve/Inbox.sol index 9e054d18c..426ac71ee 100644 --- a/contracts/core/src/solve/Inbox.sol +++ b/contracts/core/src/solve/Inbox.sol @@ -5,6 +5,8 @@ import { OwnableRoles } from "solady/src/auth/OwnableRoles.sol"; import { ReentrancyGuard } from "solady/src/utils/ReentrancyGuard.sol"; import { Initializable } from "solady/src/utils/Initializable.sol"; import { XAppBase } from "../pkg/XAppBase.sol"; +import { IInbox } from "./interfaces/IInbox.sol"; + import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol"; import { IConversionRateOracle } from "../interfaces/IConversionRateOracle.sol"; import { Solve } from "./Solve.sol"; @@ -13,7 +15,7 @@ import { Solve } from "./Solve.sol"; * @title Inbox * @notice Entrypoint and alt-mempoool for user solve requests. */ -contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { +contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase, IInbox { using SafeTransferLib for address; error NoDeposits(); @@ -41,10 +43,11 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { /** * @notice Emitted when a request is rejected. - * @param id ID of the request. - * @param by Address of the solver who rejected the request. + * @param id ID of the request. + * @param by Address of the solver who rejected the request. + * @param reason Reason for rejecting the request. */ - event Rejected(bytes32 indexed id, address indexed by); + event Rejected(bytes32 indexed id, address indexed by, Solve.RejectReason indexed reason); /** * @notice Emitted when a request is cancelled. @@ -54,11 +57,17 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { /** * @notice Emitted when a request is fulfilled. - * @param guid ID of the request. + * @param id ID of the request. * @param callHash Hash of the call executed on another chain. * @param creditedTo Address of the recipient credited the funds by the solver. */ - event Fulfilled(bytes32 indexed guid, bytes32 indexed callHash, address indexed creditedTo); + event Fulfilled(bytes32 indexed id, bytes32 indexed callHash, address indexed creditedTo); + + /** + * @notice Emitted when a request is claimed. + * @param id ID of the request. + */ + event Claimed(bytes32 indexed id); /** * @notice Role for solvers. @@ -105,7 +114,7 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { * @notice Suggest the amount of native currency to send with a request. * @param call Details of the call to be executed on another chain. * @param gasLimit Maximum gas limit for the call. - * @param gasPrice Gas price in wei. + * @param gasPrice Destination chain gas price in wei. * @param fulfillFee Fee for the fulfill call, retrieved from the destination outbox. */ function suggestNativePayment(Solve.Call calldata call, uint64 gasLimit, uint64 gasPrice, uint256 fulfillFee) @@ -168,14 +177,14 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { * @dev Only a whitelisted solver can reject. * @param id ID of the request. */ - function reject(bytes32 id) external onlyRoles(SOLVER) nonReentrant { + function reject(bytes32 id, Solve.RejectReason reason) external onlyRoles(SOLVER) nonReentrant { Solve.Request storage req = _requests[id]; if (req.status != Solve.Status.Pending) revert RequestStateInvalid(); req.updatedAt = uint40(block.timestamp); req.status = Solve.Status.Rejected; - emit Rejected(id, msg.sender); + emit Rejected(id, msg.sender, reason); } /** @@ -188,7 +197,10 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { if (req.status != Solve.Status.Pending && req.status != Solve.Status.Rejected) revert RequestStateInvalid(); if (req.from != msg.sender) revert Unauthorized(); - _cancelRequest(req); + req.updatedAt = uint40(block.timestamp); + req.status = Solve.Status.Reverted; + + _transferDeposits(req.from, req.deposits); emit Reverted(id); } @@ -197,34 +209,54 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { * @notice Fulfill a request. * @dev Only callable by the outbox. */ - function markFulfilled(bytes32 guid, bytes32 callHash, address creditTo) external xrecv nonReentrant { + function markFulfilled(bytes32 id, bytes32 callHash, address creditTo) external xrecv nonReentrant { // Validate request state and fulfillment context - Solve.Request storage req = _requests[guid]; + Solve.Request storage req = _requests[id]; if (req.status != Solve.Status.Accepted) revert RequestStateInvalid(); if (req.call.destChainId != xmsg.sourceChainId) revert IncorrectChain(); if (xmsg.sender != _outbox) revert Unauthorized(); // No need to check if msg.sender is OmniPortal as we use xrecv for source validation // Validate call hash, ensuring the correct action is being fulfilled - bytes32 _callHash = keccak256(abi.encode(guid, req.call)); + bytes32 _callHash = keccak256(abi.encode(id, block.chainid, req.call)); if (_callHash != callHash) revert InvalidCall(); // Update request state req.updatedAt = uint40(block.timestamp); req.status = Solve.Status.Fulfilled; + req.acceptedBy = creditTo; + + emit Fulfilled(id, callHash, creditTo); + } + + /** + * @notice Claim a fulfilled request. + * @param id ID of the request. + */ + function claim(bytes32 id) external nonReentrant { + Solve.Request storage req = _requests[id]; + if (req.status != Solve.Status.Fulfilled) revert RequestStateInvalid(); - // Transfer deposits to solver - Solve.Deposit[] memory deposits = req.deposits; + req.updatedAt = uint40(block.timestamp); + req.status = Solve.Status.Claimed; + + _transferDeposits(req.acceptedBy, req.deposits); + + emit Claimed(id); + } + + /** + * @dev Transfer deposits to recipient. Used regardless of refund or claim. + */ + function _transferDeposits(address recipient, Solve.Deposit[] memory deposits) internal { for (uint256 i; i < deposits.length; ++i) { if (deposits[i].isNative) { - (bool success,) = payable(creditTo).call{ value: deposits[i].amount }(""); + (bool success,) = payable(recipient).call{ value: deposits[i].amount }(""); if (!success) revert TransferFailed(); } else { - deposits[i].token.safeTransfer(creditTo, deposits[i].amount); + deposits[i].token.safeTransfer(recipient, deposits[i].amount); } } - - emit Fulfilled(guid, callHash, creditTo); } /** @@ -261,24 +293,6 @@ contract Inbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { } } - function _cancelRequest(Solve.Request storage req) internal { - // Update state - req.updatedAt = uint40(block.timestamp); - req.status = Solve.Status.Reverted; - - // Refund deposits - uint256 length = req.deposits.length; - for (uint256 i = 0; i < length; i++) { - Solve.Deposit memory deposit = req.deposits[i]; - if (deposit.isNative) { - (bool success,) = payable(msg.sender).call{ value: deposit.amount }(""); - if (!success) revert TransferFailed(); - } else { - deposit.token.safeTransfer(msg.sender, deposit.amount); - } - } - } - /** * @dev Increment and return _lastId. */ diff --git a/contracts/core/src/solve/Outbox.sol b/contracts/core/src/solve/Outbox.sol index 2e4d1b6d8..1b178d3b7 100644 --- a/contracts/core/src/solve/Outbox.sol +++ b/contracts/core/src/solve/Outbox.sol @@ -5,14 +5,12 @@ import { OwnableRoles } from "solady/src/auth/OwnableRoles.sol"; import { ReentrancyGuard } from "solady/src/utils/ReentrancyGuard.sol"; import { Initializable } from "solady/src/utils/Initializable.sol"; import { XAppBase } from "../pkg/XAppBase.sol"; + import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol"; import { ConfLevel } from "../libraries/ConfLevel.sol"; import { Solve } from "./Solve.sol"; -interface ISymbioticVault { - function collateral() external view returns (address); - function deposit(address onBehalfOf, uint256 amount) external; -} +import { IInbox } from "./interfaces/IInbox.sol"; /** * @title Outbox @@ -22,6 +20,7 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { using SafeTransferLib for address; error CallFailed(); + error DuplicateCall(); error IncorrectChain(); error InsufficientFee(); error IncorrectBalance(); @@ -31,11 +30,11 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { /** * @notice Emitted when a request is fulfilled. - * @param guid ID of the request. + * @param reqId ID of the request. * @param callHash Hash of the call executed. * @param creditedTo Origin address credited the funds by the solver. */ - event Fulfilled(bytes32 indexed guid, bytes32 indexed callHash, address indexed creditedTo); + event Fulfilled(bytes32 indexed reqId, bytes32 indexed callHash, address indexed creditedTo); /** * @notice Role for solvers. @@ -43,6 +42,37 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { */ uint256 internal constant SOLVER = _ROLE_0; + /** + * @notice Gas limit for the callback. + */ + uint64 internal constant CALLBACK_GAS_LIMIT = 200_000; + + /** + * @notice Placeholder for request ID. + */ + bytes32 internal constant ID_PLACEHOLDER = bytes32(type(uint256).max); + + /** + * @notice Placeholder for call hash. + */ + bytes32 internal constant CALLHASH_PLACEHOLDER = bytes32(type(uint256).max); + + /** + * @notice Placeholder for solver address. + */ + address internal constant SOLVER_PLACEHOLDER = address(type(uint160).max); + + /** + * @notice Signature of the markFulfilled function. + */ + string internal constant MARK_FULFILLED_SIGNATURE = "markFulfilled(bytes32,bytes32,address)"; + + /** + * @notice Encoded call data for the markFulfilled function. + */ + bytes internal constant MARK_FULFILLED_CALLDATA = + abi.encodeWithSignature(MARK_FULFILLED_SIGNATURE, ID_PLACEHOLDER, CALLHASH_PLACEHOLDER, SOLVER_PLACEHOLDER); + /** * @notice Address of the inbox contract. */ @@ -53,6 +83,12 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { */ mapping(address target => mapping(bytes4 selector => bool)) public allowedCalls; + /** + * @notice Mapping of fulfilled calls. + * @dev callHash used to prevent duplicate fulfillment. + */ + mapping(bytes32 callHash => bool fulfilled) public fulfilledCalls; + /** * @notice Initialize the contract's owner and solver. * @dev Used instead of constructor as we want to use the transparent upgradeable proxy pattern. @@ -66,41 +102,54 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { _inbox = inbox_; } - function setAllowedCall(address target, bytes4 selector, bool allowed) external onlyOwner { - allowedCalls[target][selector] = allowed; - emit AllowedCallSet(target, selector, allowed); - } - /** * @notice Calculate the message passing fee for a fulfill call. * @param sourceChainId ID of the source chain. */ function fulfillFee(uint64 sourceChainId) public view returns (uint256) { - bytes memory data = abi.encodeWithSignature( - "fulfill(bytes32,bytes32,address)", - keccak256(abi.encode("GUID")), - keccak256(abi.encode("CALLHASH")), - address(uint160(uint256(keccak256(abi.encode("SOLVER"))))) - ); - return feeFor(sourceChainId, data, 200_000); + return feeFor(sourceChainId, MARK_FULFILLED_CALLDATA, CALLBACK_GAS_LIMIT); + } + + /** + * @notice Check if a call has been fulfilled. + * @param reqId ID of the request. + * @param sourceChainId ID of the source chain. + * @param call Details of the call executed. + */ + function didFulfill(bytes32 reqId, uint64 sourceChainId, Solve.Call calldata call) external view returns (bool) { + bytes32 callHash = keccak256(abi.encode(reqId, sourceChainId, call)); + return fulfilledCalls[callHash]; + } + + /** + * @notice Set an allowed call for a target contract. + * @param target Address of the target contract. + * @param selector 4-byte selector of the function to allow. + * @param allowed Whether the call is allowed. + */ + function setAllowedCall(address target, bytes4 selector, bool allowed) external onlyOwner { + allowedCalls[target][selector] = allowed; + emit AllowedCallSet(target, selector, allowed); } /** * @notice Fulfill a request. - * @param guid ID of the request. + * @param reqId ID of the request. * @param sourceChainId ID of the source chain. * @param creditTo Address to credit funds to on the origin chain. * @param call Details of the call to execute. * @param prereqs Pre-requisite token deposits required by the call. */ function fulfill( - bytes32 guid, + bytes32 reqId, uint64 sourceChainId, address creditTo, Solve.Call calldata call, Solve.TokenPrereq[] calldata prereqs ) external payable onlyRoles(SOLVER) nonReentrant { - // Verify the call is intended for this chain and is authorized + // Verify the call + bytes32 callHash = keccak256(abi.encode(reqId, sourceChainId, call)); + if (fulfilledCalls[callHash]) revert DuplicateCall(); if (call.destChainId != block.chainid) revert IncorrectChain(); if (!allowedCalls[call.target][bytes4(call.data)]) revert UnauthorizedCall(); @@ -121,14 +170,11 @@ contract Outbox is OwnableRoles, ReentrancyGuard, Initializable, XAppBase { if (prereqs[i].token.balanceOf(address(this)) != prereqBalances[i]) revert IncorrectBalance(); } - // Calculate the call hash and prepare fulfillment call - bytes32 callHash = keccak256(abi.encode(guid, call)); - bytes memory data = abi.encodeWithSignature("markFulfilled(bytes32,bytes32,address)", guid, callHash, creditTo); - // Send the fulfillment call to the inbox - uint256 fee = xcall(sourceChainId, ConfLevel.Finalized, _inbox, data, 200_000); + bytes memory data = abi.encodeCall(IInbox.markFulfilled, (reqId, callHash, creditTo)); + uint256 fee = xcall(sourceChainId, ConfLevel.Finalized, _inbox, data, CALLBACK_GAS_LIMIT); if (msg.value - call.value < fee) revert InsufficientFee(); - emit Fulfilled(guid, callHash, creditTo); + emit Fulfilled(reqId, callHash, creditTo); } } diff --git a/contracts/core/src/solve/Solve.sol b/contracts/core/src/solve/Solve.sol index 6b87b35c6..a69ee6a52 100644 --- a/contracts/core/src/solve/Solve.sol +++ b/contracts/core/src/solve/Solve.sol @@ -2,13 +2,27 @@ pragma solidity =0.8.24; library Solve { + /** + * @notice Status of a request. + */ enum Status { Invalid, Pending, Accepted, Rejected, Reverted, - Fulfilled + Fulfilled, + Claimed + } + + /** + * @notice Reason for rejecting a request. + */ + enum RejectReason { + None, + DestCallReverts, + InsufficientFee, + InsufficientInventory } /** diff --git a/contracts/core/src/solve/interfaces/IInbox.sol b/contracts/core/src/solve/interfaces/IInbox.sol new file mode 100644 index 000000000..c7acc07c8 --- /dev/null +++ b/contracts/core/src/solve/interfaces/IInbox.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity =0.8.24; + +interface IInbox { + function markFulfilled(bytes32 id, bytes32 callHash, address creditTo) external; +} diff --git a/contracts/core/test/solve/Inbox_accept.t.sol b/contracts/core/test/solve/Inbox_accept.t.sol index 40d722e8c..dd3f8907d 100644 --- a/contracts/core/test/solve/Inbox_accept.t.sol +++ b/contracts/core/test/solve/Inbox_accept.t.sol @@ -61,7 +61,7 @@ contract Inbox_accept_Test is Test { // cannot accept rejected request vm.startPrank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); vm.expectRevert(Inbox.RequestStateInvalid.selector); inbox.accept(id); vm.stopPrank(); diff --git a/contracts/core/test/solve/Inbox_cancel.t.sol b/contracts/core/test/solve/Inbox_cancel.t.sol index 165196783..633afe83a 100644 --- a/contracts/core/test/solve/Inbox_cancel.t.sol +++ b/contracts/core/test/solve/Inbox_cancel.t.sol @@ -55,7 +55,7 @@ contract Inbox_cancel_Test is Test { // cannot double cancel rejected request vm.prank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); vm.startPrank(user); inbox.cancel(id); vm.expectRevert(Inbox.RequestStateInvalid.selector); @@ -214,7 +214,7 @@ contract Inbox_cancel_Test is Test { // reject request vm.prank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); // cancel rejected request vm.prank(user); @@ -239,7 +239,7 @@ contract Inbox_cancel_Test is Test { // reject request vm.prank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); // cancel rejected request vm.prank(user); diff --git a/contracts/core/test/solve/Inbox_reject.t.sol b/contracts/core/test/solve/Inbox_reject.t.sol index a71535cdf..42cad4f19 100644 --- a/contracts/core/test/solve/Inbox_reject.t.sol +++ b/contracts/core/test/solve/Inbox_reject.t.sol @@ -34,11 +34,11 @@ contract Inbox_reject_Test is Test { // cannot reject non-existent request vm.prank(solver); vm.expectRevert(Inbox.RequestStateInvalid.selector); - inbox.reject(bytes32(0)); + inbox.reject(bytes32(0), Solve.RejectReason.None); // needs to have solver role vm.expectRevert(Ownable.Unauthorized.selector); - inbox.reject(bytes32(0)); + inbox.reject(bytes32(0), Solve.RejectReason.None); // create request to cancel before rejecting vm.deal(user, 1 ether); @@ -52,7 +52,7 @@ contract Inbox_reject_Test is Test { // cannot reject cancelled request vm.prank(solver); vm.expectRevert(Inbox.RequestStateInvalid.selector); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); // create request to accept before rejecting vm.deal(user, 1 ether); @@ -63,7 +63,7 @@ contract Inbox_reject_Test is Test { vm.startPrank(solver); inbox.accept(id); vm.expectRevert(Inbox.RequestStateInvalid.selector); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); vm.stopPrank(); } @@ -77,7 +77,7 @@ contract Inbox_reject_Test is Test { // reject request vm.prank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); assertEq(address(inbox).balance, 1 ether, "address(inbox).balance"); assertEq(address(user).balance, 0, "address(user).balance"); @@ -96,8 +96,8 @@ contract Inbox_reject_Test is Test { // reject both requests vm.startPrank(solver); - inbox.reject(id1); - inbox.reject(id2); + inbox.reject(id1, Solve.RejectReason.None); + inbox.reject(id2, Solve.RejectReason.None); vm.stopPrank(); assertEq(address(inbox).balance, 2 ether, "address(inbox).balance"); @@ -118,7 +118,7 @@ contract Inbox_reject_Test is Test { // reject oldest request vm.startPrank(solver); - inbox.reject(id1); + inbox.reject(id1, Solve.RejectReason.None); vm.stopPrank(); assertEq(address(inbox).balance, 2 ether, "address(inbox).balance"); @@ -141,7 +141,7 @@ contract Inbox_reject_Test is Test { // reject request vm.prank(solver); - inbox.reject(id); + inbox.reject(id, Solve.RejectReason.None); assertEq(address(inbox).balance, 1 ether, "address(inbox).balance"); assertEq(address(user).balance, 0, "address(user).balance"); diff --git a/contracts/core/test/solve/Outbox_fulfill.t.sol b/contracts/core/test/solve/Outbox_fulfill.t.sol index b12e40a6c..a5c04c602 100644 --- a/contracts/core/test/solve/Outbox_fulfill.t.sol +++ b/contracts/core/test/solve/Outbox_fulfill.t.sol @@ -70,7 +70,7 @@ contract Outbox_fulfill_Test is Base { uint256 fee = outbox.fulfillFee(thisChainId); // Ensure fulfill call is executed properly - bytes32 callHash = _getCallHash(bytes32(uint256(1)), call); + bytes32 callHash = _getCallHash(bytes32(uint256(1)), thisChainId, call); vm.expectCall(call.target, call.data); vm.expectEmit(address(outbox)); emit Outbox.Fulfilled(bytes32(uint256(1)), callHash, solver); @@ -191,19 +191,25 @@ contract Outbox_fulfill_Test is Base { } /// @dev Hash of the call to be included in the xmsg - function _getCallHash(bytes32 guid, Solve.Call memory call) internal pure returns (bytes32) { - return keccak256(abi.encode(guid, call)); + function _getCallHash(bytes32 id, uint64 sourceChainId, Solve.Call memory call) internal pure returns (bytes32) { + return keccak256(abi.encode(id, sourceChainId, call)); } /// @dev Create an xmsg to deliver the markFulfilled callback - function _getXMsg(bytes32 guid, uint64 offset, Solve.Call memory call) internal view returns (XTypes.Msg memory) { + function _getXMsg(bytes32 id, uint64 sourceChainId, uint64 offset, Solve.Call memory call) + internal + view + returns (XTypes.Msg memory) + { return XTypes.Msg({ destChainId: thisChainId, shardId: ConfLevel.Finalized, offset: offset, sender: address(outbox), to: address(inbox), - data: abi.encodeWithSignature("markFulfilled(bytes32,bytes32,address)", guid, _getCallHash(guid, call), solver), + data: abi.encodeWithSignature( + "markFulfilled(bytes32,bytes32,address)", id, _getCallHash(id, sourceChainId, call), solver + ), gasLimit: 200_000 }); } @@ -235,18 +241,17 @@ contract Outbox_fulfill_Test is Base { _mintAndApprove(token, depositAmount); Solve.TokenPrereq[] memory prereqs = new Solve.TokenPrereq[](1); prereqs[0] = Solve.TokenPrereq({ token: token, spender: call.target, amount: depositAmount }); - uint256 fee = outbox.fulfillFee(thisChainId); - outbox.fulfill{ value: fee }(id, thisChainId, solver, call, prereqs); + outbox.fulfill{ value: outbox.fulfillFee(thisChainId) }(id, thisChainId, solver, call, prereqs); vm.stopPrank(); // Build and perform XSubmission to deliver markFulfilled callback vm.chainId(thisChainId); XTypes.Msg[] memory xmsgs = new XTypes.Msg[](1); - xmsgs[0] = _getXMsg(id, uint64(1), call); + xmsgs[0] = _getXMsg(id, thisChainId, uint64(1), call); TestXTypes.Block memory xblock = _xblock(chainAId, ConfLevel.Finalized, uint64(1), xmsgs); XTypes.Submission memory xsub = makeXSub(1, xblock.blockHeader, xblock.msgs, msgFlagsForDest(xblock.msgs, thisChainId)); - bytes32 callHash = _getCallHash(bytes32(uint256(1)), call); + bytes32 callHash = _getCallHash(bytes32(uint256(1)), thisChainId, call); // Perform and validate XSubmission vm.prank(relayer); @@ -256,5 +261,9 @@ contract Outbox_fulfill_Test is Base { vm.expectEmit(true, true, true, false, address(portal)); emit IOmniPortal.XReceipt(chainAId, ConfLevel.Finalized, uint64(1), 0, relayer, true, bytes("")); portal.xsubmit(xsub); + + // Claim the request as the solver + vm.prank(solver); + inbox.claim(id); } }