Commit 6fbcf63c authored by Emiliano Balbuena's avatar Emiliano Balbuena

(refactor): Tweak flow and add all Request props to DB

1 merge request!393WIP: (feat): Withdrawal status support
Pipeline #95178026 failed with stages
in 2 minutes and 45 seconds
<?php
namespace Minds\Controllers\api\v2\admin\rewards;
use Exception;
use Minds\Api\Exportable;
use Minds\Core\Di\Di;
use Minds\Core\Rewards\Withdraw\Manager;
use Minds\Core\Rewards\Withdraw\Repository;
use Minds\Core\Rewards\Withdraw\Request;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Api\Factory;
......@@ -13,7 +17,7 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
* Equivalent to HTTP GET method
* @param array $pages
* @return mixed|null
* @throws \Exception
* @throws Exception
*/
public function get($pages)
{
......@@ -59,6 +63,37 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
*/
public function put($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Rewards\Withdraw\Manager');
$request = $manager->get(
(new Request())
->setUserGuid((string) $pages[0] ?? null)
->setTimestamp((int) $pages[1] ?? null)
->setTx((string) $pages[2] ?? null)
);
if (!$request) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Missing request',
]);
}
try {
$success = $manager->approve($request);
} catch (Exception $exception) {
$success = false;
$errorMessage = $exception->getMessage();
}
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Cannot approve request',
]);
}
return Factory::response([]);
}
......@@ -69,6 +104,37 @@ class withdrawals implements Interfaces\Api, Interfaces\ApiAdminPam
*/
public function delete($pages)
{
/** @var Manager $manager */
$manager = Di::_()->get('Rewards\Withdraw\Manager');
$request = $manager->get(
(new Request())
->setUserGuid((string) $pages[0] ?? null)
->setTimestamp((int) $pages[1] ?? null)
->setTx((string) $pages[2] ?? null)
);
if (!$request) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Missing request',
]);
}
try {
$success = $manager->reject($request);
} catch (Exception $exception) {
$success = false;
$errorMessage = $exception->getMessage();
}
if (!$success) {
return Factory::response([
'status' => 'error',
'message' => $errorMessage ?? 'Cannot reject request',
]);
}
return Factory::response([]);
}
}
......@@ -123,10 +123,11 @@ class transactions implements Interfaces\Api
break;
case "withdraw":
$request = new Withdraw\Request();
$request->setTx($_POST['tx'])
$request
->setUserGuid(Session::getLoggedInUser()->guid)
->setAddress($_POST['address'])
->setTimestamp(time())
->setTx($_POST['tx'])
->setAddress($_POST['address'])
->setGas($_POST['gas'])
->setAmount((string) BigNumber::_($_POST['amount']));
......
......@@ -92,21 +92,36 @@ class WithdrawEvent implements BlockchainEventInterface
$gas = (string) BigNumber::fromHex($gas);
$amount = (string) BigNumber::fromHex($amount);
//double check the details of this transaction match with what the user actually requested
$request = new Request();
$request
->setTx($tx)
->setAddress($address)
->setUserGuid($user_guid)
->setGas($gas)
->setTimestamp($transaction->getTimestamp())
->setAmount($amount);
try {
$request = $this->manager->get(
(new Request())
->setUserGuid($user_guid)
->setTimestamp($transaction->getTimestamp())
->setTx($tx)
);
if (!$request) {
throw new \Exception('Unknown withdrawal');
}
if ((string) $address !== (string) $request->getAddress()) {
throw new \Exception('Wrong address value');
} elseif ((string) $gas !== (string) $request->getGas()) {
throw new \Exception('Wrong gas value');
} elseif ((string) $amount !== (string) $request->getAmount()) {
throw new \Exception('Wrong amount value');
}
$this->manager->confirm($request, $transaction);
} catch (Exception $e) {
error_log(print_r($e, true));
$this->manager->fail(
(new Request())
->setUserGuid($user_guid)
->setTimestamp($transaction->getTimestamp())
->setTx($tx)
);
error_log($e);
}
}
......@@ -114,7 +129,6 @@ class WithdrawEvent implements BlockchainEventInterface
{
if ($transaction->getContract() !== 'withdraw') {
throw new Exception("Failed but not a withdrawal");
return;
}
$transaction->setFailed(true);
......
......@@ -1525,7 +1525,7 @@ CREATE TABLE minds.notification_batches (
primary key (user_guid, batch_id)
);
ALTER TABLE minds.withdrawals ADD status text;
ALTER TABLE minds.withdrawals ADD (status text, address text, gas varint);
CREATE MATERIALIZED VIEW minds.withdrawals_by_status AS
SELECT *
......
......@@ -59,6 +59,22 @@ class NotificationsDelegate
]);
}
/**
* @param Request $request
*/
public function onFail(Request $request): void
{
$message = 'Your token withdrawal request transaction failed. Please contact an administrator.';
$this->dispatcher->trigger('notification', 'all', [
'to' => [ $request->getUserGuid() ],
'from' => 100000000000000519,
'notification_view' => 'custom_message',
'params' => ['message' => $message],
'message' => $message,
]);
}
/**
* @param Request $request
* @throws Exception
......
......@@ -83,10 +83,35 @@ class Manager
/**
* @param Request $request
* @return void
* @return Request|null
* @throws Exception
*/
public function request($request)
public function get(Request $request): ?Request
{
if (
!$request->getUserGuid() ||
!$request->getTimestamp() ||
!$request->getTx()
) {
throw new Exception('Missing request keys');
}
$requests = $this->repository->getList([
'user_guid' => $request->getUserGuid(),
'timestamp' => $request->getTimestamp(),
'tx' => $request->getTx(),
'limit' => 1,
]);
return $requests['withdrawals'][0] ?? null;
}
/**
* @param Request $request
* @return bool
* @throws Exception
*/
public function request($request): bool
{
if (!$this->check($request->getUserGuid())) {
throw new Exception('A withdrawal has already been requested in the last 24 hours');
......@@ -139,21 +164,29 @@ class Manager
// Notify
$this->notificationsDelegate->onRequest($request);
//
return true;
}
/**
* @param Request $request
* @param Transaction $transaction - the transaction we store
* @return void
* @return bool
* @throws Exception
*/
public function confirm(Request $request, $transaction)
public function confirm(Request $request, Transaction $transaction): bool
{
if ($request->getUserGuid() != $transaction->getUserGuid()) {
if ($request->getStatus() !== 'pending') {
throw new Exception('Request is not pending');
}
if ((string) $request->getUserGuid() !== (string) $transaction->getUserGuid()) {
throw new Exception('The user who requested this operation does not match the transaction');
}
if (strtolower($request->getAddress()) != strtolower($transaction->getData()['address'])) {
if (strtolower($request->getAddress()) !== strtolower($transaction->getData()['address'])) {
throw new Exception('The address does not match the transaction');
}
......@@ -182,7 +215,7 @@ class Manager
->create();
} catch (LockFailedException $e) {
$this->txManager->add($transaction);
return;
return false;
}
// Set request status
......@@ -197,14 +230,55 @@ class Manager
// Notify
$this->notificationsDelegate->onConfirm($request);
//
return true;
}
/**
* @param Request $request
* @return bool
* @throws Exception
*/
public function approve(Request $request)
public function fail(Request $request): bool
{
if ($request->getStatus() !== 'pending') {
throw new Exception('Request is not pending');
}
$user = new User;
$user->guid = (string) $request->getUserGuid();
// Set request status
$request
->setStatus('failed');
// Update
$this->repository->add($request);
// Notify
$this->notificationsDelegate->onFail($request);
//
return true;
}
/**
* @param Request $request
* @return bool
* @throws Exception
*/
public function approve(Request $request): bool
{
if ($request->getStatus() !== 'pending_approval') {
throw new Exception('Request is not pending approval');
}
// Send blockchain transaction
$txHash = $this->eth->sendRawTransaction($this->config->get('blockchain')['contracts']['withdraw']['wallet_pkey'], [
......@@ -234,14 +308,23 @@ class Manager
// Notify
$this->notificationsDelegate->onApprove($request);
//
return true;
}
/**
* @param Request $request
* @return bool
* @throws Exception
*/
public function reject(Request $request)
public function reject(Request $request): bool
{
if ($request->getStatus() !== 'pending_approval') {
throw new Exception('Request is not pending approval');
}
$user = new User;
$user->guid = (string) $request->getUserGuid();
......@@ -254,7 +337,7 @@ class Manager
->setAmount((string) BigNumber::_($request->getAmount()))
->create();
} catch (LockFailedException $e) {
throw new \Exception('Cannot refund rejected withdrawal tokens');
throw new Exception('Cannot refund rejected withdrawal tokens');
}
// Set request status
......@@ -269,5 +352,9 @@ class Manager
// Notify
$this->notificationsDelegate->onReject($request);
//
return true;
}
}
......@@ -57,6 +57,16 @@ class Repository
$values[] = new Varint($opts['user_guid']);
}
if ($opts['timestamp']) {
$where[] = 'timestamp = ?';
$values[] = new Timestamp($opts['timestamp']);
}
if ($opts['tx']) {
$where[] = 'tx = ?';
$values[] = (string) $opts['tx'];
}
if ($opts['from']) {
$where[] = 'timestamp >= ?';
$values[] = new Timestamp($opts['from']);
......@@ -92,11 +102,14 @@ class Repository
$request
->setUserGuid((string) $row['user_guid']->value())
->setTimestamp($row['timestamp']->time())
->setAmount((string) BigNumber::_($row['amount']))
->setTx($row['tx'])
->setStatus($row['status'] ?: '')
->setAddress($row['address'] ?: '')
->setAmount((string) BigNumber::_($row['amount']))
->setCompleted((bool) $row['completed'])
->setCompletedTx($row['completed_tx']);
->setCompletedTx($row['completed_tx'] ?: null)
->setGas((string) BigNumber::_($row['gas']))
->setStatus($row['status'] ?: '')
;
$requests[] = $request;
}
......@@ -117,15 +130,17 @@ class Repository
*/
public function add(Request $request)
{
$cql = "INSERT INTO withdrawals (user_guid, timestamp, amount, tx, status, completed, completed_tx) VALUES (?, ?, ?, ?, ?, ?, ?)";
$cql = "INSERT INTO withdrawals (user_guid, timestamp, tx, address, amount, completed, completed_tx, gas, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
$values = [
new Varint($request->getUserGuid()),
new Timestamp($request->getTimestamp()),
new Varint($request->getAmount()),
$request->getTx(),
$request->getStatus(),
(string) $request->getAddress(),
new Varint($request->getAmount()),
(bool) $request->isCompleted(),
$request->getCompletedTx(),
((string) $request->getCompletedTx()) ?: null,
new Varint($request->getGas()),
(string) $request->getStatus(),
];
$prepared = new Custom();
......
......@@ -20,7 +20,7 @@ use Minds\Traits\MagicAttributes;
* @method Request setAmount(string $amount)
* @method string getStatus()
* @method Request setStatus(string $status)
* @method bool getCompleted()
* @method bool isCompleted()
* @method Request setCompleted(bool $completed)
* @method int getTimestamp()
* @method Request setTimestamp(int $timestamp)
......
Please register or to comment