Repository: 0xSpaceShard/starknet-devnet (language: python)

Issue:
Support of get_full_contract in forking mode
Use forking mode:
```
starknet-devnet --seed 0 --disable-rpc-request-validation --fork-network alpha-mainnet

Forking https://alpha-mainnet.starknet.io from block 38219
```

and try to fetch contracts from blocks before 38219

`curl https://alpha-mainnet.starknet.io/feeder_gateway/get_full_contract?blockNumber=pending&contractAddress=0x0419772c05f2db85b8c2b2f92e04ef4f66884270f4eb97718c8693f3b33492f5`
will work 

`curl http://127.0.0.1:5050/feeder_gateway/get_full_contract?blockNumber=pending&contractAddress=0x0419772c05f2db85b8c2b2f92e04ef4f66884270f4eb97718c8693f3b33492f5`
should work but it doesn't, we will need to fetch it from origin (alpha-mainnet in this case), also cache mechanism can be added to avoid multiple calls






Discussion / hints:

Fix patch:
diff --git a/starknet_devnet/blueprints/feeder_gateway.py b/starknet_devnet/blueprints/feeder_gateway.py
index 89dfd1e5c..e4bc3ada2 100644
--- a/starknet_devnet/blueprints/feeder_gateway.py
+++ b/starknet_devnet/blueprints/feeder_gateway.py
@@ -6,9 +6,6 @@
 
 from flask import Blueprint, Response, jsonify, request
 from marshmallow import ValidationError
-from starkware.starknet.services.api.contract_class.contract_class import (
-    DeprecatedCompiledClass,
-)
 from starkware.starknet.services.api.feeder_gateway.request_objects import (
     CallFunction,
     CallL1Handler,
@@ -209,10 +206,12 @@ async def get_full_contract():
         contract_address, block_id
     )
 
-    if isinstance(contract_class, DeprecatedCompiledClass):
-        contract_class = contract_class.remove_debug_info()
+    # strip debug_info if cairo 0 class
+    class_program = contract_class.get("program")
+    if class_program and class_program.get("debug_info"):
+        class_program["debug_info"] = None
 
-    return jsonify(contract_class.dump())
+    return jsonify(contract_class)
 
 
 @feeder_gateway.route("/get_class_hash_at", methods=["GET"])
diff --git a/starknet_devnet/blueprints/rpc/classes.py b/starknet_devnet/blueprints/rpc/classes.py
index c60c936a7..5dc4c8c8f 100644
--- a/starknet_devnet/blueprints/rpc/classes.py
+++ b/starknet_devnet/blueprints/rpc/classes.py
@@ -64,10 +64,11 @@ async def get_class_at(block_id: BlockId, contract_address: Address) -> dict:
     await assert_block_id_is_valid(block_id)
 
     try:
-        result = await state.starknet_wrapper.get_class_by_address(
+        result_dict = await state.starknet_wrapper.get_class_by_address(
             int(contract_address, 16), block_id
         )
+        contract_class = DeprecatedCompiledClass.load(result_dict)
     except StarkException as ex:
         raise RpcError.from_spec_name("CONTRACT_NOT_FOUND") from ex
 
-    return rpc_contract_class(result)
+    return rpc_contract_class(contract_class)
diff --git a/starknet_devnet/starknet_wrapper.py b/starknet_devnet/starknet_wrapper.py
index 38aa678d6..c7d884b3c 100644
--- a/starknet_devnet/starknet_wrapper.py
+++ b/starknet_devnet/starknet_wrapper.py
@@ -1,4 +1,3 @@
-# pylint: disable=too-many-lines
 """
 This module introduces `StarknetWrapper`, a wrapper class of
 starkware.starknet.testing.starknet.Starknet.
@@ -33,7 +32,6 @@
 from starkware.starknet.definitions.transaction_type import TransactionType
 from starkware.starknet.services.api.contract_class.contract_class import (
     CompiledClass,
-    CompiledClassBase,
     ContractClass,
     DeprecatedCompiledClass,
     EntryPointType,
@@ -730,23 +728,10 @@ async def get_class_hash_at(
 
     async def get_class_by_address(
         self, contract_address: int, block_id: BlockId = DEFAULT_BLOCK_ID
-    ) -> CompiledClassBase:
+    ) -> dict:
         """Return contract class given the contract address"""
-        state = await self.__get_query_state(block_id)
-        cached_state = state.state
-        class_hash = await self.get_class_hash_at(contract_address)
-
-        if class_hash in cached_state.contract_classes:
-            return cached_state.contract_classes[class_hash]
-
-        if class_hash in self._contract_classes:
-            return self._contract_classes[class_hash]
-
-        raise StarknetDevnetException(
-            code=StarkErrorCode.INVALID_CONTRACT_ADDRESS,
-            status_code=500,
-            message="Getting a full contract of a forked contract from previous blocks is not supported.",
-        )
+        class_hash = await self.get_class_hash_at(contract_address, block_id)
+        return await self.get_class_by_hash(class_hash)
 
     async def get_code(
         self, contract_address: int, block_id: BlockId = DEFAULT_BLOCK_ID
@@ -755,8 +740,8 @@ async def get_code(
         try:
             contract_class = await self.get_class_by_address(contract_address, block_id)
             result_dict = {
-                "abi": contract_class.abi,
-                "bytecode": contract_class.dump()["program"]["data"],
+                "abi": contract_class["abi"],
+                "bytecode": contract_class["program"]["data"],
             }
         except StarkException as err:
             if err.code != StarknetErrorCode.UNINITIALIZED_CONTRACT:


Test patch:
diff --git a/test/test_fork_feeder_gateway.py b/test/test_fork_feeder_gateway.py
index 004e7f9aa..235305df6 100644
--- a/test/test_fork_feeder_gateway.py
+++ b/test/test_fork_feeder_gateway.py
@@ -25,6 +25,7 @@
 )
 from .testnet_deployment import (
     TESTNET_CONTRACT_ADDRESS,
+    TESTNET_CONTRACT_CLASS_HASH,
     TESTNET_DEPLOYMENT_BLOCK,
     TESTNET_FORK_PARAMS,
     TESTNET_URL,
@@ -48,6 +49,7 @@
     deploy,
     devnet_in_background,
     get_block,
+    get_full_contract,
 )
 
 DEPLOYMENT_INPUT = "10"
@@ -84,8 +86,8 @@ def _make_expected_invoke(gateway_url=APP_URL):
     # starting from an earlier block; otherwise the contract class is already present
     str(TESTNET_DEPLOYMENT_BLOCK - 1),
 )
-def test_contract_responses():
-    """Assert that get_full_contract only makes sense on fork after deployment"""
+def test_contract_responses_before_deployment_on_origin():
+    """Assert that get_full_contract etc. only makes sense on fork after deployment"""
     # full contract
     assert_full_contract_not_present(
         address=EXPECTED_DEPLOYMENT_ADDRESS, feeder_gateway_url=TESTNET_URL
@@ -144,6 +146,7 @@ def test_contract_responses():
         address=EXPECTED_DEPLOYMENT_ADDRESS, feeder_gateway_url=APP_URL
     )
 
+    # class hash
     assert_address_has_no_class_hash(
         contract_address=EXPECTED_DEPLOYMENT_ADDRESS, feeder_gateway_url=TESTNET_URL
     )
@@ -168,6 +171,33 @@ def test_contract_responses():
     )
 
 
+@devnet_in_background(
+    *TESTNET_FORK_PARAMS, "--fork-block", str(TESTNET_DEPLOYMENT_BLOCK)
+)
+def test_contract_responses_after_deployment_on_origin():
+    """Assert get_full_contract etc. works for contract deployed earlier"""
+
+    full_contract_testnet = get_full_contract(
+        contract_address=TESTNET_CONTRACT_ADDRESS,
+        feeder_gateway_url=TESTNET_URL,
+    )
+    full_contract_devnet = get_full_contract(
+        contract_address=TESTNET_CONTRACT_ADDRESS,
+        feeder_gateway_url=APP_URL,
+    )
+    assert full_contract_testnet == full_contract_devnet
+
+    assert_contract_code_present(
+        address=TESTNET_CONTRACT_ADDRESS, feeder_gateway_url=APP_URL
+    )
+
+    assert_class_hash_at_address(
+        contract_address=TESTNET_CONTRACT_ADDRESS,
+        expected_class_hash=TESTNET_CONTRACT_CLASS_HASH,
+        feeder_gateway_url=APP_URL,
+    )
+
+
 @devnet_in_background(
     *TESTNET_FORK_PARAMS, "--fork-block", str(TESTNET_DEPLOYMENT_BLOCK - 1)
 )