Contractless-PHP-RPC/src/Transaction/StorageBuilder.php

220 lines
8.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Contractless\Rpc\Transaction;
use Contractless\Rpc\Crypto\CryptoInterface;
use Contractless\Rpc\Exception\ProtocolException;
use Contractless\Rpc\Protocol\Address;
use Contractless\Rpc\Protocol\Binary;
use Contractless\Rpc\Protocol\RequestBuilder;
use Contractless\Rpc\Wallet\Credentials;
final class StorageBuilder
{
use SignsTransactions;
public function __construct(CryptoInterface $crypto, Credentials $credentials)
{
$this->crypto = $crypto;
$this->credentials = $credentials;
}
public function createKey(
string $address,
int $fee,
?int $timestamp = null,
): SignedTransaction {
$timestamp ??= time();
$unsigned = [
'txtype' => 100, 'time' => $timestamp, 'address' => $address, 'txfee' => $fee,
];
return $this->sign(
$unsigned,
Binary::u8(100) . Binary::u32($timestamp)
. Address::toBytes($address) . Binary::u64($fee),
);
}
public function boolean(
string $storageKey,
string $key,
bool $value,
string $address,
int $fee,
?int $timestamp = null,
): SignedTransaction {
return $this->value(101, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function u8(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(102, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function u16(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(103, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function u32(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(104, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function u64(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(105, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function i8(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(108, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function i16(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(109, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function i32(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(110, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function i64(string $storageKey, string $key, int $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value(111, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function u128(string $storageKey, string $key, string $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value128(106, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function i128(string $storageKey, string $key, string $value, string $address, int $fee, ?int $timestamp = null): SignedTransaction
{
return $this->value128(112, $storageKey, $key, $value, $address, $fee, $timestamp);
}
public function string(
string $storageKey,
string $key,
string $value,
string $address,
string $previousHash,
int $fee,
?int $timestamp = null,
): SignedTransaction {
$timestamp ??= time();
$key = Binary::fixedText($key, 50, 'Storage data key');
$value = Binary::fixedText($value, 180, 'Storage string value');
$unsigned = [
'txtype' => 107, 'time' => $timestamp, 'storagekey' => $storageKey,
'key' => $key, 'value' => $value, 'address' => $address,
'previous' => $previousHash, 'txfee' => $fee,
];
$binary = Binary::u8(107) . Binary::u32($timestamp)
. RequestBuilder::hash($storageKey, 'Storage key') . $key . $value
. Address::toBytes($address)
. RequestBuilder::hash($previousHash, 'Previous string transaction hash')
. Binary::u64($fee);
return $this->sign($unsigned, $binary);
}
public function delete(
string $storageKey,
string $key,
string $address,
int $fee,
?int $timestamp = null,
): SignedTransaction {
$timestamp ??= time();
$key = Binary::fixedText($key, 50, 'Storage data key');
$unsigned = [
'txtype' => 113, 'time' => $timestamp, 'storagekey' => $storageKey,
'key' => $key, 'address' => $address, 'txfee' => $fee,
];
$binary = Binary::u8(113) . Binary::u32($timestamp)
. RequestBuilder::hash($storageKey, 'Storage key') . $key
. Address::toBytes($address) . Binary::u64($fee);
return $this->sign($unsigned, $binary);
}
private function value(
int $type,
string $storageKey,
string $key,
bool|int $value,
string $address,
int $fee,
?int $timestamp,
): SignedTransaction {
$timestamp ??= time();
$key = Binary::fixedText($key, 50, 'Storage data key');
$valueBytes = match ($type) {
101 => Binary::u8($value === true ? 1 : 0),
102 => Binary::u8($this->integer($value)),
103 => Binary::u16($this->integer($value)),
104 => Binary::u32($this->integer($value)),
105 => Binary::u64($this->integer($value)),
108 => Binary::i8($this->integer($value)),
109 => Binary::i16($this->integer($value)),
110 => Binary::i32($this->integer($value)),
111 => Binary::i64($this->integer($value)),
default => throw new ProtocolException("Unsupported storage value type $type."),
};
$unsigned = [
'txtype' => $type, 'time' => $timestamp, 'storagekey' => $storageKey,
'key' => $key, 'value' => $value, 'address' => $address, 'txfee' => $fee,
];
$binary = Binary::u8($type) . Binary::u32($timestamp)
. RequestBuilder::hash($storageKey, 'Storage key') . $key . $valueBytes
. Address::toBytes($address) . Binary::u64($fee);
return $this->sign($unsigned, $binary);
}
private function integer(bool|int $value): int
{
if (!is_int($value)) {
throw new ProtocolException('Numeric storage values must be integers.');
}
return $value;
}
private function value128(
int $type,
string $storageKey,
string $key,
string $value,
string $address,
int $fee,
?int $timestamp,
): SignedTransaction {
$timestamp ??= time();
$key = Binary::fixedText($key, 50, 'Storage data key');
$value = trim($value);
$valueBytes = $type === 106 ? Binary::u128($value) : Binary::i128($value);
// serde_json writes Rust u128/i128 values as unquoted decimal numbers.
$json = '{"txtype":' . $type
. ',"time":' . $timestamp
. ',"storagekey":' . json_encode($storageKey, JSON_THROW_ON_ERROR)
. ',"key":' . json_encode($key, JSON_THROW_ON_ERROR)
. ',"value":' . $value
. ',"address":' . json_encode($address, JSON_THROW_ON_ERROR)
. ',"txfee":' . $fee . '}';
$digest = $this->crypto->skein256($json);
$signature = $this->crypto->sign(
$digest,
$this->credentials->privateKey,
$this->credentials->publicKey,
);
$binary = Binary::u8($type) . Binary::u32($timestamp)
. RequestBuilder::hash($storageKey, 'Storage key') . $key . $valueBytes
. Address::toBytes($address) . Binary::u64($fee);
return new SignedTransaction($binary . $signature, $signature, $json);
}
}