Skip to content

Commit

Permalink
tests: add univ3 svg generation test (#1292)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

<!-- Give an estimate of the time you spent on this PR in terms of work
days.
Did you spend 0.5 days on this PR or rather 2 days?  -->

Time spent on this PR: 1d

Adds Mock UniswapV3 contracts that allow querying a dynamically
generated on-chain SVG representing a position.
The values are mocked, and due to some issues, the base64 encoding of
the image crashes with a stack underflow error (tbd).

However, the encoding of the rest takes ~27M steps

Also added a way to do library linking in the python kakarot scripts 

## Pull request type

<!-- Please try to limit your pull request to one type,
submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying,
or link to a relevant issue. -->

Resolves #<Issue number>

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1292)
<!-- Reviewable:end -->
  • Loading branch information
enitrat authored Jul 25, 2024
1 parent e3469f8 commit a41fe3b
Show file tree
Hide file tree
Showing 18 changed files with 1,082 additions and 35 deletions.
10 changes: 9 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
[submodule "solidity_contracts/lib/forge-std"]
path = solidity_contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.3.0
[submodule "solidity_contracts/lib/kakarot-lib"]
path = solidity_contracts/lib/kakarot-lib
url = https://github.com/kkrt-labs/kakarot-lib
[submodule "solidity_contracts/lib/v3-core"]
path = solidity_contracts/lib/v3-core
url = https://github.com/Uniswap/v3-core
[submodule "solidity_contracts/lib/openzeppelin"]
path = solidity_contracts/lib/openzeppelin
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "solidity_contracts/lib/base64-sol"]
path = solidity_contracts/lib/base64-sol
url = https://github.com/Brechtpd/base64
4 changes: 2 additions & 2 deletions kakarot_scripts/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class NetworkType(Enum):
"l1_rpc_url": "http://127.0.0.1:8545",
"type": NetworkType.DEV,
"check_interval": 0.01,
"max_wait": 1,
"max_wait": 3,
},
"katana": {
"name": "katana",
Expand All @@ -64,7 +64,7 @@ class NetworkType(Enum):
"l1_rpc_url": "http://127.0.0.1:8545",
"type": NetworkType.DEV,
"check_interval": 0.01,
"max_wait": 2,
"max_wait": 3,
},
"madara": {
"name": "madara",
Expand Down
110 changes: 99 additions & 11 deletions kakarot_scripts/utils/kakarot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import logging
from pathlib import Path
from types import MethodType
from typing import List, Optional, Union, cast
from typing import Any, Dict, List, Optional, Tuple, Union, cast

import rlp
from async_lru import alru_cache
from eth_abi import decode
from eth_abi.exceptions import InsufficientDataBytes
from eth_account import Account as EvmAccount
Expand Down Expand Up @@ -72,6 +73,7 @@ def get_solidity_artifacts(
except (NameError, FileNotFoundError):
foundry_file = toml.loads(Path("foundry.toml").read_text())

src_path = Path(foundry_file["profile"]["default"]["src"])
all_compilation_outputs = [
json.load(open(file))
for file in Path(foundry_file["profile"]["default"]["out"]).glob(
Expand All @@ -82,9 +84,7 @@ def get_solidity_artifacts(
target_compilation_output = all_compilation_outputs[0]
else:
target_solidity_file_path = list(
(Path(foundry_file["profile"]["default"]["src"]) / contract_app).glob(
f"**/{contract_name}.sol"
)
(src_path / contract_app).glob(f"**/{contract_name}.sol")
)
if len(target_solidity_file_path) != 1:
raise ValueError(
Expand All @@ -105,31 +105,57 @@ def get_solidity_artifacts(
f"found {len(target_compilation_output)} outputs:\n{target_compilation_output}"
)
target_compilation_output = target_compilation_output[0]

def process_link_references(
link_references: Dict[str, Dict[str, Any]]
) -> Dict[str, Dict[str, Any]]:
return {
Path(file_path)
.relative_to(src_path)
.parts[0]: {
library_name: references
for library_name, references in libraries.items()
}
for file_path, libraries in link_references.items()
}

return {
"bytecode": target_compilation_output["bytecode"]["object"],
"bytecode_runtime": target_compilation_output["deployedBytecode"]["object"],
"bytecode": {
"object": target_compilation_output["bytecode"]["object"],
"linkReferences": process_link_references(
target_compilation_output["bytecode"].get("linkReferences", {})
),
},
"bytecode_runtime": {
"object": target_compilation_output["deployedBytecode"]["object"],
"linkReferences": process_link_references(
target_compilation_output["deployedBytecode"].get("linkReferences", {})
),
},
"abi": target_compilation_output["abi"],
"name": contract_name,
}


def get_contract(
async def get_contract(
contract_app: str,
contract_name: str,
address=None,
caller_eoa: Optional[Account] = None,
) -> Web3Contract:

artifacts = get_solidity_artifacts(contract_app, contract_name)

bytecode, bytecode_runtime = await link_libraries(artifacts)

contract = cast(
Web3Contract,
WEB3.eth.contract(
address=to_checksum_address(address) if address is not None else address,
abi=artifacts["abi"],
bytecode=artifacts["bytecode"],
bytecode=bytecode,
),
)
contract.bytecode_runtime = HexBytes(artifacts["bytecode_runtime"])
contract.bytecode_runtime = HexBytes(bytecode_runtime)

try:
for fun in contract.functions:
Expand All @@ -140,12 +166,74 @@ def get_contract(
return contract


@alru_cache()
async def get_or_deploy_library(library_app: str, library_name: str) -> str:
"""
Deploy a solidity library if not already deployed and return its address.
Args:
----
library_app (str): The application name of the library.
library_name (str): The name of the library.
Returns:
-------
str: The deployed library address as a hexstring with the '0x' prefix.
"""
library_contract = await deploy(library_app, library_name)
logger.info(f"ℹ️ Deployed {library_name} at address {library_contract.address}")
return library_contract.address


async def link_libraries(artifacts: Dict[str, Any]) -> Tuple[str, str]:
"""
Process an artifacts bytecode by linking libraries with their deployed addresses.
Args:
----
artifacts (Dict[str, Any]): The contract artifacts containing bytecode and link references.
Returns:
-------
Tuple[str, str]: The processed bytecode and runtime bytecode.
"""

async def process_bytecode(bytecode_type: str) -> str:
bytecode_obj = artifacts[bytecode_type]
current_bytecode = bytecode_obj["object"][2:]
link_references = bytecode_obj.get("linkReferences", {})

for library_app, libraries in link_references.items():
for library_name, references in libraries.items():
library_address = await get_or_deploy_library(library_app, library_name)

for ref in references:
start, length = ref["start"] * 2, ref["length"] * 2
placeholder = current_bytecode[start : start + length]
current_bytecode = current_bytecode.replace(
placeholder, library_address[2:].lower()
)

logger.info(
f"ℹ️ Replaced {library_name} in {bytecode_type} with address 0x{library_address}"
)

return current_bytecode

bytecode = await process_bytecode("bytecode")
bytecode_runtime = await process_bytecode("bytecode_runtime")

return bytecode, bytecode_runtime


async def deploy(
contract_app: str, contract_name: str, *args, **kwargs
) -> Web3Contract:
logger.info(f"⏳ Deploying {contract_name}")
caller_eoa = kwargs.pop("caller_eoa", None)
contract = get_contract(contract_app, contract_name, caller_eoa=caller_eoa)
contract = await get_contract(contract_app, contract_name, caller_eoa=caller_eoa)
max_fee = kwargs.pop("max_fee", None)
value = kwargs.pop("value", 0)
receipt, response, success, _ = await eth_send_transaction(
Expand Down
4 changes: 2 additions & 2 deletions kakarot_scripts/utils/l1.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ def get_l1_contract(
L1_RPC_PROVIDER.eth.contract(
address=to_checksum_address(address) if address is not None else address,
abi=artifacts["abi"],
bytecode=artifacts["bytecode"],
bytecode=artifacts["bytecode"]["object"],
),
)
contract.bytecode_runtime = HexBytes(artifacts["bytecode_runtime"])
contract.bytecode_runtime = HexBytes(artifacts["bytecode_runtime"]["object"])

try:
for fun in contract.functions:
Expand Down
1 change: 1 addition & 0 deletions solidity_contracts/lib/base64-sol
Submodule base64-sol added at dcbf85
1 change: 1 addition & 0 deletions solidity_contracts/lib/openzeppelin
Submodule openzeppelin added at 8e0296
1 change: 1 addition & 0 deletions solidity_contracts/lib/v3-core
Submodule v3-core added at e3589b
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ library UniswapV2Library {
hex"ff",
factory,
keccak256(abi.encodePacked(token0, token1)),
hex"666a5b78ea0b660c426b08cb5b7427447e909408067de1a5519c772ee9a3c032" // init code hash
hex"0f5b822a8dffa6ce589a2c240d78a6a2b38a51835a97ceab40c1f301e46ba30b" // init code hash
)
)
)
Expand Down
29 changes: 29 additions & 0 deletions solidity_contracts/src/UniswapV3/HexStrings.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.7.6;

library HexStrings {
bytes16 internal constant ALPHABET = "0123456789abcdef";

/// @notice Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
/// @dev Credit to Open Zeppelin under MIT license https://github.com/OpenZeppelin/openzeppelin-contracts/blob/243adff49ce1700e0ecb99fe522fb16cff1d1ddc/contracts/utils/Strings.sol#L55
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = ALPHABET[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}

function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length);
for (uint256 i = buffer.length; i > 0; i--) {
buffer[i - 1] = ALPHABET[value & 0xf];
value >>= 4;
}
return string(buffer);
}
}
Loading

0 comments on commit a41fe3b

Please sign in to comment.