At the moment you need to use Cairo for this. Warp is a transpiler from Solidity to Cairo. It looks like this:
$$ \text{warp}:\text{Solidity}\rightarrow\text{Cairo} $$
Nice; how's it work?
$ warp transpile /path/to/your/solidity/codebase/Foobar.sol
Woah, this is going to be easy. How do we install it?
$ git clone git@github.com:NethermindEth/warp.git && cd warp && wc -l readme.md | cut -f 1 -d ' '
357
Wow! That's a lot of steps! The time saved could be huge though, so I guess it's worth it.
*Follows README installation steps*
Phew, glad that's done. Let's transpile!
$ git clone git@github.com:ourzora/v3.git && cd v3
$ warp transpile contracts/ZoraModuleManager.sol
---Compile Failed---
Compiler version 0.8.14 reported errors:
--1--
ParserError: Source file requires different compiler version (current compiler is 0.8.14+commit.de5c19a6.Linux.g++) - note that nightly builds are considered to be strictly less than the released version
--> contracts/ZoraModuleManager.sol:2:1:
|
2 | pragma solidity 0.8.10;
| ^^^^^^^^^^^^^^^^^^^^^^^
Cannot start transpilation
Ah, wrong Solidity version; bound to happen. I guess warp
allows us to specify the Solidity version we want to use, or alternatively just provide it a path to a solc
binary we can use?
$ warp
Usage: warp [options] [command]
Options:
-h, --help display help for command
Commands:
transpile [options] <files...>
transform [options] <file>
test [options]
analyse [options] <file>
status [options] <tx_hash>
compile [options] <file>
deploy [options] <file>
deploy_account [options]
invoke [options] <file>
call [options] <file>
install [options]
declare [options] <cairo_contract> Command to declare Cairo contract on a StarkNet Network.
version
help [command] display help for command
$ warp help transpile
Usage: warp transpile [options] <files...>
Options:
--compile-cairo
--no-compile-errors
--check-trees
--highlight <ids...>
--order <passOrder>
-o, --output-dir <path> Output directory for transpiled Cairo files. (default: "warp_output")
-d, --debug-info Include debug information. (default: false)
--print-trees
--no-result
--no-stubs
--no-strict
--until <pass>
--no-warnings
--dev Run AST sanity checks on every pass instead of the final AST only (default: false)
-h, --help display help for command
Huh, nothing here. Maybe the warp compile
subcommand has something and it's just been omitted from the help text for wrap transpile
?
$ warp help compile
Usage: warp compile [options] <file>
Options:
-d, --debug-info Include debug information. (default: false)
-h, --help display help for command
Okay, don't panic. Surely the source will shed some light on what's going on here?
$ cd /path/to/warp/source
$ ls src
2110008 4.0K drwxr-xr-x 8 jmcph4 jmcph4 4.0K Nov 21 01:36 .
2109932 4.0K drwxr-xr-x 17 jmcph4 jmcph4 4.0K Nov 21 01:37 ..
2110009 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:36 ast
2088607 12K -rw-r--r-- 1 jmcph4 jmcph4 9.0K Nov 21 01:36 autoRunSemanticTests.ts
2110011 4.0K drwxr-xr-x 8 jmcph4 jmcph4 4.0K Nov 21 01:36 cairoUtilFuncGen
2088658 44K -rw-r--r-- 1 jmcph4 jmcph4 41K Nov 21 01:36 cairoWriter.ts
2088659 4.0K -rw-r--r-- 1 jmcph4 jmcph4 405 Nov 21 01:36 export.ts
2088660 4.0K -rw-r--r-- 1 jmcph4 jmcph4 2.2K Nov 21 01:36 freeStructWritter.ts
2110019 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:36 icf
2088670 12K -rw-r--r-- 1 jmcph4 jmcph4 12K Nov 21 01:36 index.ts
2088671 4.0K -rw-r--r-- 1 jmcph4 jmcph4 3.4K Nov 21 01:36 io.ts
2088672 4.0K -rw-r--r-- 1 jmcph4 jmcph4 918 Nov 21 01:36 nethersolc.ts
2110021 4.0K drwxr-xr-x 16 jmcph4 jmcph4 4.0K Nov 21 01:36 passes
2088796 8.0K -rw-r--r-- 1 jmcph4 jmcph4 6.0K Nov 21 01:36 semanticTestRunner.ts
2088797 8.0K -rw-r--r-- 1 jmcph4 jmcph4 6.9K Nov 21 01:36 solCompile.ts
2088798 4.0K -rw-r--r-- 1 jmcph4 jmcph4 3.2K Nov 21 01:36 solWriter.ts
2088799 12K -rw-r--r-- 1 jmcph4 jmcph4 9.1K Nov 21 01:36 starknetCli.ts
2088800 24K -rw-r--r-- 1 jmcph4 jmcph4 21K Nov 21 01:36 testing.ts
2088801 8.0K -rw-r--r-- 1 jmcph4 jmcph4 7.3K Nov 21 01:36 transpiler.ts
2110037 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:36 utils
2110038 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:36 warplib
$ less src/nethersolc.ts
import * as os from 'os';
import * as path from 'path';
import { NotSupportedYetError } from './utils/errors';
type SupportedPlatforms = 'linux_x64' | 'darwin_x64' | 'darwin_arm64';
export type SupportedSolcVersions = '7' | '8';
function getPlatform(): SupportedPlatforms {
const platform = `${os.platform()}_${os.arch()}`;
switch (platform) {
case 'linux_x64':
case 'darwin_x64':
case 'darwin_arm64':
return platform;
default:
throw new NotSupportedYetError(`Unsupported plaform ${platform}`);
}
}
export function nethersolcPath(version: SupportedSolcVersions): string {
const platform = getPlatform();
return path.resolve(__dirname, '..', 'nethersolc', platform, version, 'solc');
}
export function fullVersionFromMajor(majorVersion: SupportedSolcVersions): string {
switch (majorVersion) {
case '7':
return '0.7.6';
case '8':
return '0.8.14';
}
}
Whack. Alas, we're a lot harder to knock over than that! We can trick warp
into using a different Solidity compiler because it just vendors the raw binary anyway. Let's acquire a binary of the Solidity compiler for the version Zora uses: 0.8.10. Fortunately, the Ethereum Foundation maintains a repository of exactly this for each Solidity version.
$ curl --output solc810 https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/linux-amd64/solc-linux-amd64-v0.8.10%2Bcommit.fc410830
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 13.1M 100 13.1M 0 0 28.0M 0 --:--:-- --:--:-- --:--:-- 28.2M
$ chmod u+x solc810
Now we need to find where on Earth warp
loads its vendored compiler from.
$ which warp
/home/jmcph4/.yarn/bin/warp
$ cd /home/jmcph4/.yarn/bin
$ ls
1558195 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:32 .
1558194 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Jul 8 2021 ..
1534712 0 lrwxrwxrwx 1 jmcph4 jmcph4 50 Nov 21 01:32 ethers -> ../../.config/yarn/global/node_modules/.bin/ethers
1535269 0 lrwxrwxrwx 1 jmcph4 jmcph4 56 Nov 21 01:32 ethers-build -> ../../.config/yarn/global/node_modules/.bin/ethers-build
1535270 0 lrwxrwxrwx 1 jmcph4 jmcph4 57 Nov 21 01:32 ethers-deploy -> ../../.config/yarn/global/node_modules/.bin/ethers-deploy
1564459 0 lrwxrwxrwx 1 jmcph4 jmcph4 48 Nov 21 01:32 warp -> ../../.config/yarn/global/node_modules/.bin/warp
$ cd ../../.config/yarn/global/node_modules/.bin
$ ls
total 24K
2654710 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:32 .
2608393 20K drwxr-xr-x 586 jmcph4 jmcph4 20K Nov 21 01:32 ..
2615429 0 lrwxrwxrwx 1 jmcph4 jmcph4 24 Nov 21 01:32 ethers -> ../ethers-cli/bin/ethers
2615430 0 lrwxrwxrwx 1 jmcph4 jmcph4 30 Nov 21 01:32 ethers-build -> ../ethers-cli/bin/ethers-build
2615431 0 lrwxrwxrwx 1 jmcph4 jmcph4 34 Nov 21 01:32 ethers-deploy -> ../ethers-cli/bin/ethers-deploy.js
2615428 0 lrwxrwxrwx 1 jmcph4 jmcph4 31 Nov 21 01:32 warp -> ../@nethermindeth/warp/bin/warp
$ cd ../@nethermindeth/warp
$ ls
total 68K
2654258 4.0K drwxr-xr-x 9 jmcph4 jmcph4 4.0K Nov 21 01:33 .
2654246 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:32 ..
2654378 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:32 bin
2654379 4.0K drwxr-xr-x 7 jmcph4 jmcph4 4.0K Nov 21 01:32 build
2629586 12K -rw-r--r-- 1 jmcph4 jmcph4 12K Nov 21 01:32 LICENSE
2654380 4.0K drwxr-xr-x 5 jmcph4 jmcph4 4.0K Nov 21 01:32 nethersolc
2654701 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:32 node_modules
2629597 4.0K -rw-r--r-- 1 jmcph4 jmcph4 2.9K Nov 21 01:32 package.json
2629599 12K -rw-r--r-- 1 jmcph4 jmcph4 8.8K Nov 21 01:32 readme.md
2654381 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:32 starknet-scripts
2654382 4.0K drwxr-xr-x 3 jmcph4 jmcph4 4.0K Nov 21 01:32 warplib
2654711 4.0K drwxr-xr-x 6 jmcph4 jmcph4 4.0K Nov 21 01:34 warp_venv
2629622 4.0K -rwxr-xr-x 1 jmcph4 jmcph4 225 Nov 21 01:32 warp_venv.sh
$ cd nethersolc
$ ls
total 20K
2654380 4.0K drwxr-xr-x 5 jmcph4 jmcph4 4.0K Nov 21 01:32 .
2654258 4.0K drwxr-xr-x 9 jmcph4 jmcph4 4.0K Nov 21 01:33 ..
2654581 4.0K drwxr-xr-x 4 jmcph4 jmcph4 4.0K Nov 21 01:32 darwin_arm64
2654582 4.0K drwxr-xr-x 4 jmcph4 jmcph4 4.0K Nov 21 01:32 darwin_x64
2654583 4.0K drwxr-xr-x 4 jmcph4 jmcph4 4.0K Nov 21 01:32 linux_x64
$ cd linux_x64
$ ls
total 16K
2654583 4.0K drwxr-xr-x 4 jmcph4 jmcph4 4.0K Nov 21 01:32 .
2654380 4.0K drwxr-xr-x 5 jmcph4 jmcph4 4.0K Nov 21 01:32 ..
2654658 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 01:32 7
2654659 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 03:54 8
$ cd 8
$ ls
total 16M
2654659 4.0K drwxr-xr-x 2 jmcph4 jmcph4 4.0K Nov 21 04:11 .
2654583 4.0K drwxr-xr-x 4 jmcph4 jmcph4 4.0K Nov 21 01:32 ..
2632815 16M -rwxr-xr-x 1 jmcph4 jmcph4 16M Nov 21 01:32 solc
They don't make it easy! Time for the ole' switcheroo:
$ rm solc
$ cp /path/to/our/desired/solc/binary/solc .
$ ./solc --version
Version: 0.8.10+commit.fc410830.Linux.g++
That was exhausting -- let's try and transpile again!
$ cd /path/to/zora/v3
$ warp transpile contracts/ZoraModuleManager.sol
---Compile Failed---
Compiler version 0.8.14 reported errors:
--1--
ParserError: Source "@openzeppelin/contracts/token/ERC721/ERC721.sol" not found: File not found.
--> contracts/auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol:4:1:
|
4 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Cannot start transpilation
Of course. In the fray, we've neglected the actual Zora dependencies. Let's just guess it's a Yarn thing given the syntax they've used.
$ yarn install
yarn install v1.22.10
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > @typechain/ethers-v5@10.1.0" has unmet peer dependency "@ethersproject/abi@^5.0.0".
warning " > @typechain/ethers-v5@10.1.0" has unmet peer dependency "@ethersproject/bytes@^5.0.0".
warning " > @typechain/ethers-v5@10.1.0" has unmet peer dependency "@ethersproject/providers@^5.0.0".
warning " > @typechain/ethers-v5@10.1.0" has unmet peer dependency "ethers@^5.1.3".
[4/4] Building fresh packages...
Done in 7.24s.
This alone is still insufficient, though. Because we're not using Foundry at all, we'll basically need to manually hack our own remapping feature. Not to worry:
$ ln -s node_modules/@openzeppelin @openzeppelin
$ warp transpile contracts/ZoraModuleManager.sol
---Compile Failed---
Compiler version 0.8.14 reported errors:
--1--
ParserError: Source "@openzeppelin/contracts/token/ERC721/ERC721.sol" not found: File not found.
--> contracts/auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol:4:1:
|
4 | import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Cannot start transpilation
jmcph4@dunnart:~/dev/tmp/v3$ ln -s node_modules/@openzeppelin @openzeppelin
jmcph4@dunnart:~/dev/tmp/v3$ warp transpile contracts/ZoraModuleManager.sol
Transpilation abandoned Detected 21 Unsupported Features:
File @openzeppelin/contracts/token/ERC721/ERC721.sol:
1. Conditional expressions (ternary operator, node) are not supported:
................
92 function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
93 require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
94
95 string memory baseURI = _baseURI();
96 return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
97 }
98
99 /**
2. Try/Catch statements are not supported:
................
390 uint256 tokenId,
391 bytes memory _data
392 ) private returns (bool) {
393 if (to.isContract()) {
394 try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
395 return retval == IERC721Receiver.onERC721Received.selector;
396 } catch (bytes memory reason) {
397 if (reason.length == 0) {
398 revert("ERC721: transfer to non ERC721Receiver implementer");
399 } else {
400 assembly {
401 revert(add(32, reason), mload(reason))
402 }
403 }
404 }
405 } else {
406 return true;
407 }
File @openzeppelin/contracts/token/ERC721/IERC721.sol:
3. Indexed parameters are not supported:
................
10 interface IERC721 is IERC165 {
11 /**
12 * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
13 */
14 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
15
16 /**
17 * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
4. Indexed parameters are not supported:
................
15
16 /**
17 * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
18 */
19 event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
20
21 /**
22 * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
5. Indexed parameters are not supported:
................
20
21 /**
22 * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
23 */
24 event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
25
26 /**
27 * @dev Returns the number of tokens in ``owner``'s account.
File @openzeppelin/contracts/utils/Address.sol:
6. Members of addresses are not supported. Found at MemberAccess #1783:
................
36 // This method relies on extcodesize/address.code.length, which returns 0
37 // for contracts in construction, since the code is only stored at the end
38 // of the constructor execution.
39
40 return account.code.length > 0;
41 }
42
43 /**
7. Members of addresses are not supported. Found at MemberAccess #1802:
................
56 * {ReentrancyGuard} or the
57 * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
58 */
59 function sendValue(address payable recipient, uint256 amount) internal {
60 require(address(this).balance >= amount, "Address: insufficient balance");
61
62 (bool success, ) = recipient.call{value: amount}("");
63 require(success, "Address: unable to send value, recipient may have reverted");
8. Function call options (other than `salt` when creating a contract), such as {gas:X} and {value:X} are not supported:
................
58 */
59 function sendValue(address payable recipient, uint256 amount) internal {
60 require(address(this).balance >= amount, "Address: insufficient balance");
61
62 (bool success, ) = recipient.call{value: amount}("");
63 require(success, "Address: unable to send value, recipient may have reverted");
64 }
65
9. Members of addresses are not supported. Found at MemberAccess #1899:
................
129 bytes memory data,
130 uint256 value,
131 string memory errorMessage
132 ) internal returns (bytes memory) {
133 require(address(this).balance >= value, "Address: insufficient balance for call");
134 require(isContract(target), "Address: call to non-contract");
135
136 (bool success, bytes memory returndata) = target.call{value: value}(data);
10. Function call options (other than `salt` when creating a contract), such as {gas:X} and {value:X} are not supported:
................
132 ) internal returns (bytes memory) {
133 require(address(this).balance >= value, "Address: insufficient balance for call");
134 require(isContract(target), "Address: call to non-contract");
135
136 (bool success, bytes memory returndata) = target.call{value: value}(data);
137 return verifyCallResult(success, returndata, errorMessage);
138 }
139
11. Members of addresses are not supported. Found at MemberAccess #1971:
................
159 string memory errorMessage
160 ) internal view returns (bytes memory) {
161 require(isContract(target), "Address: static call to non-contract");
162
163 (bool success, bytes memory returndata) = target.staticcall(data);
164 return verifyCallResult(success, returndata, errorMessage);
165 }
166
12. Members of addresses are not supported. Found at MemberAccess #2023:
................
186 string memory errorMessage
187 ) internal returns (bytes memory) {
188 require(isContract(target), "Address: delegate call to non-contract");
189
190 (bool success, bytes memory returndata) = target.delegatecall(data);
191 return verifyCallResult(success, returndata, errorMessage);
192 }
193
13. Yul blocks are not supported:
................
208 // Look for revert reason and bubble it up if present
209 if (returndata.length > 0) {
210 // The easiest way to bubble the revert reason is using memory via assembly
211
212 assembly {
213 let returndata_size := mload(returndata)
214 revert(add(32, returndata), returndata_size)
215 }
216 } else {
217 revert(errorMessage);
218 }
File @openzeppelin/contracts/utils/Context.sol:
14. msg object not supported outside of 'msg.sender':
................
17 return msg.sender;
18 }
19
20 function _msgData() internal view virtual returns (bytes calldata) {
21 return msg.data;
22 }
23 }
24
File contracts/ZoraModuleManager.sol:
15. Indexed parameters are not supported:
................
48 /// @notice Emitted when a user's module approval is updated
49 /// @param user The address of the user
50 /// @param module The address of the module
51 /// @param approved Whether the user added or removed approval
52 event ModuleApprovalSet(address indexed user, address indexed module, bool approved);
53
54 /// @notice Emitted when a module is registered
55 /// @param module The address of the module
16. Indexed parameters are not supported:
................
52 event ModuleApprovalSet(address indexed user, address indexed module, bool approved);
53
54 /// @notice Emitted when a module is registered
55 /// @param module The address of the module
56 event ModuleRegistered(address indexed module);
57
58 /// @notice Emitted when the registrar address is updated
59 /// @param newRegistrar The address of the new registrar
17. Indexed parameters are not supported:
................
56 event ModuleRegistered(address indexed module);
57
58 /// @notice Emitted when the registrar address is updated
59 /// @param newRegistrar The address of the new registrar
60 event RegistrarChanged(address indexed newRegistrar);
61
62 /// @param _registrar The initial registrar for the manager
63 /// @param _feeToken The module fee token contract to mint from upon module registration
18. Yul blocks are not supported:
................
299 }
300
301 /// @notice The EIP-155 chain id
302 function _chainID() private view returns (uint256 id) {
303 assembly {
304 id := chainid()
305 }
306 }
307 }
308
File contracts/auxiliary/ZoraProtocolFeeSettings/ZoraProtocolFeeSettings.sol:
19. Indexed parameters are not supported:
................
40 /// @notice Emitted when the fee for a module is updated
41 /// @param module The address of the module
42 /// @param feeRecipient The address of the fee recipient
43 /// @param feeBps The basis points of the fee
44 event ProtocolFeeUpdated(address indexed module, address feeRecipient, uint16 feeBps);
45
46 /// @notice Emitted when the contract metadata is updated
47 /// @param newMetadata The address of the new metadata
20. Indexed parameters are not supported:
................
44 event ProtocolFeeUpdated(address indexed module, address feeRecipient, uint16 feeBps);
45
46 /// @notice Emitted when the contract metadata is updated
47 /// @param newMetadata The address of the new metadata
48 event MetadataUpdated(address indexed newMetadata);
49
50 /// @notice Emitted when the contract owner is updated
51 /// @param newOwner The address of the new owner
21. Indexed parameters are not supported:
................
48 event MetadataUpdated(address indexed newMetadata);
49
50 /// @notice Emitted when the contract owner is updated
51 /// @param newOwner The address of the new owner
52 event OwnerUpdated(address indexed newOwner);
53
54 constructor() ERC721("ZORA Module Fee Switch", "ZORF") {
55 _setOwner(msg.sender);
Well, I guess we should have studied the Warp README more thoroughly. Turns out the Zora codebase uses some features Warp doesn't support yet.
After all of that, it looks like we'll have to do this ourselves the old fashioned way.