220 lines
8.3 KiB
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);
|
||
|
|
}
|
||
|
|
}
|