Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/fillers/BaseFiller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ abstract contract BaseFiller is IPayloadCreator {

function _preDeliveryHook(address recipient, address token, uint256 outputAmount) internal virtual returns (uint256);

function _getOutputDescriptionHash(OutputDescription calldata output) internal virtual pure returns (bytes32 outputHash) {
return OutputEncodingLib.getOutputDescriptionHash(output);
}

/**
* @notice Verifies & Fills an order.
* If an order has already been filled given the output & fillDeadline, then this function
Expand All @@ -58,7 +62,7 @@ abstract contract BaseFiller is IPayloadCreator {
_IAmRemoteFiller(output.remoteFiller);

// Get hash of output.
bytes32 outputHash = OutputEncodingLib.getOutputDescriptionHash(output);
bytes32 outputHash = _getOutputDescriptionHash(output);

// Get the proof state of the fulfillment.
bytes32 existingSolver = _filledOutputs[orderId][outputHash].solver;
Expand Down
2 changes: 1 addition & 1 deletion src/fillers/coin/CoinFiller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract CoinFiller is BaseFiller {
*/
function _getAmount(
OutputDescription calldata output
) internal view returns (uint256 amount) {
) internal virtual view returns (uint256 amount) {
uint256 fulfillmentLength = output.fulfillmentContext.length;
if (fulfillmentLength == 0) return output.amount;
bytes1 orderType = bytes1(output.fulfillmentContext);
Expand Down
109 changes: 109 additions & 0 deletions src/fillers/coin/SignedCoinFiller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import { EIP712 } from "solady/utils/EIP712.sol";
import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";

import { CoinFillerWithFee } from "./CoinFillerWithFee.sol";
import { OutputDescription, OutputEncodingLib } from "src/libs/OutputEncodingLib.sol";
import { SignedOutputType } from "./types/SignedOutputType.sol";

/**
* @dev Solvers use Oracles to pay outputs. This allows us to record the payment.
* Tokens never touch this contract but goes directly from solver to user.
*/
contract SignedCoinFiller is CoinFillerWithFee, EIP712 {
error InvalidSigner();

constructor(
address owner
) payable CoinFillerWithFee(owner) { }

function _domainNameAndVersion() internal pure virtual override returns (string memory name, string memory version) {
name = "SignedCompactFiller";
version = "Signed1";
}

function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparator();
}

/**
* @notice Removed the first 32 bytes of fulfillmentContext, since that is expected to have been
* prepended by the auction server.
*/
function _getOutputDescriptionHash(
OutputDescription calldata outputDescription
) internal override pure returns (bytes32 outputHash) {
bytes calldata remoteCall = outputDescription.remoteCall;
bytes calldata fulfillmentContext = outputDescription.fulfillmentContext;
// Check that the length of remoteCall & fulfillmentContext does not exceed type(uint16).max
if (remoteCall.length > type(uint16).max) revert OutputEncodingLib.RemoteCallOutOfRange();
if (fulfillmentContext.length > type(uint16).max) revert OutputEncodingLib.FulfillmentContextCallOutOfRange();

return outputHash = keccak256(abi.encodePacked(
outputDescription.remoteOracle,
outputDescription.remoteFiller,
outputDescription.chainId,
outputDescription.token,
outputDescription.amount,
outputDescription.recipient,
uint16(remoteCall.length), // To protect against data collisions
remoteCall,
uint16(fulfillmentContext.length), // To protect against data collisions
fulfillmentContext[32:fulfillmentContext.length]
));
}

function _contextTrueAmount(bytes calldata fulfillmentContext) internal pure returns (uint256 amount) {
// amount = uint256(bytes32(output.fulfillmentContext[1:33])));
assembly ("memory-safe") {
amount := calldataload(add(fulfillmentContext.offset, 0x01))
}
}

function _contextSigner(bytes calldata fulfillmentContext) internal pure returns (address signer) {
// signer = address(uint160(uint256(bytes32(output.fulfillmentContext[34:66])));
assembly ("memory-safe") {
signer := calldataload(add(fulfillmentContext.offset, 0x22))
}
}

/**
* @notice Computes the amount of an order. Allows limit orders and dutch auctions.
* @dev Uses the fulfillmentContext of the output to determine order type.
* This contract only understand off-chain auction swaps.
* Structure:
* 0x80 | uint256(trueAmount) | bytes1(orderTypeIdentifier) | signer | ......
* In the actual order, bytes1(orderTypeIdentifier) is the first byte but the order server pre-pends the trueAmount
*/
function _getAmount(
OutputDescription calldata output
) internal override pure returns (uint256 amount) {
amount = _contextTrueAmount(output.fulfillmentContext);
// We don't care about the rest of fulfillmentContext. That is used for off-chain services.
}

function _validateSolver(
OutputDescription calldata output,
bytes32 solver,
bytes calldata signature
) view internal {
bytes calldata fulfillmentContext = output.fulfillmentContext;
uint256 amount = _contextTrueAmount(fulfillmentContext);
// The output amount shall describe a minimum output such
// that a fraudulent signer cannot cheat the user.
amount = amount >= output.amount ? amount : output.amount;
address signer = _contextSigner(fulfillmentContext);

bytes32 digest = _hashTypedData(SignedOutputType.hashSignedOutput(output, solver, amount));

bool isValid = SignatureCheckerLib.isValidSignatureNowCalldata(signer, digest, signature);
if (!isValid) revert InvalidSigner();
}

function fill(bytes32 orderId, OutputDescription calldata output, bytes32 proposedSolver, bytes calldata signature) external returns (bytes32) {
_validateSolver(output, proposedSolver, signature);
return _fill(orderId, output, proposedSolver);
}
}
53 changes: 53 additions & 0 deletions src/fillers/coin/types/SignedOutputType.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import { OutputDescription } from "src/libs/OutputEncodingLib.sol";

/**
* @notice Signed struct
*/
struct SignedOutput {
bytes32 winner;

bytes32 remoteOracle;
bytes32 remoteFiller;
uint256 chainId;
bytes32 token;
uint256 amount;
/** @dev Amended amount to the original order. */
uint256 trueAmount;
bytes32 recipient;
bytes remoteCall;
bytes fulfillmentContext;
}

/**
* @notice Helper library for the Signed Output type.
* TYPE_PARTIAL: An incomplete type. Is missing a field.
* TYPE_STUB: Type has no subtypes.
* TYPE: Is complete including sub-types.
*/
library SignedOutputType {
bytes constant SIGNED_OUTPUT_TYPE_STUB = abi.encodePacked(
"OutputDescription(" "bytes32 winner," "bytes32 remoteOracle," "bytes32 remoteFiller," "uint256 chainId," "bytes32 token," "uint256 amount," "uint256 trueAmount," "bytes32 recipient," "bytes remoteCall," "bytes fulfillmentContext" ")"
);

bytes32 constant SIGNED_OUTPUT_TYPE_HASH = keccak256(SIGNED_OUTPUT_TYPE_STUB);

function hashSignedOutput(OutputDescription calldata output, bytes32 winner, uint256 trueAmount) internal pure returns (bytes32) {
return keccak256(abi.encode(
SIGNED_OUTPUT_TYPE_HASH,
winner,
output.remoteOracle,
output.remoteFiller,
output.chainId,
output.token,
output.amount,
trueAmount,
output.recipient,
keccak256(output.remoteCall),
keccak256(output.fulfillmentContext)
)
);
}
}
Loading