Introducing UniStark: Uniswap, only Warp’ed to StarkNet

Jorik Schellekens
Nethermind.eth
Published in
7 min readOct 8, 2022

--

By Jorik Schellekens

Thanks to Guy McComb, Greg Vardy, Swapnil Raj, Carmen Irene Cabrera Rodriguez, Rodrigo Pino, Anneli Nowak, and Carla Segvic for helpful comments and revisions.

We are excited to announce that the next big milestone in the Warp project has been reached: we have successfully transpiled and compiled Uniswap v3! What’s more, is that we’re finishing a hardhat plugin that allows you to run all your Solidity hardhat tests on the transpiled Cairo:

Running Uniswap v3’s SqrtPriceMath tests against UniStark

In the video above, we’re using the original SqrtPriceMath.spec.ts file from the Uniswap v3 hardhat test suite. The hardhat Warp plugin automatically runs the tests on the transpiled Cairo. The plugin is still under development, but soon we will have Uniswap’s entire test suite running against a Uniswap implementation deployed on StarkNet.

From day one, Warp was designed to allow users to transpile existing Solidity codebases to Cairo and deploy them on StarkNet. With the recent advances on StarkNet, namely contract creation from other contracts, Warp is finally in a position to go after one of Solidity’s most complex projects: Uniswap v3.

Don’t take our word for it; go to our Uniswap fork, install the latest Warp and take it for a spin!

warp transpile --compile-cairo contracts/UniswapV3Pool.sol

Warp successfully transpiles and deploys every Solidity file in the Uniswap v3 repo with only minor changes to the source.

Much work has gone into correctly compiling a project of this size, but the effort pays off-net. With Warp rapidly maturing, the barrier to entry for large and small projects alike to test out the capabilities of StarkNet is lowering.

Warp is not about to stop with Uniswap! We will continue working hard on features and repeat this experiment with a few other protocols, bringing new protocols to StarkNet at warp speed. If you are interested in Warping your project to StarkNet, please reach out!

Attempting this transpilation has ironed out many kinks in Warp’s usage and helped us to prioritize new features. It’s also motivated us to integrate Warp with Hardhat — more on this later.

🎉 As a result of this effort, Warp now also supports the following:

  • abi.encode, abi.encodePacked - Caution! Addresses are packed in 32 bytes because StarkNet addresses are 251 bits
  • abi.decode - For now, only value types are supported

⚠️ Modifications to Uniswap’s Solidity Contracts

We made minor modifications to the Uniswap source to get it working. There are still a few Solidity features that Warp does not support. Certain unsupported features result from fundamental differences between Ethereum and StarkNet, which need a developer’s intervention to replace. For other features, support is being developed as we speak.

Before we dig deeper, here is a short list of the changes required in order of significance/difficulty (from most to least):

  1. Bump the Solidity version from 0.7.6 to 0.8.14
  2. Replace the inline assembly with equivalent Solidity or Inline Cairo
  3. Replace low-level calls with calls to the equivalent interface
  4. Remove indexed args of Events

We’ll take you through these changes, why they are needed, and discuss what Warp can do about them in the future.

Bumping the Solidity version

Uniswap’s contracts predominantly use 0.7.6, whereas Warp only fully supports Solidity 0.8.*. To get all the benefits of Warp, we changed the pragma on all the contracts to 0.8.14. Nevertheless, this is no light feat; the version change introduces some semantic changes, and these differences need to be accounted for.

For example, arithmetic in 0.7.* is unchecked, i.e., it can happily overflow or underflow silently. However, in 0.8.*, all arithmetic is checked, i.e., overflows and underflows will cause errors. To account for this, we needed to wrap any arithmetic in unchecked blocks.

There are some subtle differences in type conversions for operators, and there are changes in which conversions are allowed to be implicit. You will see some minor type conversion changes littered across the full diff of the repos as a result.

The contracts UniswapV3Pool.sol and TransferHelper.sol had low-level calls that were replaced with high-level cross-contract calls using interfaces.

Currently, there is work being done on supporting 0.7.*, although this is not prioritized for Warp at the moment.

Replacing inline assembly

Uniswap’s libraries: FullMath.sol, TickMath.sol, and UnsafeMath.sol, all use YUL assembly for gas efficiency. Warp does not support YUL assembly blocks since many assembly instructions make assumptions about the layout of memory and calldata in the EVM. These assumptions are not preserved in the Cairo transpilation; therefore, we’ve removed support for assembly.

In this case, replacing the YUL was a simple matter of rewriting the assembly in Solidity. For example, in contracts/libraries/FullMath.sol, the following assembly:

assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}

is replaced by:

unchecked {
prod0 = a * b;
uint256 mm = mulmod(a, b, 2**256 - 1);
uint256 prod1;
prod1 = mm - prod0;
if (mm < prod0) prod1 -= 1;
}

We replace the YUL block by block in FullMath to demonstrate how it is done, but a single simple Cairo function could have replaced the whole FullMath library.

Replacing highly optimized solidity code with simple hinted python versions

Replacing the assembly with Solidity no doubt affected the ‘gas efficiency’ of the contracts, but with StarkNet promising to slash gas costs this should not be an issue for a contract running on StarkNet.

Should you want features from YUL which are not expressible as simple Solidity arithmetic, Warp supports inline Cairo blocks:

contract WARP {
/// warp-cairo
/// from starkware.starknet.common.syscalls import library_call
/// DECORATOR(external)
/// DECORATOR(raw_input)
/// DECORATOR(raw_output)
/// func __default__{
/// syscall_ptr : felt*,
/// pedersen_ptr : HashBuiltin*,
/// range_check_ptr,
/// }(selector : felt, calldata_size : felt, calldata : felt*) -> (
/// retdata_size : felt, retdata : felt*
/// ){
/// alloc_locals;
/// let (class_hash_low) = WARP_STORAGE.read(STATEVAR(implementation_hash));
/// let (class_hash_high) = WARP_STORAGE.read(STATEVAR(implementation_hash) + 1);
/// let class_hash = class_hash_low + 2**128 * class_hash_high;
///
/// let (retdata_size : felt, retdata : felt*) = library_call(
/// class_hash=class_hash,
/// function_selector=selector,
/// calldata_size=calldata_size,
/// calldata=calldata,
/// );
/// return (retdata_size=retdata_size, retdata=retdata);
/// }
fallback() external {
}
}

After this effort, we have prioritized supporting an arithmetic subset of YUL.

Replacing low-level calls

Warp does not preserve the same calldata semantics as Solidity. As a result, low-level calls cannot be meaningfully transpiled. The solution is to bring back that semantic information and replace the call with a call to an appropriately bound interface.

Indexed Events

This change is easy. Warp doesn’t support indexed events yet, but nothing is stopping it from doing so. Support is coming very soon! In the meantime s/indexed//g

Ternaries

We are currently testing ternaries in Warp. We expect this to be released next week. In the interim we replace the ternaries in Uniswap v3 with their equivalent if statements.

Change summary

All in all, the contract modifications we made were straightforward and only required knowledge of Solidity. You can check out all the changes we made here.

Uniswap tests

Compiling and deploying the project is an essential first step, but we can’t stop there. Warp already has a large set of Semantic tests for solc’s repo, but this doesn’t tell us we modified our Solidity correctly! We want to bring Uniswap’s tests into play as well, and to that end, we’ve been carefully hacking the interface between ethers.js, Waffle, and Hardhat so that UniSwaps entire test suite runs out of the box. Our goal is to run Uniswap’s test with little to no changes, which means the inputs and outputs for the tests stay the same, the matchers stay the same, and the fixtures stay the same. To achieve this, we translate the inputs and outputs between the Solidity ABI and Cairo ABI automagically:

Transaction flow with Warp’s modified hardhat

We have already started the testing using the above strategy. We will release the results in the coming days with step counts (StarkNet’s equivalent measure of computation) and other benchmarking data for analysis.

What’s next?

Once the test suite is completed, we will release a blog post detailing the test outcomes, the benchmarks, and a UniStark testnet deployment address. We will include implementation details of the Hardhat integration along with instructions for replicating this on your own project. There are more technical blogs to follow explaining how Warp handles some of the more complex transpilation challenges, such as modifiers, inheritance, and memory/storage copy semantics. Follow us on Twitter to make sure you don’t miss it!

We have many cool projects at Nethermind, and we’re always looking for passionate people to join us. Our work touches every part of the Web3 ecosystem, from our Nethermind node to fundamental cryptography research and application-layer protocol development. If you’re interested in compilers, then the Warp team is interested in you. Check out our job board: https://nethermind.io/company/.

Kindly note, this is a Nethermind project carried out with no affiliation to Uniswap. All of the development and testing described in the article has taken place in accordance with the Uniswap Business Source Licence 1.1. and other applicable licenses. Nethermind does not use Uniswap v3 Core for revenue-generating purposes.

--

--