Skip to content

Commit 833873f

Browse files
committed
use the version of IndirectCall that verifies call origin
1 parent 2edf9c7 commit 833873f

File tree

2 files changed

+146
-30
lines changed

2 files changed

+146
-30
lines changed

contracts/crosschain/ERC7802Bridge.sol

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,18 @@ import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol";
88
import {IERC7786GatewaySource, IERC7786Receiver} from "../interfaces/IERC7786.sol";
99

1010
// Utilities
11-
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
1211
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
1312
import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
1413
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
1514
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol";
1615
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
1716
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol";
18-
19-
contract ERC7802BridgeSatellite {
20-
address private immutable _bridge = msg.sender;
21-
22-
fallback(bytes calldata data) external payable returns (bytes memory) {
23-
require(msg.sender == _bridge && data.length >= 20);
24-
25-
(bool success, bytes memory returndata) = address(bytes20(data)).call{value: msg.value}(data[20:]);
26-
27-
if (!success) {
28-
assembly ("memory-safe") {
29-
revert(add(returndata, 0x20), mload(returndata))
30-
}
31-
} else {
32-
return returndata;
33-
}
34-
}
35-
}
17+
import {IndirectCall} from "../utils/IndirectCall.sol";
3618

3719
contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Receiver {
3820
using BitMaps for BitMaps.BitMap;
3921
using InteroperableAddress for bytes;
22+
4023
struct BridgeMetadata {
4124
address token;
4225
bool isPaused;
@@ -45,7 +28,6 @@ contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Rece
4528
mapping(bytes chain => bytes) remote;
4629
}
4730

48-
address private immutable _satellite = address(new ERC7802BridgeSatellite());
4931
mapping(bytes32 bridgeId => BridgeMetadata) private _bridges;
5032
BitMaps.BitMap private _processed;
5133

@@ -70,8 +52,8 @@ contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Rece
7052
// ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
7153
// │ Getters │
7254
// └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
73-
function getBridgeEndpoint(bytes32 bridgeId) public view returns (address) {
74-
return Clones.predictDeterministicAddress(_satellite, bridgeId);
55+
function getBridgeEndpoint(bytes32 bridgeId) public returns (address) {
56+
return IndirectCall.getRelayer(bridgeId);
7557
}
7658

7759
function getBridgeToken(bytes32 bridgeId) public view returns (address token, bool isCustodial) {
@@ -128,7 +110,6 @@ contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Rece
128110
details.token = token;
129111
details.isCustodial = isCustodial;
130112

131-
Clones.cloneDeterministic(_satellite, bridgeId);
132113
_safeMint(admin == address(0) ? address(1) : admin, uint256(bridgeId));
133114

134115
for (uint256 i = 0; i < foreign.length; ++i) {
@@ -237,11 +218,10 @@ contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Rece
237218
function _fetchTokens(bytes32 bridgeId, address from, uint256 amount) private returns (address) {
238219
address token = _bridges[bridgeId].token;
239220
if (_bridges[bridgeId].isCustodial) {
240-
(bool success, bytes memory returndata) = getBridgeEndpoint(bridgeId).call(
241-
abi.encodePacked(
242-
token,
243-
abi.encodeCall(IERC20.transferFrom, (from, getBridgeEndpoint(bridgeId), amount))
244-
)
221+
(bool success, bytes memory returndata) = IndirectCall.indirectCall(
222+
token,
223+
abi.encodeCall(IERC20.transferFrom, (from, getBridgeEndpoint(bridgeId), amount)),
224+
bridgeId
245225
);
246226
require(success && (returndata.length == 0 ? token.code.length == 0 : uint256(bytes32(returndata)) == 1));
247227
} else {
@@ -256,8 +236,10 @@ contract ERC7802Bridge is ERC721("ERC7802Bridge", "ERC7802Bridge"), IERC7786Rece
256236
function _distributeTokens(bytes32 bridgeId, address to, uint256 amount) private returns (address) {
257237
address token = _bridges[bridgeId].token;
258238
if (_bridges[bridgeId].isCustodial) {
259-
(bool success, bytes memory returndata) = getBridgeEndpoint(bridgeId).call(
260-
abi.encodePacked(token, abi.encodeCall(IERC20.transfer, (to, amount)))
239+
(bool success, bytes memory returndata) = IndirectCall.indirectCall(
240+
token,
241+
abi.encodeCall(IERC20.transfer, (to, amount)),
242+
bridgeId
261243
);
262244
require(success && (returndata.length == 0 ? token.code.length == 0 : uint256(bytes32(returndata)) == 1));
263245
} else {

contracts/utils/IndirectCall.sol

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.20;
4+
5+
/**
6+
* @dev Helper contract for performing potentially dangerous calls through a relay the hide the address of the
7+
* original sender.
8+
*
9+
* Some contract are required to perform arbitrary action controlled by user input. This is dangerous if the contract
10+
* has special permissions, or holds assets. In such cases, using a relay contract can be useful to change the
11+
* msg.sender of the outgoing call. This pattern is used in the ERC-4337 entrypoint that relies on a helper called the
12+
* "senderCreator" when calling account factories. Similarly ERC-6942 does factory calls that could be dangerous if
13+
* performed directly.
14+
*
15+
* This contract provides a `indirectCall` that can be used to perform dangerous calls. These calls are indirect
16+
* through a minimal relayer.
17+
*/
18+
library IndirectCall {
19+
function indirectCall(address target, bytes memory data) internal returns (bool, bytes memory) {
20+
return indirectCall(target, 0, data);
21+
}
22+
23+
function indirectCall(address target, uint256 value, bytes memory data) internal returns (bool, bytes memory) {
24+
return indirectCall(target, value, data, bytes32(0));
25+
}
26+
27+
function indirectCall(address target, bytes memory data, bytes32 salt) internal returns (bool, bytes memory) {
28+
return indirectCall(target, 0, data, salt);
29+
}
30+
31+
function indirectCall(
32+
address target,
33+
uint256 value,
34+
bytes memory data,
35+
bytes32 salt
36+
) internal returns (bool, bytes memory) {
37+
return getRelayer(salt).call{value: value}(abi.encodePacked(target, data));
38+
}
39+
40+
function getRelayer() internal returns (address) {
41+
return getRelayer(bytes32(0));
42+
}
43+
44+
function getRelayer(bytes32 salt) internal returns (address relayer) {
45+
// [Relayer details]
46+
//
47+
// deployment prefix: 5f604780600a5f3981f3
48+
// deployed bytecode: 73<addr>331460133611166022575f5ffd5b6014360360145f375f5f601436035f345f3560601c5af13d5f5f3e5f3d91604557fd5bf3
49+
//
50+
// offset | bytecode | opcode | stack
51+
// -------|-------------|----------------|--------
52+
// 0x0000 | 73<factory> | push20 <addr> | <factory>
53+
// 0x0015 | 33 | address | <caller> <factory>
54+
// 0x0016 | 14 | eq | access
55+
// 0x0017 | 6013 | push1 0x13 | 0x13 access
56+
// 0x0019 | 36 | calldatasize | cds 0x13 access
57+
// 0x001a | 11 | gt | (cds>0x13) access
58+
// 0x001b | 16 | and | (cds>0x13 && access)
59+
// 0x001c | 6022 | push1 0x22 | 0x22 (cds>0x13 && access)
60+
// 0x001e | 57 | jumpi | 0x22 (cds>0x13 && access)
61+
// 0x001f | 5f | push0 | 0
62+
// 0x0020 | 5f | push0 | 0 0
63+
// 0x0021 | fd | revert |
64+
// 0x0022 | 5b | jumpdest |
65+
// 0x0023 | 6014 | push1 0x14 | 0x14
66+
// 0x0025 | 36 | calldatasize | cds 0x14
67+
// 0x0026 | 03 | sub | (cds-0x14)
68+
// 0x0027 | 6014 | push1 0x14 | 0x14 (cds-0x14)
69+
// 0x0029 | 5f | push0 | 0 0x14 (cds-0x14)
70+
// 0x002a | 37 | calldatacopy |
71+
// 0x002b | 5f | push0 | 0
72+
// 0x002c | 5f | push0 | 0 0
73+
// 0x002d | 6014 | push1 0x14 | 0x14 0 0
74+
// 0x002f | 36 | calldatasize | cds 0x14 0 0
75+
// 0x0030 | 03 | sub | (cds-0x14) 0 0
76+
// 0x0031 | 5f | push0 | 0 (cds-0x14) 0 0
77+
// 0x0032 | 34 | callvalue | value 0 (cds-0x14) 0 0
78+
// 0x0033 | 5f | push0 | 0 value 0 (cds-0x14) 0 0
79+
// 0x0034 | 35 | calldataload | cd[0] value 0 (cds-0x14) 0 0
80+
// 0x0035 | 6060 | push1 0x60 | 0x60 cd[0] value 0 (cds-0x14) 0 0
81+
// 0x0037 | 1c | shr | target value 0 (cds-0x14) 0 0
82+
// 0x0038 | 5a | gas | gas target value 0 (cds-0x14) 0 0
83+
// 0x0039 | f1 | call | suc
84+
// 0x003a | 3d | returndatasize | rds suc
85+
// 0x003b | 5f | push0 | 0 rds suc
86+
// 0x003c | 5f | push0 | 0 0 rds suc
87+
// 0x003d | 3e | returndatacopy | suc
88+
// 0x003e | 5f | push0 | 0 suc
89+
// 0x003f | 3d | returndatasize | rds 0 suc
90+
// 0x0040 | 91 | swap2 | suc 0 rds
91+
// 0x0041 | 6045 | push1 0x45 | 0x45 suc 0 rds
92+
// 0x0043 | 57 | jumpi | 0 rds
93+
// 0x0044 | fd | revert |
94+
// 0x0045 | 5b | jumpdest | 0 rds
95+
// 0x0046 | f3 | return |
96+
97+
assembly ("memory-safe") {
98+
let fmp := mload(0x40)
99+
100+
// build initcode at FMP
101+
mstore(add(fmp, 0x46), 0x60145f375f5f601436035f345f3560601c5af13d5f5f3e5f3d91604557fd5bf3)
102+
mstore(add(fmp, 0x26), 0x331460133611166022575f5ffd5b60143603)
103+
mstore(add(fmp, 0x14), address())
104+
mstore(add(fmp, 0), 0x5f604780600a5f3981f373)
105+
let initcodehash := keccak256(add(fmp, 0x15), 0x51)
106+
107+
// compute create2 address
108+
mstore(0x40, initcodehash)
109+
mstore(0x20, salt)
110+
mstore(0x00, address())
111+
mstore8(0x0b, 0xff)
112+
relayer := and(keccak256(0x0b, 0x55), shr(96, not(0)))
113+
114+
// is relayer not yet deployed, deploy it
115+
if iszero(extcodesize(relayer)) {
116+
if iszero(create2(0, add(fmp, 0x15), 0x51, salt)) {
117+
returndatacopy(fmp, 0, returndatasize())
118+
revert(fmp, returndatasize())
119+
}
120+
}
121+
122+
// cleanup fmp space used as scratch
123+
mstore(0x40, fmp)
124+
}
125+
126+
// For reference: equivalent in solidity
127+
// bytes memory initcode = abi.encodePacked(hex"5f604780600a5f3981f373", address(this), hex"331460133611166022575f5ffd5b6014360360145f375f5f601436035f345f3560601c5af13d5f5f3e5f3d91604557fd5bf3");
128+
// address relayer = Create2.computeAddress(salt, keccak256(initcode));
129+
// if (relayer.code.length == 0) {
130+
// Create2.deploy(0, salt, initcode);
131+
// }
132+
// return relayer;
133+
}
134+
}

0 commit comments

Comments
 (0)