Skip to content

Commit

Permalink
Merge pull request #53 from skalenetwork/skale-allocator
Browse files Browse the repository at this point in the history
Add skale-allocator project
  • Loading branch information
DimaStebaev committed Apr 4, 2024
2 parents a2b0ae3 + 691331e commit e200ce4
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 70 deletions.
2 changes: 2 additions & 0 deletions python/scripts/full_check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ echo "Run pylint"
pylint src
echo "Run mypy"
mypy --strict src
echo "Run flake8"
flake8 src
31 changes: 23 additions & 8 deletions python/src/skale_contracts/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from __future__ import annotations
from abc import ABC, abstractmethod
import json
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, cast
from attr import dataclass
from eth_typing import ChecksumAddress
from parver import Version as PyVersion
from semver.version import Version as SemVersion

Expand All @@ -24,14 +25,15 @@
"stateMutability": "view",
"payable": False,
"inputs": [],
"outputs": [ { "type": "string", "name": "" } ]
"outputs": [{"type": "string", "name": ""}]
}


@dataclass
class InstanceData:
"""Contains instance data"""
data: dict[str, str]

@classmethod
def from_json(cls, data: str) -> InstanceData:
"""Create InstanceData object from json string"""
Expand Down Expand Up @@ -79,20 +81,33 @@ def version(self) -> str:
def abi(self) -> SkaleAbi:
"""Get abi file of the project instance"""
if self._abi is None:
self._abi = json.loads(self._project.download_abi_file(self.version))
self._abi = json.loads(
self._project.download_abi_file(self.version)
)
return self._abi

@abstractmethod
def get_contract_address(self, name: str) -> Address:
def get_contract_address(
self,
name: str,
*args: str | Address | ChecksumAddress
) -> Address:
"""Get address of the contract by it's name"""

def get_contract(self, name: str) -> Contract:
def get_contract(
self,
name: str,
*args: str | Address | ChecksumAddress
) -> Contract:
"""Get Contract object of the contract by it's name"""
address = self.get_contract_address(name)
address = self.get_contract_address(name, *args)
return self.web3.eth.contract(address=address, abi=self.abi[name])

# protected

@abstractmethod
def _get_version(self) -> str:
pass
contract = self.web3.eth.contract(
address=self.address,
abi=[DEFAULT_GET_VERSION_FUNCTION]
)
return cast(str, contract.functions.version().call())
7 changes: 6 additions & 1 deletion python/src/skale_contracts/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class NetworkMetadata:
chain_id: int
path: str


@dataclass
class MetadataFile:
"""Represents file with metadata"""
Expand All @@ -33,6 +34,7 @@ def from_json(cls, data: str) -> MetadataFile:
path=network['path']))
return cls(networks)


class Metadata:
"""Class to manage SKALE contracts metadata"""
networks: list[NetworkMetadata]
Expand All @@ -49,7 +51,10 @@ def download(self) -> None:
metadata = MetadataFile.from_json(metadata_response.text)
self.networks = metadata.networks

def get_network_by_chain_id(self, chain_id: int) -> Optional[NetworkMetadata]:
def get_network_by_chain_id(
self,
chain_id: int
) -> Optional[NetworkMetadata]:
"""Get network metadata by it's chain id.
Returns None if there is no such network in the metadata.
"""
Expand Down
13 changes: 11 additions & 2 deletions python/src/skale_contracts/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@

class Network:
"""Represents blockchain with deployed smart contracts projects"""
def __init__(self, skale_contracts: SkaleContracts, provider: BaseProvider):
def __init__(
self,
skale_contracts: SkaleContracts,
provider: BaseProvider
):
self.web3 = Web3(provider)
self._skale_contracts = skale_contracts

Expand All @@ -39,7 +43,12 @@ def as_listed(self) -> ListedNetwork:

class ListedNetwork(Network):
"""Network that is listed in the metadata"""
def __init__(self, skale_contracts: SkaleContracts, provider: BaseProvider, path: str):
def __init__(
self,
skale_contracts: SkaleContracts,
provider: BaseProvider,
path: str
):
super().__init__(skale_contracts, provider)
self.path = path

Expand Down
19 changes: 14 additions & 5 deletions python/src/skale_contracts/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,30 @@
if TYPE_CHECKING:
from eth_typing import Address
from .network import Network
from .project_metadata import ProjectMetadata


class Project(ABC):
"""Represents set of smart contracts known as project"""

def __init__(self, network: Network, metadata: ProjectMetadata) -> None:
def __init__(self, network: Network) -> None:
super().__init__()
self.network = network
self._metadata = metadata

@staticmethod
@abstractmethod
def name() -> str:
"""Name of the project"""

@property
@abstractmethod
def github_repo(self) -> str:
"""URL of github repo with the project"""

@property
def folder(self) -> str:
"""Folder name with instances json files"""
return self.name()

def get_instance(self, alias_or_address: str) -> Instance:
"""Create instance object based on alias or address"""
if self.network.web3.is_address(alias_or_address):
Expand All @@ -53,7 +61,8 @@ def download_abi_file(self, version: str) -> str:

def get_abi_url(self, version: str) -> str:
"""Calculate URL of ABI file"""
return f'{self.github_repo}releases/download/{version}/{self.get_abi_filename(version)}'
filename = self.get_abi_filename(version)
return f'{self.github_repo}releases/download/{version}/{filename}'

@abstractmethod
def get_abi_filename(self, version: str) -> str:
Expand All @@ -63,7 +72,7 @@ def get_instance_data_url(self, alias: str) -> str:
"""Get URL of a file containing address for provided alias"""
if self.network.is_listed():
return f'{REPOSITORY_URL}{self.network.as_listed().path}/' + \
f'{self._metadata.path}/{alias}.json'
f'{self.folder}/{alias}.json'
raise ValueError('Network is unknown')

@abstractmethod
Expand Down
25 changes: 10 additions & 15 deletions python/src/skale_contracts/project_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,25 @@

from __future__ import annotations
from typing import TYPE_CHECKING
from attr import dataclass
import inspect

from .project import Project
from . import projects
from .project_metadata import ProjectMetadata

if TYPE_CHECKING:
from .project import Project
from .network import Network


@dataclass
class Projects:
"""Contains all known projects"""
skale_manager = ProjectMetadata(name='skale-manager', path='skale-manager')
mainnet_ima = ProjectMetadata(name='mainnet-ima', path='mainnet-ima')
schain_ima = ProjectMetadata(name='schain-ima', path='schain-ima')
projects_dict = {
project_type.name(): project_type
for _, project_type
in inspect.getmembers(projects, inspect.isclass)
if issubclass(project_type, Project)
}


def create_project(network: Network, name: str) -> Project:
"""Create Project object based on it's name"""
if name == Projects.skale_manager.name:
return projects.SkaleManager(network, Projects.skale_manager)
if name == Projects.mainnet_ima.name:
return projects.MainnetIma(network, Projects.mainnet_ima)
if name == Projects.schain_ima.name:
return projects.SchainIma(network, Projects.schain_ima)
if name in projects_dict:
return projects_dict[name](network)
raise ValueError(f'Project with name {name} is unknown')
4 changes: 0 additions & 4 deletions python/src/skale_contracts/project_metadata.py

This file was deleted.

3 changes: 2 additions & 1 deletion python/src/skale_contracts/projects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
MainnetImaProject as MainnetIma, \
SchainImaProject as SchainIma
from .skale_manager import SkaleManagerProject as SkaleManager
from .skale_allocator import SkaleAllocatorProject as SkaleAllocator

__all__ = ['MainnetIma', 'SchainIma', 'SkaleManager']
__all__ = ['MainnetIma', 'SchainIma', 'SkaleAllocator', 'SkaleManager']
74 changes: 52 additions & 22 deletions python/src/skale_contracts/projects/ima.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""Module connects IMA to the SKALE contracts library"""

from __future__ import annotations
from typing import cast, TYPE_CHECKING
from eth_typing import Address
from typing import TYPE_CHECKING
from eth_utils.address import to_canonical_address

from skale_contracts.constants import PREDEPLOYED_ALIAS
Expand All @@ -13,6 +12,7 @@


if TYPE_CHECKING:
from eth_typing import Address, ChecksumAddress
from web3.contract.contract import Contract

MESSAGE_PROXY_ABI = [
Expand All @@ -24,10 +24,10 @@ class ImaInstance(Instance):
"""Represents instance of IMA"""
def __init__(self, project: Project, address: Address) -> None:
super().__init__(project, address)
self.message_proxy = self.web3.eth.contract(address=address, abi=MESSAGE_PROXY_ABI)

def _get_version(self) -> str:
return cast(str, self.message_proxy.functions.version().call())
self.message_proxy = self.web3.eth.contract(
address=address,
abi=MESSAGE_PROXY_ABI
)


class ImaProject(Project):
Expand All @@ -45,24 +45,30 @@ def __init__(self, project: Project, address: Address) -> None:
super().__init__(project, address)
self._contract_manager: Contract | None = None

def get_contract_address(self, name: str) -> Address:
def get_contract_address(
self,
name: str, *args: str | Address | ChecksumAddress
) -> Address:
if name == 'MessageProxyForMainnet':
return self.address
if name == 'CommunityPool':
return to_canonical_address(
self.get_contract("MessageProxyForMainnet").functions.communityPool().call()
self.get_contract("MessageProxyForMainnet")
.functions.communityPool().call()
)
if name == 'Linker':
return to_canonical_address(
self.get_contract("MessageProxyForMainnet").functions.linker().call()
self.get_contract("MessageProxyForMainnet")
.functions.linker().call()
)
return to_canonical_address(
self.contract_manager.functions.getContract(name).call()
)

@property
def contract_manager(self) -> Contract:
"""ContractManager contract of a skale-manager instance associated with the IMA"""
"""ContractManager contract of a skale-manager instance
associated with the IMA"""
if self._contract_manager is None:
self._contract_manager = self.web3.eth.contract(
address=to_canonical_address(
Expand All @@ -77,6 +83,10 @@ def contract_manager(self) -> Contract:
class MainnetImaProject(ImaProject):
"""Represents mainnet part of IMA project"""

@staticmethod
def name() -> str:
return 'mainnet-ima'

def create_instance(self, address: Address) -> Instance:
return MainnetImaInstance(self, address)

Expand All @@ -89,19 +99,33 @@ class SchainImaInstance(ImaInstance):

PREDEPLOYED: dict[str, Address] = {
name: to_canonical_address(address) for name, address in {
'ProxyAdmin': '0xd2aAa00000000000000000000000000000000000',
'MessageProxyForSchain': '0xd2AAa00100000000000000000000000000000000',
'KeyStorage': '0xd2aaa00200000000000000000000000000000000',
'CommunityLocker': '0xD2aaa00300000000000000000000000000000000',
'TokenManagerEth': '0xd2AaA00400000000000000000000000000000000',
'TokenManagerERC20': '0xD2aAA00500000000000000000000000000000000',
'TokenManagerERC721': '0xD2aaa00600000000000000000000000000000000',
'TokenManagerLinker': '0xD2aAA00800000000000000000000000000000000',
'TokenManagerERC1155': '0xD2aaA00900000000000000000000000000000000',
'TokenManagerERC721WithMetadata': '0xd2AaA00a00000000000000000000000000000000'
'ProxyAdmin':
'0xd2aAa00000000000000000000000000000000000',
'MessageProxyForSchain':
'0xd2AAa00100000000000000000000000000000000',
'KeyStorage':
'0xd2aaa00200000000000000000000000000000000',
'CommunityLocker':
'0xD2aaa00300000000000000000000000000000000',
'TokenManagerEth':
'0xd2AaA00400000000000000000000000000000000',
'TokenManagerERC20':
'0xD2aAA00500000000000000000000000000000000',
'TokenManagerERC721':
'0xD2aaa00600000000000000000000000000000000',
'TokenManagerLinker':
'0xD2aAA00800000000000000000000000000000000',
'TokenManagerERC1155':
'0xD2aaA00900000000000000000000000000000000',
'TokenManagerERC721WithMetadata':
'0xd2AaA00a00000000000000000000000000000000'
}.items()}

def get_contract_address(self, name: str) -> Address:
def get_contract_address(
self,
name: str,
*args: str | Address | ChecksumAddress
) -> Address:
if name in self.PREDEPLOYED:
return self.PREDEPLOYED[name]
raise RuntimeError(f"Can't get address of {name} contract")
Expand All @@ -110,9 +134,15 @@ def get_contract_address(self, name: str) -> Address:
class SchainImaProject(ImaProject):
"""Represents schain part of IMA project"""

@staticmethod
def name() -> str:
return 'schain-ima'

def get_instance(self, alias_or_address: str) -> Instance:
if alias_or_address == PREDEPLOYED_ALIAS:
return self.create_instance(SchainImaInstance.PREDEPLOYED['MessageProxyForSchain'])
return self.create_instance(
SchainImaInstance.PREDEPLOYED['MessageProxyForSchain']
)
return super().get_instance(alias_or_address)

def create_instance(self, address: Address) -> Instance:
Expand Down
Loading

0 comments on commit e200ce4

Please sign in to comment.