Skip to content

Commit

Permalink
Merge 2c3ff09 into c42e2cb
Browse files Browse the repository at this point in the history
  • Loading branch information
Uxio0 committed Nov 7, 2018
2 parents c42e2cb + 2c3ff09 commit 73e3e46
Show file tree
Hide file tree
Showing 22 changed files with 296 additions and 30 deletions.
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
]
LOCAL_APPS = [
'safe_relay_service.relay.apps.RelayConfig',
'safe_relay_service.tokens.apps.TokensConfig',
'safe_relay_service.gas_station.apps.GasStationConfig',
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ethereum==2.3.2
factory-boy==2.11.1
faker==0.9.2
gevent==1.3.7
gnosis-py==0.4.5
gnosis-py==0.5.0
gunicorn==19.9.0
hexbytes==0.1.0
jsonschema==2.6.0
Expand Down
3 changes: 2 additions & 1 deletion safe_relay_service/relay/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import django.contrib.postgres.fields
import django.db.models.deletion
import django.utils.timezone
import django_eth.models
import model_utils.fields
from django.db import migrations, models

import django_eth.models


class Migration(migrations.Migration):

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Generated by Django 2.0.8 on 2018-10-02 14:22

import django_eth.models
from django.db import migrations

import django_eth.models


class Migration(migrations.Migration):

Expand Down
6 changes: 4 additions & 2 deletions safe_relay_service/relay/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from django.contrib.postgres.fields import ArrayField
from django.db import models
from django_eth.models import EthereumAddressField, Sha3HashField, Uint256Field
from gnosis.safe.ethereum_service import EthereumServiceProvider
from gnosis.safe.safe_service import (InvalidMultisigTx, SafeOperation,
SafeServiceProvider)
from model_utils.models import TimeStampedModel

from django_eth.models import EthereumAddressField, Sha3HashField, Uint256Field
from safe_relay_service.gas_station.gas_station import GasStationProvider


Expand Down Expand Up @@ -167,7 +167,8 @@ def create_multisig_tx(self,
gas_token: str,
refund_receiver: str,
nonce: int,
signatures: List[Dict[str, int]]):
signatures: List[Dict[str, int]],
tx_gas_price: int):
"""
:return: Database model of SafeMultisigTx
:raises: SafeMultisigTxExists: If Safe Multisig Tx with nonce already exists
Expand Down Expand Up @@ -195,6 +196,7 @@ def create_multisig_tx(self,
gas_token,
refund_receiver,
signatures_packed,
tx_gas_price=tx_gas_price
)
except InvalidMultisigTx as exc:
raise self.SafeMultisigTxError(str(exc)) from exc
Expand Down
13 changes: 4 additions & 9 deletions safe_relay_service/relay/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

from gnosis.safe.ethereum_service import EthereumServiceProvider
from gnosis.safe.safe_service import SafeServiceProvider
from gnosis.safe.serializers import SafeMultisigTxSerializer
from gnosis.safe.serializers import SafeMultisigTxSerializer, SafeSignatureSerializer
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from django_eth.constants import (NULL_ADDRESS, SIGNATURE_S_MAX_VALUE,
SIGNATURE_S_MIN_VALUE)
from django_eth.serializers import (EthereumAddressField, HexadecimalField,
Sha3HashField, SignatureSerializer,
from django_eth.serializers import (EthereumAddressField, Sha3HashField,
TransactionResponseSerializer)
from safe_relay_service.relay.models import SafeCreation, SafeFunding

Expand All @@ -34,7 +33,7 @@ def validate(self, data):


class SafeRelayMultisigTxSerializer(SafeMultisigTxSerializer):
signatures = serializers.ListField(child=SignatureSerializer())
signatures = serializers.ListField(child=SafeSignatureSerializer())

def validate(self, data):
super().validate(data)
Expand All @@ -49,10 +48,6 @@ def validate(self, data):

safe_service = SafeServiceProvider()

gas_token = data.get('gas_token')
if gas_token and gas_token != NULL_ADDRESS:
raise ValidationError('Gas Token is still not supported')

refund_receiver = data.get('refund_receiver')
if refund_receiver and refund_receiver != NULL_ADDRESS:
raise ValidationError('Refund Receiver is not configurable')
Expand Down Expand Up @@ -118,4 +113,4 @@ class SafeMultisigEstimateTxResponseSerializer(serializers.Serializer):
operational_gas = serializers.IntegerField(min_value=0)
gas_price = serializers.IntegerField(min_value=0)
last_used_nonce = serializers.IntegerField(min_value=0, allow_null=True)
gas_token = HexadecimalField(allow_blank=True, allow_null=True)
gas_token = EthereumAddressField(allow_null=True, allow_zero_address=True)
27 changes: 20 additions & 7 deletions safe_relay_service/relay/tests/factories.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
from logging import getLogger

from django_eth.tests.factories import get_eth_address_with_key
from ethereum.transactions import secpk1n
from faker import Factory as FakerFactory
from faker import Faker
from gnosis.safe.tests.factories import generate_valid_s

from django_eth.tests.factories import get_eth_address_with_key
from safe_relay_service.relay.models import SafeCreation

fakerFactory = FakerFactory.create()
Expand Down Expand Up @@ -36,7 +37,8 @@ def generate_safe(owners=None, number_owners=3, threshold=None) -> SafeCreation:
return safe_creation


def deploy_safe(w3, safe_creation, funder) -> str:
#FIXME Use the functions in gnosis-py
def deploy_safe(w3, safe_creation, funder: str, initial_funding_wei: int=0) -> str:
w3.eth.waitForTransactionReceipt(
w3.eth.sendTransaction({
'from': funder,
Expand All @@ -45,15 +47,26 @@ def deploy_safe(w3, safe_creation, funder) -> str:
})
)

w3.eth.sendTransaction({
'from': funder,
'to': safe_creation.safe.address,
'value': safe_creation.payment
})
w3.eth.waitForTransactionReceipt(
w3.eth.sendTransaction({
'from': funder,
'to': safe_creation.safe.address,
'value': safe_creation.payment
})
)

tx_hash = w3.eth.sendRawTransaction(bytes(safe_creation.signed_tx))
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
assert tx_receipt.contractAddress == safe_creation.safe.address
assert tx_receipt.status

if initial_funding_wei > 0:
w3.eth.waitForTransactionReceipt(
w3.eth.sendTransaction({
'from': funder,
'to': safe_creation.safe.address,
'value': initial_funding_wei
})
)

return safe_creation.safe.address
3 changes: 2 additions & 1 deletion safe_relay_service/relay/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from django.test import TestCase
from django_eth.tests.factories import get_eth_address_with_key
from ethereum.transactions import secpk1n
from faker import Faker
from gnosis.safe.safe_service import SafeServiceProvider
from hexbytes import HexBytes

from django_eth.tests.factories import get_eth_address_with_key

from ..models import SafeContract, SafeFunding
from ..serializers import (SafeCreationSerializer,
SafeFundingResponseSerializer,
Expand Down
1 change: 1 addition & 0 deletions safe_relay_service/relay/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from django_eth.tests.factories import get_eth_address_with_key

from ..validators import validate_checksumed_address
Expand Down
120 changes: 115 additions & 5 deletions safe_relay_service/relay/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
from ethereum.utils import check_checksum
from faker import Faker
from gnosis.safe.safe_service import SafeServiceProvider
from gnosis.safe.tests.factories import deploy_example_erc20
from rest_framework import status
from rest_framework.test import APITestCase

from django_eth.constants import NULL_ADDRESS
from django_eth.tests.factories import (get_eth_address_with_invalid_checksum,
get_eth_address_with_key)
from safe_relay_service.tokens.tests.factories import TokenFactory

from ..models import SafeContract, SafeCreation, SafeMultisigTx
from ..serializers import SafeCreationSerializer
Expand Down Expand Up @@ -193,6 +194,113 @@ def test_safe_multisig_tx(self):
self.assertEqual(request.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
self.assertTrue('exists' in request.data)

def test_safe_multisig_tx_gas_token(self):
# Create Safe ------------------------------------------------
safe_service = SafeServiceProvider()
w3 = safe_service.w3
funder = w3.eth.accounts[0]
owner, owner_key = get_eth_address_with_key()
threshold = 1

safe_balance = w3.toWei(0.01, 'ether')
safe_creation = generate_safe(owners=[owner], threshold=threshold)
my_safe_address = deploy_safe(w3, safe_creation, funder, initial_funding_wei=safe_balance)

# Get tokens for the safe
safe_token_balance = int(1e18)
erc20_contract = deploy_example_erc20(self.w3, safe_token_balance, my_safe_address, funder)

# Send something to the owner, who will be sending the tx
owner0_balance = safe_balance
w3.eth.waitForTransactionReceipt(w3.eth.sendTransaction({
'from': funder,
'to': owner,
'value': owner0_balance
}))

# Safe prepared --------------------------------------------
to, _ = get_eth_address_with_key()
value = safe_balance
tx_data = None
operation = 0
refund_receiver = None
nonce = 0
gas_token = erc20_contract.address

data = {
"to": to,
"value": value,
"data": tx_data,
"operation": operation,
"gasToken": gas_token
}

# Get estimation for gas. Token does not exist
response = self.client.post(reverse('v1:safe-multisig-tx-estimate', args=(my_safe_address,)),
data=data,
format='json')
self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
self.assertIn('Gas token', response.json())

# Create token
token_model = TokenFactory(address=gas_token)
response = self.client.post(reverse('v1:safe-multisig-tx-estimate', args=(my_safe_address,)),
data=data,
format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
estimation_json = response.json()

safe_tx_gas = estimation_json['safeTxGas'] + estimation_json['operationalGas']
data_gas = estimation_json['dataGas']
gas_price = estimation_json['gasPrice']
gas_token = estimation_json['gasToken']

multisig_tx_hash = safe_service.get_hash_for_safe_tx(
my_safe_address,
to,
value,
tx_data,
operation,
safe_tx_gas,
data_gas,
gas_price,
gas_token,
refund_receiver,
nonce
)

signatures = [w3.eth.account.signHash(multisig_tx_hash, private_key) for private_key in [owner_key]]
signatures_json = [{'v': s['v'], 'r': s['r'], 's': s['s']} for s in signatures]

data = {
"to": to,
"value": value,
"data": tx_data,
"operation": operation,
"safe_tx_gas": safe_tx_gas,
"data_gas": data_gas,
"gas_price": gas_price,
"gas_token": gas_token,
"nonce": nonce,
"signatures": signatures_json
}

response = self.client.post(reverse('v1:safe-multisig-tx', args=(my_safe_address,)),
data=data,
format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
tx_hash = response.json()['transactionHash'][2:] # Remove leading 0x
safe_multisig_tx = SafeMultisigTx.objects.get(tx_hash=tx_hash)
self.assertEqual(safe_multisig_tx.to, to)
self.assertEqual(safe_multisig_tx.value, value)
self.assertEqual(safe_multisig_tx.data, tx_data)
self.assertEqual(safe_multisig_tx.operation, operation)
self.assertEqual(safe_multisig_tx.safe_tx_gas, safe_tx_gas)
self.assertEqual(safe_multisig_tx.data_gas, data_gas)
self.assertEqual(safe_multisig_tx.gas_price, gas_price)
self.assertEqual(safe_multisig_tx.gas_token, gas_token)
self.assertEqual(safe_multisig_tx.nonce, nonce)

def test_safe_multisig_tx_errors(self):
my_safe_address = get_eth_address_with_invalid_checksum()
request = self.client.post(reverse('v1:safe-multisig-tx', args=(my_safe_address,)),
Expand Down Expand Up @@ -225,16 +333,18 @@ def test_safe_multisig_tx_estimate(self):
format='json')
self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND)

initial_funding = self.w3.toWei(0.0001, 'ether')
to, _ = get_eth_address_with_key()
data = {
'to': to,
'value': 10,
'value': initial_funding // 2,
'data': '0x',
'operation': 1
}

safe_creation = generate_safe()
my_safe_address = deploy_safe(self.w3, safe_creation, self.w3.eth.accounts[0])
my_safe_address = deploy_safe(self.w3, safe_creation, self.w3.eth.accounts[0],
initial_funding_wei=initial_funding)

request = self.client.post(reverse('v1:safe-multisig-tx-estimate', args=(my_safe_address,)),
data=data,
Expand All @@ -245,12 +355,12 @@ def test_safe_multisig_tx_estimate(self):
self.assertGreater(response['dataGas'], 0)
self.assertGreater(response['gasPrice'], 0)
self.assertIsNone(response['lastUsedNonce'])
self.assertEqual(response['gasToken'], NULL_ADDRESS)
self.assertEqual(response['gasToken'], None)

to, _ = get_eth_address_with_key()
data = {
'to': to,
'value': 100,
'value': initial_funding // 2,
'data': None,
'operation': 0
}
Expand Down
Loading

0 comments on commit 73e3e46

Please sign in to comment.