|
| 1 | +// SPDX-License-Identifier: LicenseRef-DCL-1.0 |
| 2 | +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd |
| 3 | +pragma solidity =0.8.25; |
| 4 | + |
| 5 | +import {RaindexV6ExternalRealTest} from "test/util/abstract/RaindexV6ExternalRealTest.sol"; |
| 6 | +import { |
| 7 | + OrderConfigV4, |
| 8 | + OrderV4, |
| 9 | + IOV2, |
| 10 | + TaskV2, |
| 11 | + EvaluableV4, |
| 12 | + SignedContextV1 |
| 13 | +} from "raindex-interface-0.1.8/src/interface/IRaindexV6.sol"; |
| 14 | +import {IERC20} from "@openzeppelin-contracts-5.7.0/token/ERC20/IERC20.sol"; |
| 15 | +import {MessageHashUtils} from "@openzeppelin-contracts-5.7.0/utils/cryptography/MessageHashUtils.sol"; |
| 16 | +import {Strings} from "@openzeppelin-contracts-5.7.0/utils/Strings.sol"; |
| 17 | +import {LibDecimalFloat} from "rain-math-float-0.3.10/src/lib/LibDecimalFloat.sol"; |
| 18 | +import {LibTestAddOrder} from "test/util/lib/LibTestAddOrder.sol"; |
| 19 | + |
| 20 | +/// @title RaindexV6PostTaskSignedContextTest |
| 21 | +/// @notice `signer<i>()` and `signed-context<i j>()` read the real signers and |
| 22 | +/// signed data in every Raindex post task, so a caller cannot forge a signer by |
| 23 | +/// placing an address in signed data. |
| 24 | +contract RaindexV6PostTaskSignedContextTest is RaindexV6ExternalRealTest { |
| 25 | + using Strings for address; |
| 26 | + |
| 27 | + uint256 internal constant SIGNER_A_PK = 0xA11CE; |
| 28 | + uint256 internal constant SIGNER_B_PK = 0xB0B; |
| 29 | + address internal constant DATA_A = address(0xDA7A0); |
| 30 | + address internal constant DATA_B = address(0xDA7A1); |
| 31 | + address internal constant TRUSTED = address(0x7777); |
| 32 | + address internal constant ACTOR = address(0xD0); |
| 33 | + |
| 34 | + function signed(uint256 pk, bytes32[] memory ctx) internal pure returns (SignedContextV1 memory) { |
| 35 | + bytes32 h = MessageHashUtils.toEthSignedMessageHash(keccak256(abi.encodePacked(ctx))); |
| 36 | + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, h); |
| 37 | + return SignedContextV1(vm.addr(pk), ctx, abi.encodePacked(r, s, v)); |
| 38 | + } |
| 39 | + |
| 40 | + function single(address a) internal pure returns (bytes32[] memory) { |
| 41 | + bytes32[] memory ctx = new bytes32[](1); |
| 42 | + ctx[0] = bytes32(uint256(uint160(a))); |
| 43 | + return ctx; |
| 44 | + } |
| 45 | + |
| 46 | + function task(string memory body, SignedContextV1[] memory sc) internal returns (TaskV2[] memory) { |
| 47 | + TaskV2[] memory post = new TaskV2[](1); |
| 48 | + post[0] = TaskV2( |
| 49 | + EvaluableV4( |
| 50 | + iInterpreter, |
| 51 | + iStore, |
| 52 | + iParserV2.parse2( |
| 53 | + bytes(string.concat("using-words-from ", address(iSubParser).toHexString(), "\n", body)) |
| 54 | + ) |
| 55 | + ), |
| 56 | + sc |
| 57 | + ); |
| 58 | + return post; |
| 59 | + } |
| 60 | + |
| 61 | + /// Two signers, each signing its own data; the task asserts each word reads |
| 62 | + /// its own signer and datum. |
| 63 | + function realSignersTask() internal returns (TaskV2[] memory) { |
| 64 | + SignedContextV1[] memory sc = new SignedContextV1[](2); |
| 65 | + sc[0] = signed(SIGNER_A_PK, single(DATA_A)); |
| 66 | + sc[1] = signed(SIGNER_B_PK, single(DATA_B)); |
| 67 | + return task( |
| 68 | + string.concat( |
| 69 | + ":ensure(equal-to(signer<0>() ", |
| 70 | + vm.addr(SIGNER_A_PK).toHexString(), |
| 71 | + ") \"signer 0\"),", |
| 72 | + ":ensure(equal-to(signer<1>() ", |
| 73 | + vm.addr(SIGNER_B_PK).toHexString(), |
| 74 | + ") \"signer 1\"),", |
| 75 | + ":ensure(equal-to(signed-context<0 0>() ", |
| 76 | + DATA_A.toHexString(), |
| 77 | + ") \"signed context 0\"),", |
| 78 | + ":ensure(equal-to(signed-context<1 0>() ", |
| 79 | + DATA_B.toHexString(), |
| 80 | + ") \"signed context 1\");" |
| 81 | + ), |
| 82 | + sc |
| 83 | + ); |
| 84 | + } |
| 85 | + |
| 86 | + /// One attacker key signs three contexts, the third holding `TRUSTED`. A |
| 87 | + /// task that accepts only `TRUSTED` as signer must reject it. |
| 88 | + function forgedSignerTask() internal returns (TaskV2[] memory) { |
| 89 | + SignedContextV1[] memory sc = new SignedContextV1[](3); |
| 90 | + sc[0] = signed(SIGNER_A_PK, single(DATA_A)); |
| 91 | + sc[1] = signed(SIGNER_A_PK, single(DATA_A)); |
| 92 | + sc[2] = signed(SIGNER_A_PK, single(TRUSTED)); |
| 93 | + return task(string.concat(":ensure(equal-to(signer<0>() ", TRUSTED.toHexString(), ") \"signer trusted\");"), sc); |
| 94 | + } |
| 95 | + |
| 96 | + function deposit(TaskV2[] memory post) internal { |
| 97 | + vm.mockCall( |
| 98 | + address(iToken0), |
| 99 | + abi.encodeWithSelector(IERC20.transferFrom.selector, ACTOR, address(iRaindex), 1e18), |
| 100 | + abi.encode(true) |
| 101 | + ); |
| 102 | + vm.prank(ACTOR); |
| 103 | + iRaindex.deposit4(address(iToken0), bytes32(uint256(1)), LibDecimalFloat.packLossless(1, 0), post); |
| 104 | + } |
| 105 | + |
| 106 | + function withdraw(TaskV2[] memory post) internal { |
| 107 | + deposit(new TaskV2[](0)); |
| 108 | + vm.mockCall(address(iToken0), abi.encodeWithSelector(IERC20.transfer.selector, ACTOR, 1e18), abi.encode(true)); |
| 109 | + vm.prank(ACTOR); |
| 110 | + iRaindex.withdraw4(address(iToken0), bytes32(uint256(1)), LibDecimalFloat.packLossless(1, 0), post); |
| 111 | + } |
| 112 | + |
| 113 | + function orderConfig() internal view returns (OrderConfigV4 memory) { |
| 114 | + IOV2[] memory inputs = new IOV2[](1); |
| 115 | + inputs[0] = IOV2(address(iToken0), bytes32(uint256(1))); |
| 116 | + IOV2[] memory outputs = new IOV2[](1); |
| 117 | + outputs[0] = IOV2(address(iToken1), bytes32(uint256(1))); |
| 118 | + return OrderConfigV4( |
| 119 | + EvaluableV4(iInterpreter, iStore, LibTestAddOrder.VALID_ORDER_BYTECODE), inputs, outputs, bytes32(0), "", "" |
| 120 | + ); |
| 121 | + } |
| 122 | + |
| 123 | + function addOrder(TaskV2[] memory post) internal { |
| 124 | + vm.prank(ACTOR); |
| 125 | + assertTrue(iRaindex.addOrder4(orderConfig(), post)); |
| 126 | + } |
| 127 | + |
| 128 | + function removeOrder(TaskV2[] memory post) internal { |
| 129 | + OrderConfigV4 memory config = orderConfig(); |
| 130 | + vm.prank(ACTOR); |
| 131 | + iRaindex.addOrder4(config, new TaskV2[](0)); |
| 132 | + vm.prank(ACTOR); |
| 133 | + assertTrue( |
| 134 | + iRaindex.removeOrder3( |
| 135 | + OrderV4(ACTOR, config.evaluable, config.validInputs, config.validOutputs, config.nonce), post |
| 136 | + ) |
| 137 | + ); |
| 138 | + } |
| 139 | + |
| 140 | + function entask(TaskV2[] memory post) internal { |
| 141 | + vm.prank(ACTOR); |
| 142 | + iRaindex.entask2(post); |
| 143 | + } |
| 144 | + |
| 145 | + function testDepositTaskSignerWordsReadRealSigners() external { |
| 146 | + deposit(realSignersTask()); |
| 147 | + } |
| 148 | + |
| 149 | + function testDepositTaskRejectsSignerForgedInSignedData() external { |
| 150 | + TaskV2[] memory post = forgedSignerTask(); |
| 151 | + vm.expectRevert("signer trusted"); |
| 152 | + this.externalDeposit(post); |
| 153 | + } |
| 154 | + |
| 155 | + function testWithdrawTaskSignerWordsReadRealSigners() external { |
| 156 | + withdraw(realSignersTask()); |
| 157 | + } |
| 158 | + |
| 159 | + function testWithdrawTaskRejectsSignerForgedInSignedData() external { |
| 160 | + TaskV2[] memory post = forgedSignerTask(); |
| 161 | + vm.expectRevert("signer trusted"); |
| 162 | + this.externalWithdraw(post); |
| 163 | + } |
| 164 | + |
| 165 | + function testAddOrderTaskSignerWordsReadRealSigners() external { |
| 166 | + addOrder(realSignersTask()); |
| 167 | + } |
| 168 | + |
| 169 | + function testAddOrderTaskRejectsSignerForgedInSignedData() external { |
| 170 | + TaskV2[] memory post = forgedSignerTask(); |
| 171 | + vm.expectRevert("signer trusted"); |
| 172 | + this.externalAddOrder(post); |
| 173 | + } |
| 174 | + |
| 175 | + function testRemoveOrderTaskSignerWordsReadRealSigners() external { |
| 176 | + removeOrder(realSignersTask()); |
| 177 | + } |
| 178 | + |
| 179 | + function testRemoveOrderTaskRejectsSignerForgedInSignedData() external { |
| 180 | + TaskV2[] memory post = forgedSignerTask(); |
| 181 | + vm.expectRevert("signer trusted"); |
| 182 | + this.externalRemoveOrder(post); |
| 183 | + } |
| 184 | + |
| 185 | + function testEntaskSignerWordsReadRealSigners() external { |
| 186 | + entask(realSignersTask()); |
| 187 | + } |
| 188 | + |
| 189 | + function testEntaskRejectsSignerForgedInSignedData() external { |
| 190 | + TaskV2[] memory post = forgedSignerTask(); |
| 191 | + vm.expectRevert("signer trusted"); |
| 192 | + this.externalEntask(post); |
| 193 | + } |
| 194 | + |
| 195 | + function externalDeposit(TaskV2[] memory post) external { |
| 196 | + deposit(post); |
| 197 | + } |
| 198 | + |
| 199 | + function externalWithdraw(TaskV2[] memory post) external { |
| 200 | + withdraw(post); |
| 201 | + } |
| 202 | + |
| 203 | + function externalAddOrder(TaskV2[] memory post) external { |
| 204 | + addOrder(post); |
| 205 | + } |
| 206 | + |
| 207 | + function externalRemoveOrder(TaskV2[] memory post) external { |
| 208 | + removeOrder(post); |
| 209 | + } |
| 210 | + |
| 211 | + function externalEntask(TaskV2[] memory post) external { |
| 212 | + entask(post); |
| 213 | + } |
| 214 | +} |
0 commit comments