-
Notifications
You must be signed in to change notification settings - Fork 0
feat: FDC cross-chain triggers, multi-oracle consensus, and gasless orders #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ pragma solidity ^0.8.27; | |
| import { ITeeExtensionRegistry } from "./interfaces/ITeeExtensionRegistry.sol"; | ||
| import { ITeeMachineRegistry } from "./interfaces/ITeeMachineRegistry.sol"; | ||
| import { IERC20, IUniswapV2Router, IAssetManager } from "./interfaces/IWraithExternal.sol"; | ||
| import { IFdcVerification, IPayment, IWeb2Json } from "./interfaces/IFdc.sol"; | ||
|
|
||
| /// @title WraithOrders | ||
| /// @notice Private conditional orders on Flare. A user escrows an asset and stores | ||
|
|
@@ -97,6 +98,16 @@ contract WraithOrders { | |
| bytes encrypted; // ECIES ciphertext of the trigger terms — the secret | ||
| } | ||
|
|
||
| /// @notice A gasless order the owner signed off-chain for a relayer to submit. | ||
| struct CreateIntent { | ||
| address owner; // signer, and owner of the resulting order | ||
| address tokenIn; | ||
| uint256 amountIn; | ||
| uint64 expiry; | ||
| uint256 relayerFee; // paid to the submitter in tokenIn | ||
| uint256 deadline; // unix seconds; the intent is dead after this | ||
| } | ||
|
|
||
| /// @notice Message handed to the TEE on each tick. | ||
| struct EvalMessage { | ||
| uint256 orderId; | ||
|
|
@@ -112,6 +123,24 @@ contract WraithOrders { | |
| /// @notice FAssets AssetManager used for the redeem action. | ||
| IAssetManager public assetManager; | ||
|
|
||
| /// @notice On-chain FDC verifier, resolved from the Flare contract registry | ||
| /// under `FdcVerification`. Required for cross-chain and consensus orders. | ||
| IFdcVerification public fdcVerification; | ||
|
|
||
| /// @notice EIP-712 type hash for a gasless order intent. | ||
| /// | ||
| /// The ciphertext is committed to by hash rather than by value: the intent | ||
| /// must bind the exact sealed terms a relayer submits, but hashing keeps the | ||
| /// signed struct a fixed size. | ||
| bytes32 public constant CREATE_ORDER_TYPEHASH = keccak256( | ||
| "CreateOrder(address owner,bytes32 encryptedHash,address tokenIn,uint256 amountIn,uint64 expiry,uint256 relayerFee,uint256 nonce,uint256 deadline)" | ||
| ); | ||
|
|
||
| /// @notice Per-signer counter that makes each gasless intent single-use. | ||
| /// Without it, one signature could be replayed until the signer's whole | ||
| /// balance had been escrowed. | ||
| mapping(address => uint256) public nonces; | ||
|
|
||
| /// @dev There is deliberately no owner-controlled set of accepted signers. | ||
| /// Authority to settle an order comes from `TeeMachineRegistry`, so the | ||
| /// contract owner cannot grant it to an address of their choosing. | ||
|
|
@@ -129,6 +158,8 @@ contract WraithOrders { | |
| event PeakTracked(uint256 indexed orderId, uint256 peakE18); | ||
| event RouterSet(address indexed router); | ||
| event AssetManagerSet(address indexed assetManager); | ||
| event FdcVerificationSet(address indexed fdcVerification); | ||
| event OrderRelayed(uint256 indexed orderId, address indexed relayer, uint256 fee); | ||
|
|
||
| modifier onlyOwner() { | ||
| require(msg.sender == owner, "not owner"); | ||
|
|
@@ -174,6 +205,12 @@ contract WraithOrders { | |
| emit AssetManagerSet(_assetManager); | ||
| } | ||
|
|
||
| function setFdcVerification(address _fdcVerification) external onlyOwner { | ||
| require(_fdcVerification != address(0), "zero fdc verification"); | ||
| fdcVerification = IFdcVerification(_fdcVerification); | ||
| emit FdcVerificationSet(_fdcVerification); | ||
| } | ||
|
|
||
| // --- Order lifecycle --- | ||
|
|
||
| /// @notice Create a private conditional order. | ||
|
|
@@ -196,10 +233,105 @@ contract WraithOrders { | |
|
|
||
| require(IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn), "escrow transfer failed"); | ||
|
|
||
| orderId = _push(msg.sender, _encrypted, _tokenIn, _amountIn, _expiry); | ||
| } | ||
|
|
||
| /// @notice Open an order on behalf of a user who signed for it off-chain. | ||
| /// | ||
| /// @dev This is the gasless path. A user who minted FXRP holds no FLR, so | ||
| /// they cannot pay for the transaction that would escrow it — the asset they | ||
| /// want to protect is exactly the one they cannot act on. Here they sign an | ||
| /// EIP-712 intent (free, no chain interaction) and any relayer submits it, | ||
| /// reimbursing itself with `_relayerFee` **in the escrowed token** rather | ||
| /// than in native gas. The user never needs FLR at any point: settlement is | ||
| /// already permissionless, so `execute()` costs them nothing either. | ||
| /// | ||
| /// Every parameter is covered by the signature, so a relayer cannot raise | ||
| /// its own fee, retarget the escrow, or swap in different sealed terms — | ||
| /// changing any of them simply makes the signature recover to another | ||
| /// address. | ||
| /// | ||
| /// @param _intent What the user signed. Every field is covered by the | ||
| /// signature. | ||
| /// @param _encrypted The sealed terms, committed to by hash in the intent. | ||
| /// @param _signature 65-byte EIP-712 signature over CREATE_ORDER_TYPEHASH. | ||
| function createOrderFor(CreateIntent calldata _intent, bytes calldata _encrypted, bytes calldata _signature) | ||
| external | ||
| returns (uint256 orderId) | ||
| { | ||
| require(_encrypted.length > 0, "empty ciphertext"); | ||
| require(_intent.tokenIn != address(0), "zero token"); | ||
| require(_intent.amountIn > 0, "zero amount"); | ||
| require(_intent.expiry > block.timestamp, "expiry in the past"); | ||
| require(block.timestamp <= _intent.deadline, "intent expired"); | ||
|
|
||
| require(_recover(_intentDigest(_intent, keccak256(_encrypted)), _signature) == _intent.owner, "bad intent signature"); | ||
|
|
||
| // Consumed before any transfer, so a token with a callback cannot | ||
| // re-enter and spend the same intent twice. | ||
| nonces[_intent.owner] += 1; | ||
|
|
||
| IERC20 token = IERC20(_intent.tokenIn); | ||
| require( | ||
| token.transferFrom(_intent.owner, address(this), _intent.amountIn + _intent.relayerFee), | ||
| "escrow transfer failed" | ||
| ); | ||
| if (_intent.relayerFee > 0) { | ||
| require(token.transfer(msg.sender, _intent.relayerFee), "relayer fee failed"); | ||
| } | ||
|
|
||
| orderId = _push(_intent.owner, _encrypted, _intent.tokenIn, _intent.amountIn, _intent.expiry); | ||
| emit OrderRelayed(orderId, msg.sender, _intent.relayerFee); | ||
| } | ||
|
|
||
| /// @notice EIP-712 digest a user must sign to authorize a relayed order. | ||
| /// Exposed so a wallet or relayer can verify what it is about to submit. | ||
| function intentDigest(CreateIntent calldata _intent, bytes calldata _encrypted) external view returns (bytes32) { | ||
| return _intentDigest(_intent, keccak256(_encrypted)); | ||
| } | ||
|
|
||
| function _intentDigest(CreateIntent calldata _intent, bytes32 _encryptedHash) private view returns (bytes32) { | ||
| bytes32 structHash = keccak256( | ||
| abi.encode( | ||
| CREATE_ORDER_TYPEHASH, | ||
| _intent.owner, | ||
| _encryptedHash, | ||
| _intent.tokenIn, | ||
| _intent.amountIn, | ||
| _intent.expiry, | ||
| _intent.relayerFee, | ||
| nonces[_intent.owner], | ||
| _intent.deadline | ||
| ) | ||
| ); | ||
| return keccak256(abi.encodePacked("\x19\x01", domainSeparator(), structHash)); | ||
| } | ||
|
|
||
| /// @notice EIP-712 domain separator for gasless intents. | ||
| /// @dev Computed rather than cached so a fork of the chain cannot replay | ||
| /// intents signed for the original. | ||
| function domainSeparator() public view returns (bytes32) { | ||
| return keccak256( | ||
| abi.encode( | ||
| keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), | ||
| keccak256("Wraith"), | ||
| keccak256("1"), | ||
| block.chainid, | ||
| address(this) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /// @dev Shared by the direct and relayed creation paths so the two cannot | ||
| /// drift apart on what an order looks like. | ||
| function _push(address _orderOwner, bytes calldata _encrypted, address _tokenIn, uint256 _amountIn, uint64 _expiry) | ||
| private | ||
| returns (uint256 orderId) | ||
| { | ||
| orderId = _orders.length; | ||
| _orders.push( | ||
| Order({ | ||
| owner: msg.sender, | ||
| owner: _orderOwner, | ||
| tokenIn: _tokenIn, | ||
| amountIn: _amountIn, | ||
| remaining: _amountIn, | ||
|
|
@@ -212,30 +344,113 @@ contract WraithOrders { | |
| }) | ||
| ); | ||
|
|
||
| emit OrderCreated(orderId, msg.sender, _tokenIn, _amountIn, _expiry); | ||
| emit OrderCreated(orderId, _orderOwner, _tokenIn, _amountIn, _expiry); | ||
| } | ||
|
|
||
| /// @notice Ask the TEE to evaluate an order's private condition. | ||
| /// @dev Permissionless — anyone may run a keeper. The keeper learns nothing: | ||
| /// it forwards ciphertext and cannot tell a "not triggered" reply from silence. | ||
| /// Native value is forwarded to the registry as the instruction fee. | ||
| function tick(uint256 _orderId) external payable { | ||
| Order storage o = _orders[_orderId]; | ||
| Order storage o = _prepareTick(_orderId); | ||
| _send(_orderId, abi.encode(_orderId, address(this), o.encrypted, o.peakE18, o.remaining)); | ||
| } | ||
|
|
||
| /// @notice Drops per XRP. FDC reports XRPL amounts in drops (1e-6 XRP); the | ||
| /// enclave compares everything at 1e18. | ||
| uint256 private constant XRP_DROPS_TO_E18 = 1e12; | ||
|
|
||
| /// @notice Tick an order with an FDC-attested XRPL payment attached. | ||
| /// | ||
| /// @dev The enclave cannot reach FDC: the TEE-based FDC is a Flare *system* | ||
| /// application with no interface a third-party extension can call. So the | ||
| /// proof is verified here, on-chain, and only the verified reading crosses | ||
| /// into the enclave. By the time the extension sees it, `verified` reflects | ||
| /// a Merkle check against a finalized attestation round rather than the | ||
| /// keeper's word. | ||
| /// | ||
| /// What this leaks is the *observed* fact — that some XRPL payment landed — | ||
| /// which is public on XRPL anyway. What it does not leak is the threshold | ||
| /// that fact is being compared against, which stays in the ciphertext. That | ||
| /// asymmetry is the whole design. | ||
| /// | ||
| /// Note the source travels as the FDC address *hash*, never the r-address: | ||
| /// the address the order watches is part of the secret, and the sealed terms | ||
| /// carry the same hash so the enclave can match them without either side | ||
| /// publishing it. | ||
| function tickAttested(uint256 _orderId, IPayment.Proof calldata _proof) external payable { | ||
| require(address(fdcVerification) != address(0), "FDC verification not set"); | ||
| require(fdcVerification.verifyPayment(_proof), "FDC rejected the proof"); | ||
|
|
||
| IPayment.ResponseBody calldata body = _proof.data.responseBody; | ||
| require(body.status == 0, "payment did not succeed"); | ||
| require(body.receivedAmount >= 0, "negative received amount"); | ||
|
|
||
| Order storage o = _prepareTick(_orderId); | ||
| _send( | ||
| _orderId, | ||
| abi.encode( | ||
| _orderId, | ||
| address(this), | ||
| o.encrypted, | ||
| o.peakE18, | ||
| o.remaining, | ||
| uint256(1), | ||
| uint256(body.receivedAmount) * XRP_DROPS_TO_E18, | ||
| uint256(body.blockTimestamp), | ||
| _toHexString(body.sourceAddressHash) | ||
| ) | ||
| ); | ||
| } | ||
|
Comment on lines
+381
to
+404
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Look for an existing source-id constant or any sourceId handling in the repo.
rg -n -C4 'sourceId|SOURCE_ID|testXRP|XRPL' --glob 'contracts/**/*.sol' --glob 'keeper/src/*.js'Repository: LSUDOKO/Wraith Length of output: 152 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(WraithOrders|IPayment|FdcVerification|.*Fdc.*|.*Payment.*)\.(sol|js|ts)$' || true
printf '%s\n' '--- all sourceId and XRPL references ---'
rg -n -C3 'sourceId|SOURCE_ID|testXRP|XRPL|XRP_DROPS_TO_E18|verifyPayment' . \
--glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' || true
printf '%s\n' '--- WraithOrders structure and relevant sections ---'
fd -i 'WraithOrders.sol' . -x wc -l {}
fd -i 'WraithOrders.sol' . -x sed -n '1,180p' {}
fd -i 'WraithOrders.sol' . -x sed -n '340,430p' {}Repository: LSUDOKO/Wraith Length of output: 36796 🌐 Web query:
💡 Result: To verify a payment using the Flare Data Connector (FDC), smart contracts interact with the IFdcVerification interface on the Flare network [1]. The verification process relies on Merkle proofs, which are generated off-chain and validated on-chain against a Merkle root committed by a committee of Flare validators [2]. Key technical details for Payment verification: 1. Verification Interface: The primary function is verifyPayment(IPayment.Proof _proof), which returns a boolean indicating whether the proof is valid [1]. 2. Source ID Requirement: The sourceId parameter must be a UTF-8 string representing the source blockchain, zero-padded to 32 bytes [3]. For the XRP Ledger (XRPL), this must be specifically "XRP" (case-sensitive); "xrp" will not work [2]. 3. Attestation Types: There are two main types of payment attestations: - Payment: A chain-agnostic attestation type supporting BTC, DOGE, and XRP [4][5]. It provides a standardized interface for verifying native currency payments [6]. - XRPPayment: An XRPL-specific attestation type that provides access to native XRPL fields like destination tags and memo data without needing off-chain helpers [7][8]. 4. Payment Reference: For the general Payment attestation, the standardPaymentReference is a 32-byte field often used to idempotently link on-chain settlements to specific off-chain invoices [2][5]. Verification workflow: - The contract receives a proof (IPayment.Proof) [1]. - It calls the verifyPayment method on the FdcVerification contract [2]. - Upon success, the contract further validates the response body (e.g., checking that the payment status is successful and the payment reference is not already settled) before executing business logic [2]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- FDC interfaces ---'
cat -n contracts/src/interfaces/IFdc.sol
printf '%s\n' '--- payment-related tests and fixtures ---'
rg -n -C8 'tickAttested|IPayment|Proof|ResponseBody|sourceId|receivedAmount|sourceAddressHash' contracts/test keeper extension frontend \
--glob '!node_modules/**' || true
printf '%s\n' '--- deployment and configuration references ---'
rg -n -C5 'FdcVerification|Payment|sourceId|attestationType|bytes32\\(\"XRP\"\\)|bytes32\\(\"XRPL\"\\)' . \
--glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' || trueRepository: LSUDOKO/Wraith Length of output: 50372 🌐 Web query:
💡 Result: In the Flare Data Connector (FDC) framework, the verification of a payment is performed using the FdcVerification smart contract, which validates Merkle proofs of attestation responses [1][2]. The core interface for payment verification is IPaymentVerification, which defines the following method: function verifyPayment(IPayment.Proof calldata _proof) external view returns (bool _proved); [3] To use this: 1. Resolve the FdcVerification contract instance, typically via ContractRegistry.getFdcVerification [1]. 2. Provide a proof object (IPayment.Proof), which contains the Merkle proof (bytes32[]) and the attestation response (Response struct) [4][5]. Key components of the proof structure include: - Response: Contains the metadata for the attestation, including the attestationType (e.g., Payment), the sourceId (identifying the origin data source, such as a specific blockchain), the votingRound, and the requestBody [6][4][5]. - sourceId: A 32-byte identifier (DSI) that specifies the origin of the data (e.g., a specific blockchain) [6]. - attestationType: A 32-byte identifier (ATI) for the type of attestation [6]. For the Payment type, the identifier is the ASCII bytes of "Payment" padded with zeros to 32 bytes (0x5061796d656e7400000000000000000000000000000000000000000000000000) [6]. Developers should always verify the proof using the FdcVerification contract before decoding or acting upon the data within the proof [1]. Official examples are available in the flare-hardhat-starter repository, which provides scripts for preparing requests and verifying various attestation types [1]. Citations:
🌐 Web query:
💡 Result: In the Flare Data Connector (FDC) framework, the sourceId parameter is a 32-byte identifier used to specify the origin blockchain for an attestation request [1][2]. For payments involving XRP, BTC, or DOGE, this value is derived from the UTF-8 hex encoding of the source name (e.g., 'XRP', 'BTC', 'DOGE'), padded to 32 bytes on the right with zeros [2][3]. The verification process for these payments utilizes the verifyPayment function, which is part of the FdcVerification smart contract [4][5]. This function accepts an IPayment.Proof structure—containing the attestation response and its corresponding Merkle proof—and returns a boolean indicating whether the proof is valid [1][4][5]. Key technical details regarding this flow include: 1. Attestation Data: The FDC enables smart contracts to verify off-chain transaction data without intermediaries [6][7]. Data providers reach consensus on transaction details and commit a Merkle root to the Flare network [6][7]. 2. Verification Logic: When a user submits a transaction proof, the verifyPayment function hashes the provided response body against the Merkle branch and checks it against the on-chain voting round root [6]. 3. Implementation: Developers access the FdcVerification contract (typically via the ContractRegistry) to perform this check [3][5]. 4. Compliance: Only transactions validated through this consensus process return true [6]. Contracts should also perform additional sanity checks on the decoded fields (such as status, amount, and payment references) contained within the IPayment.Response struct to ensure the payment meets specific business requirements [6][5][7]. Standard identifiers for these sources follow the format of 32-byte lowercase hex strings, such as 0x42544300... for 'BTC' [2]. When developing, these proofs can be generated by interacting with a Flare verifier node at the specific endpoint for the target chain (e.g., /verifier/xrp/Payment/prepareRequest) [3]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- IFdc.sol ---'
cat -n contracts/src/interfaces/IFdc.sol
printf '%s\n' '--- all local payment fixtures ---'
rg -n -C6 'tickAttested|IPayment|Proof|ResponseBody|sourceId|receivedAmount|sourceAddressHash' contracts/test keeper extension frontend \
--glob '!node_modules/**' || true
printf '%s\n' '--- FDC setup references ---'
rg -n -C4 'FdcVerification|attestationType|sourceId|bytes32\\(\"XRP\"\\)|bytes32\\(\"XRPL\"\\)' . \
--glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' || trueRepository: LSUDOKO/Wraith Length of output: 41634 🏁 Script executed: #!/bin/bash
set -eu
url='https://raw.githubusercontent.com/flare-foundation/flare-smart-contracts-v2/main/contracts/userInterfaces/fdc/IPaymentVerification.sol'
printf '%s\n' '--- IPaymentVerification.sol ---'
curl -fsSL "$url" | sed -n '1,220p'
printf '%s\n' '--- implementation references in upstream repository ---'
curl -fsSL 'https://api.github.com/search/code?q=verifyPayment+repo%3Aflare-foundation%2Fflare-smart-contracts-v2' \
| jq -r '.items[]?.html_url' || trueRepository: LSUDOKO/Wraith Length of output: 539 🌐 Web query:
💡 Result: The function verifyPayment is part of the Flare Network smart contract interfaces, specifically within the FDC (Flare Data Connector) system [1]. It is defined in the IPaymentVerification interface [1]. While verifyPayment itself takes a Proof object as an argument [1], the Proof object contains a Response struct, which includes a sourceId field [2][3][4]. In the context of the Flare Data Connector, the sourceId represents a unique identifier for the data source (such as a specific blockchain or external data feed) from which the attestation proof was derived [2][3][4]. The function signature is as follows: function verifyPayment(IPayment.Proof calldata _proof) external view returns (bool _proved); The Proof structure includes: - merkleProof: A Merkle proof corresponding to the attestation response [2][3]. - data: The attestation response, which includes the sourceId [2][3]. This design allows the Flare network to cryptographically verify data originating from external sources [1]. Citations:
🌐 Web query:
💡 Result: The Citations:
Bind the payment proof to XRP Require 🤖 Prompt for AI Agents |
||
|
|
||
| /// @notice Tick an order with an FDC-attested Web2 reading attached. | ||
| /// | ||
| /// @dev This is the second oracle in a consensus order. The attestation's | ||
| /// `abiEncodedData` is whatever the request's `abiSignature` declared; Wraith | ||
| /// requires `(string source, uint256 valueE18, uint256 timestamp)`, which a | ||
| /// one-line `postProcessJq` produces from most price APIs. | ||
| /// | ||
| /// Requiring two independent sources to agree is what defends a private stop | ||
| /// against the one attack privacy alone does not stop: an adversary who | ||
| /// cannot see the trigger can still walk a single price feed down until | ||
| /// *something* fires. They cannot walk two. | ||
| function tickAttestedWeb2(uint256 _orderId, IWeb2Json.Proof calldata _proof) external payable { | ||
| require(address(fdcVerification) != address(0), "FDC verification not set"); | ||
| require(fdcVerification.verifyWeb2Json(_proof), "FDC rejected the proof"); | ||
|
|
||
| (string memory source, uint256 valueE18, uint256 observedAt) = | ||
| abi.decode(_proof.data.responseBody.abiEncodedData, (string, uint256, uint256)); | ||
| require(bytes(source).length > 0, "attestation names no source"); | ||
|
|
||
| Order storage o = _prepareTick(_orderId); | ||
| _send( | ||
| _orderId, | ||
| abi.encode( | ||
| _orderId, address(this), o.encrypted, o.peakE18, o.remaining, uint256(1), valueE18, observedAt, source | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /// @dev The liveness and rate-limit checks every tick shares. | ||
| function _prepareTick(uint256 _orderId) private returns (Order storage o) { | ||
| o = _orders[_orderId]; | ||
| require(o.owner != address(0), "no such order"); | ||
| require(!o.executed, "already executed"); | ||
| require(!o.cancelled, "cancelled"); | ||
| require(block.timestamp < o.expiry, "expired"); | ||
| require(block.timestamp >= o.nextTickAt, "ticked too recently"); | ||
|
|
||
| o.nextTickAt = uint64(block.timestamp) + MIN_TICK_INTERVAL; | ||
| } | ||
|
|
||
| function _send(uint256 _orderId, bytes memory _message) private { | ||
| address[] memory teeIds = TEE_MACHINE_REGISTRY.getRandomTeeIds(_getExtensionId(), 1); | ||
| address[] memory cosigners = new address[](0); | ||
|
|
||
| ITeeExtensionRegistry.TeeInstructionParams memory params = ITeeExtensionRegistry.TeeInstructionParams({ | ||
| opType: OP_TYPE_WRAITH, | ||
| opCommand: OP_COMMAND_EVAL_ORDER, | ||
| message: abi.encode(_orderId, address(this), o.encrypted, o.peakE18, o.remaining), | ||
| message: _message, | ||
| cosigners: cosigners, | ||
| cosignersThreshold: 0, | ||
| claimBackAddress: msg.sender | ||
|
|
@@ -245,6 +460,21 @@ contract WraithOrders { | |
| emit OrderTicked(_orderId, instructionId); | ||
| } | ||
|
|
||
| /// @dev Lowercase `0x`-prefixed hex of a bytes32, matching `vm.toString` | ||
| /// and JavaScript's `toHex`. The enclave compares sources as strings, so | ||
| /// both sides must agree on this rendering exactly. | ||
| function _toHexString(bytes32 _value) private pure returns (string memory) { | ||
| bytes memory alphabet = "0123456789abcdef"; | ||
| bytes memory out = new bytes(66); | ||
| out[0] = "0"; | ||
| out[1] = "x"; | ||
| for (uint256 i = 0; i < 32; ++i) { | ||
| out[2 + i * 2] = alphabet[uint8(_value[i]) >> 4]; | ||
| out[3 + i * 2] = alphabet[uint8(_value[i]) & 0x0f]; | ||
| } | ||
| return string(out); | ||
| } | ||
|
|
||
| /// @notice Settle an order using a TEE-signed result proving its condition fired. | ||
| /// @dev The TEE node signs `keccak256(abi.encode(TEE_ACTION_RESULT_PREFIX, chainId, | ||
| /// ActionResult.Hash()))` with its registered key under the EIP-191 personal-sign | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Update the stale test counts elsewhere in the README.
The verification table now totals 160 tests, but two other places still state the old numbers:
tests-81%20passing.Also, line 185 uses "authorize" while line 130 uses "authorise". Pick one spelling.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~185-~185: Do not mix variants of the same word (‘authorize’ and ‘authorise’) within a single text.
Context: ...e metric. - The contract owner cannot authorize a signer of their choosing. Settlemen...
(EN_WORD_COHERENCY)
🤖 Prompt for AI Agents
Source: Linters/SAST tools