Compare commits

..

5 Commits

Author SHA1 Message Date
cadaa582f6 fix: ussd help menu
correct return/exit codes are 00, 99
2022-01-08 13:14:58 +03:00
cb61e45e4c Merge branch 'philip/ussd-data-files' into 'master'
Move cic-ussd datafiles to apps/cic-ussd/cic_ussd/data/

See merge request grassrootseconomics/cic-internal-integration!325
2022-01-07 10:50:49 +00:00
e5b06b18d7 Move cic-ussd datafiles to apps/cic-ussd/cic_ussd/data/ 2022-01-07 10:50:49 +00:00
William Luke
5d1a30021a Merge branch 'fix/docs-link' into 'master'
fix: link to docs

See merge request grassrootseconomics/cic-internal-integration!324
2022-01-05 12:22:15 +00:00
William Luke
ade3f4e917 fix: link to docs 2022-01-05 15:19:11 +03:00
39 changed files with 43 additions and 919 deletions

1
.gitignore vendored
View File

@@ -15,4 +15,3 @@ build/
.idea .idea
**/.vim **/.vim
**/*secret.yaml **/*secret.yaml
.envrc

View File

@@ -15,5 +15,5 @@ To get started see [./apps/contract-migration/README.md](./apps/contract-migrati
## Documentation ## Documentation
[https://docs.grassecon.org/cic_stack/](https://docs.grassecon.org/cic_stack/) [https://docs.grassecon.org/software/](https://docs.grassecon.org/software/)

View File

@@ -1,36 +1 @@
# CIC-ETH # CIC-ETH
## Testing CIC-ETH locally.
### Setup a Virtual Env
```bash
python3 -m venv ./venv # Python 3.9
source ./venv/activate
```
### Running All Unit Tests
```bash
bash ./tests/run_tests.sh # This will also install required dependencies
```
### Running Specific Unit Tests
Ensure that:
- You have called `bash ./tests/run_tests.sh` at least once or run the following to install required dependencies
- You have activated the virtual environment
```
pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple \
-r admin_requirements.txt \
-r services_requirements.txt \
-r test_requirements.txt
```
Then here is an example that only runs tests with the keyword(-k) `test_server`
```bash
pytest -s -v --log-cli-level DEBUG --log-level DEBUG -k test_server
```

View File

@@ -8,6 +8,9 @@ import logging
# external imports # external imports
import celery import celery
from chainlib.chain import ChainSpec
from hexathon import strip_0x
# local imports # local imports
from cic_eth.api.base import ApiBase from cic_eth.api.base import ApiBase
from cic_eth.enum import LockEnum from cic_eth.enum import LockEnum

View File

@@ -1,12 +0,0 @@
# standard imports
import os
import random
import uuid
def blockchain_address() -> str:
return os.urandom(20).hex().lower()

View File

@@ -1,132 +0,0 @@
# standard imports
import os
# external imports
import pytest
from celery import uuid
# test imports
from cic_eth.pytest.helpers.accounts import blockchain_address
@pytest.fixture(scope='function')
def task_uuid():
return uuid()
@pytest.fixture(scope='function')
def default_token_data(foo_token_symbol, foo_token):
return {
'symbol': foo_token_symbol,
'address': foo_token,
'name': 'Giftable Token',
'decimals': 6,
"converters": []
}
@pytest.fixture(scope='function')
def mock_account_creation_task_request(mocker, task_uuid):
def mock_request(self):
mocked_task_request = mocker.patch('celery.app.task.Task.request')
mocked_task_request.id = task_uuid
return mocked_task_request
mocker.patch('cic_eth.api.api_task.Api.create_account', mock_request)
@pytest.fixture(scope='function')
def mock_account_creation_task_result(mocker, task_uuid):
def task_result(self):
sync_res = mocker.patch('celery.result.AsyncResult')
sync_res.id = task_uuid
sync_res.get.return_value = blockchain_address()
return sync_res
mocker.patch('cic_eth.api.api_task.Api.create_account', task_result)
@pytest.fixture(scope='function')
def mock_token_api_query(foo_token_symbol, foo_token, mocker, task_uuid):
def mock_query(self, token_symbol, proof=None):
sync_res = mocker.patch('celery.result.AsyncResult')
sync_res.id = task_uuid
sync_res.get.return_value = [
{
'address': foo_token,
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'symbol': foo_token_symbol,
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
]
return sync_res
mocker.patch('cic_eth.api.api_task.Api.token', mock_query)
@pytest.fixture(scope='function')
def mock_tokens_api_query(foo_token_symbol, foo_token, mocker, task_uuid):
def mock_query(self, token_symbol, proof=None):
sync_res = mocker.patch('celery.result.AsyncResult')
sync_res.id = task_uuid
sync_res.get.return_value = [[
{
'address': foo_token,
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'symbol': foo_token_symbol,
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
], [
{
'address': foo_token,
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'symbol': foo_token_symbol,
},{'5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C','Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}
]]
return sync_res
mocker.patch('cic_eth.api.api_task.Api.tokens', mock_query)
@pytest.fixture(scope='function')
def mock_sync_balance_api_query(balances, mocker, task_uuid):
def sync_api_query(self, address: str, token_symbol: str):
sync_res = mocker.patch('celery.result.AsyncResult')
sync_res.id = task_uuid
sync_res.get.return_value = balances
return sync_res
mocker.patch('cic_eth.api.api_task.Api.balance', sync_api_query)
@pytest.fixture(scope='function')
def mock_sync_default_token_api_query(default_token_data, mocker, task_uuid):
def mock_query(self):
sync_res = mocker.patch('celery.result.AsyncResult')
sync_res.id = task_uuid
sync_res.get.return_value = default_token_data
return sync_res
mocker.patch('cic_eth.api.api_task.Api.default_token', mock_query)
@pytest.fixture(scope='function')
def mock_transaction_list_query(mocker):
query_args = {}
def mock_query(self, address: str, limit: int):
query_args['address'] = address
query_args['limit'] = limit
mocker.patch('cic_eth.api.api_task.Api.list', mock_query)
return query_args
@pytest.fixture(scope='function')
def mock_transfer_api(mocker):
transfer_args = {}
def mock_transfer(self, from_address: str, to_address: str, value: int, token_symbol: str):
transfer_args['from_address'] = from_address
transfer_args['to_address'] = to_address
transfer_args['value'] = value
transfer_args['token_symbol'] = token_symbol
mocker.patch('cic_eth.api.api_task.Api.transfer', mock_transfer)
return transfer_args

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python #!/usr/bin/python
import sys import sys
import os
import logging import logging
import uuid import uuid
import json import json
import argparse
# external imports # external imports
import redis import redis
from xdg.BaseDirectory import xdg_config_home
from chainlib.chain import ChainSpec
# local imports # local imports
import cic_eth.cli import cic_eth.cli

View File

@@ -1,38 +0,0 @@
import logging
import cic_eth.cli
from cic_eth.server.app import create_app
from cic_eth.server.celery import create_celery_wrapper
arg_flags = cic_eth.cli.argflag_std_base
local_arg_flags = cic_eth.cli.argflag_local_taskcallback
argparser = cic_eth.cli.ArgumentParser(arg_flags)
argparser.process_local_flags(local_arg_flags)
args = argparser.parse_args()
config = cic_eth.cli.Config.from_args(args, arg_flags, local_arg_flags)
# Define log levels
if args.vv:
logging.getLogger().setLevel(logging.DEBUG)
elif args.v:
logging.getLogger().setLevel(logging.INFO)
# Setup Celery App
celery_app = cic_eth.cli.CeleryApp.from_config(config)
celery_app.set_default()
chain_spec = config.get('CHAIN_SPEC')
celery_queue = config.get('CELERY_QUEUE')
redis_host = config.get('REDIS_HOST')
redis_port = config.get('REDIS_PORT')
redis_db = config.get('REDIS_DB')
redis_timeout = config.get('REDIS_TIMEOUT')
celery_wrapper = create_celery_wrapper(celery_queue=celery_queue, chain_spec=chain_spec,
redis_db=redis_db, redis_host=redis_host, redis_port=redis_port, redis_timeout=redis_timeout)
app = create_app(celery_wrapper)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=5000, log_level="info")

View File

@@ -1,3 +0,0 @@
from . import converters
from . import cache
from . import celery

View File

@@ -1,119 +0,0 @@
import logging
import sys
from typing import List, Optional, Union
from cic_eth.server import cache, converters
from cic_eth.server.cache import setup_cache
from cic_eth.server.celery import create_celery_wrapper
from cic_eth.server.models import (DefaultToken, Token, TokenBalance,
Transaction)
from fastapi import FastAPI, Query
log = logging.getLogger(__name__)
def create_app(celery_wrapper):
app = FastAPI(debug=True,
title="Grassroots Economics",
description="CIC ETH API",
version="0.0.1",
terms_of_service="https://www.grassrootseconomics.org/pages/terms-and-conditions.html",
contact={
"name": "Grassroots Economics",
"url": "https://www.grassrootseconomics.org",
"email": "will@grassecon.org"
},
license_info={
"name": "GPLv3",
})
@app.get("/transactions", response_model=List[Transaction])
def transactions(address: str, limit: Optional[str] = 10):
return celery_wrapper('list', address, limit=limit)
@app.get("/balance", response_model=List[TokenBalance])
def balance(token_symbol: str, address: str = Query(..., title="Address", min_length=40, max_length=42), include_pending: bool = True):
log.info(f"address: {address}")
log.info(f"token_symbol: {token_symbol}")
data = celery_wrapper('balance', address, token_symbol,
include_pending=include_pending)
for b in data:
token = get_token(token_symbol)
b['balance_network'] = converters.from_wei(
token.decimals, int(b['balance_network']))
b['balance_incoming'] = converters.from_wei(
token.decimals, int(b['balance_incoming']))
b['balance_outgoing'] = converters.from_wei(
token.decimals, int(b['balance_outgoing']))
b.update({
"balance_available": int(b['balance_network']) + int(b['balance_incoming']) - int(b['balance_outgoing'])
})
return data
@app.post("/create_account")
def create_account(password: Optional[str] = None, register: bool = True):
data = celery_wrapper(
'create_account', password=password, register=register)
return data
# def refill_gas(start_response, query: dict):
# address = query.pop('address')
# data = celery_wrapper('refill_gas', address)
# return data
# def ping(start_response, query: dict):
# data = celery_wrapper('ping', **query)
# return data
@app.post("/transfer")
def transfer(from_address: str, to_address: str, value: int, token_symbol: str):
token = get_token(
token_symbol)
wei_value = converters.to_wei(token.decimals, int(value))
data = celery_wrapper('transfer', from_address,
to_address, wei_value, token_symbol)
return data
@app.post("/transfer_from")
def transfer_from(from_address: str, to_address: str, value: int, token_symbol: str, spender_address: str):
token = get_token(
token_symbol)
wei_value = converters.to_wei(token.decimals, int(value))
data = celery_wrapper('transfer_from', from_address, to_address,
wei_value, token_symbol, spender_address)
return data
@app.get("/token", response_model=Token)
def token(token_symbol: str, proof: Optional[str] = None):
token = get_token(token_symbol)
if token == None:
sys.stderr.write(f"Cached Token {token_symbol} not found")
data = celery_wrapper('token', token_symbol, proof=proof)
token = Token.new(data)
sys.stderr.write(f"Token {token}")
return token
@app.get("/tokens", response_model=List[Token])
def tokens(token_symbols: Optional[List[str]] = Query(None), proof: Optional[Union[str, List[str], List[List[str]]]] = None):
data = celery_wrapper('tokens', token_symbols,
catch=len(token_symbols), proof=proof)
if data:
tokens = []
for token in data:
print(f"Token: {token}")
tokens.append(Token.new(token))
return tokens
return None
@app.get("/default_token", response_model=DefaultToken)
def default_token():
data = celery_wrapper('default_token')
return data
def get_token(token_symbol: str):
data = celery_wrapper('token', token_symbol)
return Token.new(data)
return app

View File

@@ -1,130 +0,0 @@
# standard imports
import hashlib
import json
import logging
from typing import Optional, Union
from cic_eth.server.models import Token
from cic_types.condiments import MetadataPointer
from redis import Redis, StrictRedis
logg = logging.getLogger(__file__)
class Cache:
store: Redis = None
def setup_cache(redis_host, redis_port, redis_db):
# Define universal redis cache access
Cache.store = StrictRedis(
host=redis_host, port=redis_port, db=redis_db, decode_responses=True)
def get_token_data(token_symbol: str):
"""
:param token_symbol:
:type token_symbol:
:return:
:rtype:
"""
identifier = [token_symbol.encode('utf-8')]
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
logg.debug(f'Retrieving token data for: {token_symbol} at: {key}')
token_data_str = get_cached_data(key=key)
if(token_data_str is None):
logg.debug(f'No token data found for: {token_symbol}')
return None
else:
token_data = json.loads(token_data_str)
logg.debug(f'Retrieved token data: {token_data}')
return token_data
def set_token_data(token_symbol: str, token: dict):
"""
:param token_symbol:
:type token_symbol:
:return:
:rtype:
"""
identifier = [token_symbol.encode('utf-8')]
key = cache_data_key(identifier, MetadataPointer.TOKEN_DATA)
cache_data(key, json.dumps(token))
logg.debug(f'Cached token data for: {token_symbol} at: {key}')
def get_default_token() -> Optional[str]:
"""This function attempts to retrieve the default token's data from the redis cache.
:return:
:rtype:
"""
logg.debug(f'Retrieving default token from cache')
# TODO: What should the identifier be?
key = cache_data_key(identifier="ff".encode('utf-8'),
salt=MetadataPointer.TOKEN_DEFAULT)
default_token_str = get_cached_data(key=key)
if default_token_str is None:
logg.debug(f'No cached default token found: {key}')
return None
default_token = json.loads(default_token_str)
logg.debug(f'Retrieved default token data: {default_token}')
return default_token
def set_default_token(default_token: dict):
"""
:param default_token:
:type default_token:
:return:
:rtype:
"""
logg.debug(f'Setting default token in cache')
key = cache_data_key(identifier="ff".encode('utf-8'),
salt=MetadataPointer.TOKEN_DEFAULT)
cache_data(key, json.dumps(default_token))
def cache_data(key: str, data: str):
"""
:param key:
:type key:
:param data:
:type data:
:return:
:rtype:
"""
cache = Cache.store
cache.set(name=key, value=data)
cache.persist(name=key)
logg.debug(f'caching: {data} with key: {key}.')
def get_cached_data(key: str):
"""
:param key:
:type key:
:return:
:rtype:
"""
cache = Cache.store
return cache.get(name=key)
def cache_data_key(identifier: Union[list, bytes], salt: MetadataPointer):
"""
:param identifier:
:type identifier:
:param salt:
:type salt:
:return:
:rtype:
"""
hash_object = hashlib.new("sha256")
if isinstance(identifier, list):
for identity in identifier:
hash_object.update(identity)
else:
hash_object.update(identifier)
hash_object.update(salt.value.encode(encoding="utf-8"))
return hash_object.digest().hex()

View File

@@ -1,64 +0,0 @@
import json
import logging
import sys
import uuid
import redis
from cic_eth.api.api_task import Api
log = logging.getLogger(__name__)
def create_celery_wrapper(chain_spec,
celery_queue,
redis_host,
redis_port,
redis_db,
redis_timeout):
def call(method, *args, catch=1, **kwargs):
""" Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning.
"""
log.debug(f"Using redis: {redis_host}, {redis_port}, {redis_db}")
redis_channel = str(uuid.uuid4())
r = redis.Redis(redis_host, redis_port, redis_db)
ps = r.pubsub()
ps.subscribe(redis_channel)
api = Api(
chain_spec,
queue=celery_queue,
callback_param='{}:{}:{}:{}'.format(
redis_host, redis_port, redis_db, redis_channel),
callback_task='cic_eth.callbacks.redis.redis',
callback_queue=celery_queue,
)
getattr(api, method)(*args, **kwargs)
ps.get_message()
try:
data = []
if catch == 1:
message = ps.get_message(timeout=redis_timeout)
data = json.loads(message['data'])["result"]
raise data
else:
for _i in range(catch):
message = ps.get_message(
timeout=redis_timeout)
result = json.loads(message['data'])["result"]
data.append(result)
except TimeoutError as e:
sys.stderr.write(
f"cic_eth.api.{method}({args}, {kwargs}) timed out:\n {e}")
raise e
except Exception as e:
sys.stderr.write(
f'Unable to parse Data:\n{data}\n Error:\n{e}')
raise e
log.debug(
f"cic_eth.api.{method}(args={args}, kwargs={kwargs})\n {data}")
ps.unsubscribe()
return data
return call

View File

@@ -1,39 +0,0 @@
# Stolen from ussd
from math import trunc
def from_wei(decimals: int, value: int) -> float:
"""This function converts values in Wei to a token in the cic network.
:param decimals: The decimals required for wei values.
:type decimals: int
:param value: Value in Wei
:type value: int
:return: SRF equivalent of value in Wei
:rtype: float
"""
value = float(value) / (10**decimals)
return truncate(value=value, decimals=2)
def to_wei(decimals: int, value: int) -> int:
"""This functions converts values from a token in the cic network to Wei.
:param decimals: The decimals required for wei values.
:type decimals: int
:param value: Value in SRF
:type value: int
:return: Wei equivalent of value in SRF
:rtype: int
"""
return int(value * (10**decimals))
def truncate(value: float, decimals: int) -> float:
"""This function truncates a value to a specified number of decimals places.
:param value: The value to be truncated.
:type value: float
:param decimals: The number of decimals for the value to be truncated to
:type decimals: int
:return: The truncated value.
:rtype: int
"""
stepper = 10.0**decimals
return trunc(stepper*value) / stepper

View File

@@ -1,92 +0,0 @@
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel, Field
class Transaction(BaseModel):
block_number: Optional[int] = Field(None, example=24531)
date_checked: Optional[str] = Field(
None, example='2021-11-12T09:36:40.725296')
date_created: Optional[str] = Field(
None, example='2021-11-12T09:36:40.131292')
date_updated: Optional[str] = Field(
None, example='2021-11-12T09:36:40.131292')
destination_token: Optional[str] = Field(
None, example=365185044137427460620354810422988491181438940190
)
destination_token_decimals: Optional[int] = Field(None, example=6)
destination_token_symbol: Optional[str] = Field(None, example='COFE')
from_value: Optional[int] = Field(None, example=100000000)
hash: Optional[str] = Field(
None,
example=90380195350511178677041624165156640995490505896556680958001954705731707874291,
)
nonce: Optional[int] = Field(None, example=1)
recipient: Optional[str] = Field(
None, example='872e1ec9d499b242ebfcfd0a279a4c3e0cd472c0'
)
sender: Optional[str] = Field(
None, example='1a92b05e0b880127a4c26ac0f68a52df3ac6b89d'
)
signed_tx: Optional[str] = Field(
None,
example=1601943273486236942256143665779318355236220334071247753507187634376562549990085710958441113013370129915441072693447256942510246386178938683325073160349857879326297351587330623503997011254644396580777843154770873208185332563272343361515226115860084201932230246018679661802320007832375955345977725551120479084062615799940692628221555193198194825737613358738414884130187144700126061702642574663703095161159219410608270,
)
source_token: Optional[str] = Field(
None, example=365185044137427460620354810422988491181438940190
)
source_token_decimals: Optional[int] = Field(None, example=6)
source_token_symbol: Optional[str] = Field(None, example='COFE')
status: Optional[str] = Field(None, example='SUCCESS')
status_code: Optional[int] = Field(None, example=4104)
timestamp: Optional[int] = Field(None, example=1636709800)
to_value: Optional[int] = Field(None, example=100000000)
tx_hash: Optional[str] = Field(
None,
example=90380195350511178677041624165156640995490505896556680958001954705731707874291,
)
tx_index: Optional[int] = Field(None, example=0)
class DefaultToken(BaseModel):
symbol: Optional[str] = Field(None, description='Token Symbol')
address: Optional[str] = Field(None, description='Token Address')
name: Optional[str] = Field(None, description='Token Name')
decimals: Optional[int] = Field(None, description='Decimals')
class TokenBalance(BaseModel):
address: Optional[str] = None
converters: Optional[List[str]] = None
balance_network: Optional[int] = None
balance_incoming: Optional[int] = None
balance_outgoing: Optional[int] = None
balance_available: Optional[int] = None
class Token(BaseModel):
decimals: Optional[int] = None
name: Optional[str] = None
address: Optional[str] = None
symbol: Optional[str] = None
proofs: Optional[List[str]] = None
converters: Optional[List[str]] = None
proofs_with_signers: Optional[List[Proof]] = None
@staticmethod
def new(data: List[dict]) -> Token:
proofs_with_signers = [{"proof": proof, "signers": signers}
for (proof, signers) in data[1].items()]
return Token(**data[0],
proofs_with_signers=proofs_with_signers,
)
class Proof(BaseModel):
proof: Optional[str] = None
signers: Optional[List[str]] = None
Token.update_forward_refs()

View File

@@ -1,5 +0,0 @@
[redis]
host=redis
database=0
password=
port=6379

View File

@@ -5,5 +5,3 @@ urlybird~=0.0.1
cic-eth-registry~=0.6.6 cic-eth-registry~=0.6.6
cic-types~=0.2.1a8 cic-types~=0.2.1a8
cic-eth-aux-erc20-demurrage-token~=0.0.3 cic-eth-aux-erc20-demurrage-token~=0.0.3
fastapi[all]==0.70.1
uvicorn[standard]<0.16.0

View File

@@ -36,7 +36,6 @@ packages =
cic_eth.db.models cic_eth.db.models
cic_eth.queue cic_eth.queue
cic_eth.ext cic_eth.ext
cic_eth.server
cic_eth.runnable cic_eth.runnable
cic_eth.runnable.daemons cic_eth.runnable.daemons
cic_eth.runnable.daemons.filters cic_eth.runnable.daemons.filters

View File

@@ -24,8 +24,6 @@ from cic_eth.pytest.fixtures_database import *
from cic_eth.pytest.fixtures_role import * from cic_eth.pytest.fixtures_role import *
from cic_eth.pytest.fixtures_contract import * from cic_eth.pytest.fixtures_contract import *
from cic_eth.pytest.fixtures_token import * from cic_eth.pytest.fixtures_token import *
from cic_eth.pytest.patches.account import *
from chainlib.eth.pytest import * from chainlib.eth.pytest import *
from eth_contract_registry.pytest import * from eth_contract_registry.pytest import *
from cic_eth_registry.pytest.fixtures_contracts import * from cic_eth_registry.pytest.fixtures_contracts import *

View File

@@ -2,9 +2,9 @@
set -e set -e
pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple \ pip install --extra-index-url https://pip.grassrootseconomics.net --extra-index-url https://gitlab.com/api/v4/projects/27624814/packages/pypi/simple
-r admin_requirements.txt \ -r admin_requirements.txt
-r services_requirements.txt \ -r services_requirements.txt
-r test_requirements.txt -r test_requirements.txt
export PYTHONPATH=. && pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests export PYTHONPATH=. && pytest -x --cov=cic_eth --cov-fail-under=90 --cov-report term-missing tests

View File

@@ -1,171 +0,0 @@
# coding: utf-8
from __future__ import absolute_import
import logging
import time
import hexathon
import pytest
from cic_eth.server.app import create_app
from cic_eth.server.celery import create_celery_wrapper
from fastapi.testclient import TestClient
log = logging.getLogger(__name__)
@pytest.fixture(scope='function')
def celery_wrapper(api):
""" Creates a redis channel and calls `cic_eth.api` with the provided `method` and `*args`. Returns the result of the api call. Catch allows you to specify how many messages to catch before returning.
"""
def wrapper(method, *args, catch=1, **kwargs):
t = getattr(api, method)(*args, **kwargs)
return t.get()
return wrapper
@pytest.fixture(scope='function')
def client(celery_wrapper):
app = create_app(celery_wrapper)
return TestClient(app)
def test_default_token(client,mock_sync_default_token_api_query):
# Default Token
response = client.get('/default_token')
log.debug(f"balance response {response}")
default_token = response.json()
assert default_token == {'symbol': 'FOO', 'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385', 'name': 'Giftable Token', 'decimals': 6}
def test_token(client, mock_token_api_query):
# Default Token
response = client.get('/token?token_symbol=FOO')
log.debug(f"token response {response}")
token = response.json()
assert token == {
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
'symbol': 'FOO',
}
def test_tokens(client, mock_tokens_api_query):
# Default Token
response = client.get(
'/tokens', params={'token_symbols': ['FOO', 'FOO']})
log.debug(f"tokens response {response}")
tokens = response.json()
assert tokens == [
{
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
'symbol': 'FOO',
},
{
'address': '0xe7c559c40B297d7f039767A2c3677E20B24F1385',
'converters': [],
'decimals': 6,
'name': 'Giftable Token',
'proofs': ['5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3'],
'proofs_with_signers': [{'proof': '5b1549818725ca07c19fc47fda5d8d85bbebb1283855d5ab99785dcb7d9051d3',
'signers': ['Eb3907eCad74a0013c259D5874AE7f22DcBcC95C', 'Eb3907eCad74a0013c259D5874AE7f22DcBcC95C']}],
'symbol': 'FOO',
},
]
@pytest.mark.skip("Not implemented")
def test_account(client):
# Default Token
response = client.get('/default_token')
log.debug(f"balance response {response}")
default_token = response.json()
# Create Account 1
params = {
'password': '',
'register': True
}
response = client.post(
'/create_account',
params=params)
address_1 = hexathon.valid(response.json())
# Create Account 2
params = {
'password': '',
'register': True
}
response = client.post('/create_account',
params=params)
address_2 = hexathon.valid(response.json())
time.sleep(30) # Required to allow balance to show
# Balance Account 1
params = {
'address': address_1,
'token_symbol': 'COFE',
'include_pending': True
}
response = client.get('/balance',
params=params)
balance = response.json()
assert (balance[0] == {
"address": default_token.get('address').lower(),
"balance_available": 30000,
"balance_incoming": 0,
"balance_network": 30000,
"balance_outgoing": 0,
"converters": []
})
# Transfer
params = {
'from_address': address_1,
'to_address': address_2,
'value': 100,
'token_symbol': 'COFE'
}
response = client.post('/transfer',
params=params)
transfer = response.json()
# Balance Account 1
params = {
'address': address_1,
'token_symbol': 'COFE',
'include_pending': True
}
response = client.get('/balance',
params=params)
balance_after_transfer = response.json()
assert (balance_after_transfer[0] == {
"address": default_token.get('address').lower(),
"balance_available": 29900,
"balance_incoming": 0,
"balance_network": 30000,
"balance_outgoing": 100,
"converters": []
})
# Transactions Account 1
params = {
'address': address_1,
'limit': 10
}
response = client.get('/transactions',
params=params)
transactions = response.json()
# TODO: What are the other 2 transactions
assert len(transactions) == 3
# Check the transaction is correct
# TODO wtf is READSEND (Ready to send? Or already sent)
assert transactions[0].status == 'READYSEND'

View File

@@ -796,18 +796,18 @@ invalid_service_code,Please dial %{valid_service_code} to access Sarafu Network,
help,"CON For assistance call %{support_phone} help,"CON For assistance call %{support_phone}
00. Back 00. Back
99. Exit","CON Kwa usaidizi piga simu %{support_phone} 99. Exit","CON Kwa usaidizi piga simu %{support_phone}
0. Rudi 00. Rudi
9. Ondoka","CON Kwa utethyo kuna simu %{support_phone} 99. Ondoka","CON Kwa utethyo kuna simu %{support_phone}
0. Itina 00. Itina
9. Ondoka","CON Kwa uteithio hora thimu %{support_phone} 99. Ondoka","CON Kwa uteithio hora thimu %{support_phone}
0. Coka 00. Coka
9. Uma","CON Kwa Kuavizwa piga simu %{support_phone} 99. Uma","CON Kwa Kuavizwa piga simu %{support_phone}
0. Uya nyuma 00. Uya nyuma
9. Uka","CON Kuom kony go simu e %{support_phone} 99. Uka","CON Kuom kony go simu e %{support_phone}
0. Dog chien 00. Dog chien
9. Wuogi","CON Qarqars simu dai%{support_phone} 99. Wuogi","CON Qarqars simu dai%{support_phone}
0. Dheebi 00. Dheebi
9. Bai" 99. Bai"
complete,"CON Your request has been sent. You will receive an SMS shortly. complete,"CON Your request has been sent. You will receive an SMS shortly.
00. Back 00. Back
99. Exit","CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi. 99. Exit","CON Ombi lako limetumwa. Utapokea uthibitishaji wa SMS kwa muda mfupi.
1 keys en sw kam kik miji luo bor
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813

View File

@@ -13,4 +13,4 @@ port =
ssl = ssl =
[system] [system]
guardians_file = var/lib/sys/guardians.txt guardians_file = cic_ussd/data/sys/guardians.txt

View File

@@ -1,5 +1,5 @@
[PIP] [PIP]
extra_index_host = pip.grassrootseconomics.net extra_index_host = pip.grassrootseconomics.net
extra_index_port = extra_index_port = 8433
extra_index_path = / extra_index_path = /
extra_index_proto = https extra_index_proto = https

View File

@@ -8,4 +8,4 @@ states=states/
transitions=transitions/ transitions=transitions/
[system] [system]
guardians_file = var/lib/sys/guardians.txt guardians_file = cic_ussd/data/sys/guardians.txt

View File

@@ -1,5 +1,5 @@
[PIP] [PIP]
extra_index_host = pip.grassrootseconomics.net extra_index_host = pip.grassrootseconomics.net
extra_index_port = extra_index_port = 8433
extra_index_path = / extra_index_path = /
extra_index_proto = https extra_index_proto = https

View File

@@ -1,10 +1,10 @@
[locale] [locale]
fallback=sw fallback=sw
path= path=cic_ussd/data/locale/
file_builders=var/lib/sys/ file_builders=cic_ussd/data/sys/
[schema] [schema]
file_path = data/schema file_path = data/schema
[languages] [languages]
file = var/lib/sys/languages.json file = cic_ussd/data/sys/languages.json

View File

@@ -1,5 +1,5 @@
[ussd] [ussd]
menu_file=cic_ussd/db/ussd_menu.json menu_file=cic_ussd/data/sys/ussd_menu.json
service_code=*483*46#,*483*061#,*384*96# service_code=*483*46#,*483*061#,*384*96#
user = user =
pass = pass =

View File

@@ -1,10 +1,10 @@
[locale] [locale]
fallback=sw fallback=sw
path=var/lib/locale/ path=cic_ussd/data/locale/
file_builders=var/lib/sys/ file_builders=cic_ussd/data/sys/
[schema] [schema]
file_path = /usr/local/lib/python3.8/site-packages/cic_translations/data/schema file_path = /usr/local/lib/python3.8/site-packages/cic_translations/data/schema
[languages] [languages]
file = var/lib/sys/languages.json file = cic_ussd/data/sys/languages.json

View File

@@ -1,5 +1,5 @@
[ussd] [ussd]
menu_file=data/ussd_menu.json menu_file=cic_ussd/data/sys/ussd_menu.json
service_code=*483*46#,*483*061#,*384*96# service_code=*483*46#,*483*061#,*384*96#
user = user =
pass = pass =

View File

@@ -3,12 +3,10 @@ ARG DOCKER_REGISTRY="registry.gitlab.com/grassrootseconomics"
FROM $DOCKER_REGISTRY/cic-base-images:python-3.8.6-dev-e8eb2ee2 FROM $DOCKER_REGISTRY/cic-base-images:python-3.8.6-dev-e8eb2ee2
RUN apt-get install -y redis-server RUN apt-get install -y redis-server
# create secrets directory # create secrets directory
RUN mkdir -vp pgp/keys RUN mkdir -vp pgp/keys
# create application directory
RUN mkdir -vp cic-ussd
RUN mkdir -vp data
ARG EXTRA_PIP_INDEX_URL=https://pip.grassrootseconomics.net ARG EXTRA_PIP_INDEX_URL=https://pip.grassrootseconomics.net
ARG EXTRA_PIP_ARGS="" ARG EXTRA_PIP_ARGS=""
@@ -25,7 +23,8 @@ RUN --mount=type=cache,mode=0755,target=/root/.cache/pip \
COPY . . COPY . .
RUN python setup.py install RUN python setup.py install
COPY cic_ussd/db/ussd_menu.json data/ # create local files directory
RUN mkdir -vp cic_ussd/data/locale
COPY docker/*.sh ./ COPY docker/*.sh ./
RUN chmod +x /root/*.sh RUN chmod +x /root/*.sh

View File

@@ -172,42 +172,6 @@ services:
set +a set +a
./start_tasker.sh --aux-all -q cic-eth -vv ./start_tasker.sh --aux-all -q cic-eth -vv
cic-eth-server:
image: ${DEV_DOCKER_REGISTRY:-registry.gitlab.com/grassrootseconomics}/cic-eth:${TAG:-latest}
ports:
- 5000:5000
build:
context: apps/cic-eth
dockerfile: docker/Dockerfile
args:
DOCKER_REGISTRY: ${DEV_DOCKER_REGISTRY:-registry.gitlab.com/grassrootseconomics}
PIP_INDEX_URL: ${PIP_INDEX_URL:-https://pypi.org/simple}
EXTRA_PIP_INDEX_URL: ${EXTRA_PIP_INDEX_URL:-https://pip.grassrootseconomics.net}
EXTRA_PIP_ARGS: $EXTRA_PIP_ARGS
environment:
REDIS_PORT: 6379
REDIS_HOST: redis
CHAIN_SPEC: ${CHAIN_SPEC:-evm:byzantium:8996:bloxberg}
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis}
CELERY_RESULT_URL: ${CELERY_RESULT_URL:-redis://redis}
CELERY_DEBUG: ${CELERY_DEBUG:-1}
restart: unless-stopped
depends_on:
- cic-eth-tasker
- cic-eth-tracker
- cic-eth-dispatcher
volumes:
# - ./apps/cic-eth/:/root
- signer-data:/run/crypto-dev-signer
- contract-config:/tmp/cic/config/:ro
command:
- /bin/bash
- -c
- |
set -a
if [[ -f /tmp/cic/config/env_reset ]]; then source /tmp/cic/config/env_reset; fi
set +a
python -m cic_eth.runnable.daemons.server
cic-eth-tracker: cic-eth-tracker:
image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-eth:${TAG:-latest} image: ${IMAGE_BASE_URL:-registry.gitlab.com/grassrootseconomics/cic-internal-integration}/cic-eth:${TAG:-latest}