Skip to content

Commit

Permalink
Add endpoints to work with SafeOperations
Browse files Browse the repository at this point in the history
  • Loading branch information
Uxio0 committed Mar 7, 2024
1 parent a9eddcf commit 2293e4c
Show file tree
Hide file tree
Showing 12 changed files with 1,124 additions and 53 deletions.
7 changes: 7 additions & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@

urlpatterns_v1 = [
path("", include("safe_transaction_service.history.urls", namespace="history")),
path(
"",
include(
"safe_transaction_service.account_abstraction.urls",
namespace="account_abstraction",
),
),
path(
"contracts/",
include("safe_transaction_service.contracts.urls", namespace="contracts"),
Expand Down
4 changes: 4 additions & 0 deletions safe_transaction_service/account_abstraction/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@
USER_OPERATION_SUPPORTED_ENTRY_POINTS = {
ChecksumAddress(HexStr(HexAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")))
}

SAFE_OPERATION_MODULE_ADDRESSES = {
ChecksumAddress(HexStr(HexAddress("0xa581c4A4DB7175302464fF3C06380BC3270b4037")))
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,123 @@
# Generated by Django 4.2.10 on 2024-02-29 17:39
# Generated by Django 5.0.3 on 2024-03-06 16:18

import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models

import model_utils.fields

import gnosis.eth.django.models


class Migration(migrations.Migration):
initial = True

dependencies = [
("history", "0078_remove_safestatus_history_saf_address_1c362b_idx_and_more"),
("history", "0079_alter_erc20transfer_unique_together_and_more"),
]

operations = [
migrations.CreateModel(
name="SafeOperation",
fields=[
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"hash",
gnosis.eth.django.models.Keccak256Field(
primary_key=True, serialize=False
),
),
("valid_after", models.DateTimeField()),
("valid_until", models.DateTimeField()),
(
"module_address",
gnosis.eth.django.models.EthereumAddressV2Field(db_index=True),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="SafeOperationConfirmation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
("owner", gnosis.eth.django.models.EthereumAddressV2Field()),
(
"signature",
gnosis.eth.django.models.HexField(
default=None, max_length=5000, null=True
),
),
(
"signature_type",
models.PositiveSmallIntegerField(
choices=[
(0, "CONTRACT_SIGNATURE"),
(1, "APPROVED_HASH"),
(2, "EOA"),
(3, "ETH_SIGN"),
],
db_index=True,
),
),
(
"safe_operation",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="confirmations",
to="account_abstraction.safeoperation",
),
),
],
options={
"ordering": ["created"],
},
),
migrations.CreateModel(
name="UserOperation",
fields=[
(
"user_operation_hash",
"hash",
gnosis.eth.django.models.Keccak256Field(
primary_key=True, serialize=False
),
Expand All @@ -45,20 +144,31 @@ class Migration(migrations.Migration):
"paymaster_data",
models.BinaryField(blank=True, editable=True, null=True),
),
("signature", models.BinaryField()),
("signature", models.BinaryField(blank=True, editable=True, null=True)),
(
"entry_point",
gnosis.eth.django.models.EthereumAddressV2Field(db_index=True),
),
(
"ethereum_tx",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="history.ethereumtx",
),
),
],
),
migrations.AddField(
model_name="safeoperation",
name="user_operation",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="safe_operation",
to="account_abstraction.useroperation",
),
),
migrations.CreateModel(
name="UserOperationReceipt",
fields=[
Expand All @@ -80,11 +190,19 @@ class Migration(migrations.Migration):
"user_operation",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="receipt",
to="account_abstraction.useroperation",
),
),
],
),
migrations.AddConstraint(
model_name="safeoperationconfirmation",
constraint=models.UniqueConstraint(
fields=("safe_operation", "owner"),
name="unique_safe_operation_owner_confirmation",
),
),
migrations.AddIndex(
model_name="useroperation",
index=models.Index(
Expand Down
59 changes: 48 additions & 11 deletions safe_transaction_service/account_abstraction/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@
from django.db.models import Index

from hexbytes import HexBytes
from model_utils.models import TimeStampedModel

from gnosis.eth.account_abstraction import UserOperation as UserOperationClass
from gnosis.eth.account_abstraction import UserOperationMetadata
from gnosis.eth.django.models import (
EthereumAddressV2Field,
HexField,
Keccak256Field,
Uint256Field,
)
from gnosis.safe.account_abstraction import SafeOperation
from gnosis.safe.account_abstraction import SafeOperation as SafeOperationClass
from gnosis.safe.safe_signature import SafeSignatureType

from safe_transaction_service.history import models as history_models

logger = logging.getLogger(__name__)

SIGNATURE_LENGTH = 5_000


class UserOperation(models.Model):
"""
Expand Down Expand Up @@ -49,13 +54,6 @@ class UserOperation(models.Model):
signature = models.BinaryField(null=True, blank=True, editable=True)
entry_point = EthereumAddressV2Field(db_index=True)

# Receipt
actual_gas_cost = Uint256Field()
actual_gas_used = Uint256Field()
success = models.BooleanField()
reason = models.CharField(max_length=256)
deposited = Uint256Field()

class Meta:
unique_together = (("sender", "nonce"),)
indexes = [
Expand Down Expand Up @@ -117,18 +115,57 @@ def to_safe_operation(self) -> SafeOperation:


class UserOperationReceipt(models.Model):
user_operation = models.OneToOneField(UserOperation, on_delete=models.CASCADE)
user_operation = models.OneToOneField(
UserOperation, on_delete=models.CASCADE, related_name="receipt"
)
actual_gas_cost = Uint256Field()
actual_gas_used = Uint256Field()
success = models.BooleanField()
reason = models.CharField(max_length=256)
deposited = Uint256Field()


class SafeOperation(models.Model):
class SafeOperation(TimeStampedModel):
hash = Keccak256Field(primary_key=True) # safeOperationHash
user_operation = models.ForeignKey(
UserOperation, on_delete=models.CASCADE, null=True, blank=True
user_operation = models.OneToOneField(
UserOperation, on_delete=models.CASCADE, related_name="safe_operation"
)
valid_after = models.DateTimeField() # Epoch uint48
valid_until = models.DateTimeField() # Epoch uint48
module_address = EthereumAddressV2Field(db_index=True)

def build_signature(self) -> bytes:
return b"".join(
[
HexBytes(signature)
for _, signature in sorted(
self.confirmations.values_list("owner", "signature"),
key=lambda tup: tup[0].lower(),
)
]
)


class SafeOperationConfirmation(TimeStampedModel):
safe_operation = models.ForeignKey(
SafeOperation,
on_delete=models.CASCADE,
related_name="confirmations",
)
owner = EthereumAddressV2Field()
signature = HexField(null=True, default=None, max_length=SIGNATURE_LENGTH)
signature_type = models.PositiveSmallIntegerField(
choices=[(tag.value, tag.name) for tag in SafeSignatureType], db_index=True
)

class Meta:
constraints = [
models.UniqueConstraint(
fields=["safe_operation", "owner"],
name="unique_safe_operation_owner_confirmation",
)
]
ordering = ["created"]

def __str__(self):
return f"Safe Operation Confirmation for owner={self.owner}"
6 changes: 6 additions & 0 deletions safe_transaction_service/account_abstraction/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework.pagination import LimitOffsetPagination


class DefaultPagination(LimitOffsetPagination):
max_limit = 200
default_limit = 100
Loading

0 comments on commit 2293e4c

Please sign in to comment.