Skip to content

Commit

Permalink
refactor: calculate fees (#994)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbarnsley authored Nov 12, 2024
1 parent f4c583d commit 9340474
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 29 deletions.
89 changes: 89 additions & 0 deletions app/Models/Receipt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace App\Models;

use App\Models\Casts\BigInteger;
use App\Services\BigNumber;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

/**
* @property string $id
* @property bool $success
* @property BigNumber $block_height
* @property BigNumber $gas_used
* @property BigNumber $gas_refunded
* @property resource|string|null $deployed_contract_address
* @property array $logs
* @property string $output
*/
final class Receipt extends Model
{
use HasFactory;

/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false;

/**
* The "type" of the primary key ID.
*
* @var string
*/
public $keyType = 'string';

/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;

/**
* The column name of the primary key.
*
* @var string
*/
protected $primaryKey = 'id';

/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'id' => 'string',
'success' => 'bool',
'block_height' => BigInteger::class,
'gas_used' => BigInteger::class,
'gas_refunded' => BigInteger::class,
'logs' => 'array',
'output' => 'string',
];

/**
* A wallet has many blocks if it is a validator.
*
* @return HasOne
*/
public function transaction(): HasOne
{
return $this->hasOne(Transaction::class, 'id', 'id');
}

/**
* Get the current connection name for the model.
*
* @return string
*/
public function getConnectionName()
{
return 'explorer';
}
}
25 changes: 25 additions & 0 deletions app/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Arr;
use Laravel\Scout\Searchable;

Expand Down Expand Up @@ -106,6 +107,10 @@ final class Transaction extends Model
'block_height' => 'int',
];

protected $with = [
'receipt',
];

private bool|string|null $vendorFieldContent = false;

/**
Expand Down Expand Up @@ -191,6 +196,16 @@ public function sender(): BelongsTo
return $this->belongsTo(Wallet::class, 'sender_public_key', 'public_key');
}

/**
* A receipt belongs to a transaction.
*
* @return HasOne
*/
public function receipt(): HasOne
{
return $this->hasOne(Receipt::class, 'id', 'id');
}

/**
* A transaction belongs to a recipient.
*
Expand Down Expand Up @@ -220,6 +235,16 @@ public function vendorField(): string|null
return $this->vendorFieldContent;
}

public function fee(): BigNumber
{
$gasPrice = clone $this->gas_price;
if ($this->receipt === null) {
return $gasPrice;
}

return $gasPrice->multipliedBy($this->receipt->gas_used->valueOf());
}

/**
* Get the current connection name for the model.
*
Expand Down
3 changes: 1 addition & 2 deletions app/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use App\Exceptions\BlockNotFoundException;
use App\Exceptions\TransactionNotFoundException;
use App\Exceptions\WalletNotFoundException;
use App\Facades\Network;
use App\Facades\Wallets;
use App\Models\Block;
use App\Models\Transaction;
Expand Down Expand Up @@ -51,7 +50,7 @@ public function boot()

Route::bind('wallet', function (string $walletID): Wallet {
if (strlen($walletID) === Constants::ADDRESS_LENGTH) {
abort_unless(Address::validate($walletID, Network::config()), 404);
abort_unless(Address::validate($walletID), 404);
}

try {
Expand Down
10 changes: 10 additions & 0 deletions app/Services/BigNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ public function plus($value): self
return $this;
}

/**
* @param BigDecimal|int|float|string $value
*/
public function multipliedBy($value): self
{
$this->value = $this->value->multipliedBy($value);

return $this;
}

public function toNumber(): int
{
return $this->value->toInt();
Expand Down
13 changes: 7 additions & 6 deletions app/Services/Cache/StatisticsCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Services\BigNumber;
use App\Services\Cache\Concerns\ManagesCache;
use App\Services\Timestamp;
use ArkEcosystem\Crypto\Utils\UnitConverter;
use Carbon\Carbon;
use Illuminate\Cache\TaggedCache;
use Illuminate\Support\Facades\Cache;
Expand All @@ -22,23 +23,23 @@ final class StatisticsCache implements Contract
public function getTransactionData(): array
{
return $this->remember('transactions', self::STATS_TTL, function () {
$timestamp = Timestamp::fromUnix(Carbon::now()->subDays(1)->unix())->unix() * 1000;
$timestamp = Carbon::now()->subDays(1)->getTimestampMs();
$data = (array) DB::connection('explorer')
->table('transactions')
->selectRaw('COUNT(*) as transaction_count')
->selectRaw('SUM(amount) as volume')
// TODO: Calculate fee using gas_price and gas_used - https://app.clickup.com/t/86dv41828
->selectRaw('SUM(gas_price) as total_fees')
->selectRaw('AVG(gas_price) as average_fee')
->selectRaw('SUM(gas_price * COALESCE(receipts.gas_used, 0)) as total_fees')
->selectRaw('AVG(gas_price * COALESCE(receipts.gas_used, 0)) as average_fee')
->from('transactions')
->join('receipts', 'transactions.id', '=', 'receipts.id')
->where('timestamp', '>', $timestamp)
->first();

return [
'transaction_count' => $data['transaction_count'],
'volume' => $data['volume'] ?? 0,
'total_fees' => $data['total_fees'] ?? 0,
'average_fee' => $data['average_fee'] ?? 0,
'total_fees' => UnitConverter::parseUnits($data['total_fees'] ?? 0, 'gwei'),
'average_fee' => UnitConverter::parseUnits($data['average_fee'] ?? 0, 'gwei'),
];
});
}
Expand Down
3 changes: 1 addition & 2 deletions app/Services/Identity.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace App\Services;

use App\Facades\Network;
use ArkEcosystem\Crypto\Identities\Address;
use Illuminate\Support\Facades\Cache;

Expand All @@ -15,7 +14,7 @@ public static function address(string $publicKey): string
return Cache::tags('identity')->remember(
$publicKey,
now()->addMinutes(10),
fn () => Address::fromPublicKey($publicKey, Network::config())
fn () => Address::fromPublicKey($publicKey)
);
}
}
5 changes: 3 additions & 2 deletions app/ViewModels/TransactionViewModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use App\ViewModels\Concerns\Transaction\InteractsWithVendorField;
use App\ViewModels\Concerns\Transaction\InteractsWithVotes;
use App\ViewModels\Concerns\Transaction\InteractsWithWallets;
use ArkEcosystem\Crypto\Utils\UnitConverter;
use Carbon\Carbon;
use Illuminate\Support\Arr;

Expand Down Expand Up @@ -93,12 +94,12 @@ public function nonce(): int

public function fee(): float
{
return $this->transaction->gas_price->toFloat(); // TODO: https://app.clickup.com/t/86dv41828
return UnitConverter::formatUnits((string) $this->transaction->fee(), 'gwei');
}

public function feeFiat(bool $showSmallAmounts = false): string
{
return ExchangeRate::convert($this->transaction->gas_price->toFloat(), $this->transaction->timestamp, $showSmallAmounts); // TODO: https://app.clickup.com/t/86dv41828
return ExchangeRate::convert($this->fee(), $this->transaction->timestamp, $showSmallAmounts);
}

public function amountForItself(): float
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"ardenthq/arkvault-url": "dev-feat/mainsail",
"arkecosystem/crypto": "dev-feat/mainsail",
"arkecosystem/foundation": "^19.0",
"blade-ui-kit/blade-icons": "^1.7",
"brick/math": "^0.12",
"danharrin/livewire-rate-limiting": "^1.3",
"doctrine/dbal": "^3.0",
Expand Down
26 changes: 13 additions & 13 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions database/factories/ReceiptFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Models\Receipt;
use App\Models\Transaction;
use App\Models\Wallet;
use Illuminate\Database\Eloquent\Factories\Factory;

final class ReceiptFactory extends Factory
{
protected $model = Receipt::class;

public function definition()
{
return [
'id' => $this->faker->transactionId,
'success' => $this->faker->boolean,
'block_height' => $this->faker->numberBetween(1, 10000),
'gas_used' => $this->faker->numberBetween(1, 100),
'gas_refunded' => $this->faker->numberBetween(1, 100),
'deployed_contract_address' => fn () => Wallet::factory()->create()->address,
'logs' => [],
'output' => null,
];
}

public function withTransaction()
{
$transaction = Transaction::factory()->create();

return $this->state(fn (array $attributes) => [
'id' => $transaction->id,
]);
}
}
2 changes: 1 addition & 1 deletion database/factories/TransactionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function definition()
'sender_public_key' => fn () => $wallet->public_key,
'recipient_id' => fn () => $wallet->address,
'timestamp' => 1603083256000,
'fee' => $this->faker->numberBetween(1, 100) * 1e18,
'gas_price' => $this->faker->numberBetween(1, 100),
'amount' => $this->faker->numberBetween(1, 100) * 1e18,
'nonce' => 1,
];
Expand Down
Loading

0 comments on commit 9340474

Please sign in to comment.