Skip to content

Commit

Permalink
Support sending f1 to typed f4 addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
cypt4 committed Jul 7, 2023
1 parent 51d9242 commit 89a6629
Show file tree
Hide file tree
Showing 17 changed files with 3,422 additions and 179 deletions.
3,023 changes: 3,023 additions & 0 deletions build/rust/Cargo.lock

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions components/brave_wallet/browser/fil_address_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ TEST(FilAddressUnitTest, From) {
address = "";
EXPECT_EQ(FilAddress::FromAddress(address).EncodeAsString(), address);
EXPECT_TRUE(FilAddress::FromAddress(address).IsEmpty());

address = "f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y";
fil_address = FilAddress::FromAddress(address);
EXPECT_EQ(fil_address.EncodeAsString(), address);
EXPECT_EQ(fil_address.protocol(), mojom::FilecoinAddressProtocol::DELEGATED);

address = "t410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y";
fil_address = FilAddress::FromAddress(address);
EXPECT_EQ(fil_address.EncodeAsString(), address);
EXPECT_EQ(fil_address.protocol(), mojom::FilecoinAddressProtocol::DELEGATED);

address = "f420frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y";
EXPECT_NE(FilAddress::FromAddress(address).EncodeAsString(), address);
}

TEST(FilAddressUnitTest, IsValidAddress) {
Expand All @@ -106,6 +119,12 @@ TEST(FilAddressUnitTest, IsValidAddress) {
EXPECT_FALSE(
FilAddress::IsValidAddress("t1h3n7rphclbmwyjcp6jrdiwlfcuwbroxy3jvg33q"));
EXPECT_FALSE(FilAddress::IsValidAddress(""));
EXPECT_TRUE(FilAddress::IsValidAddress(
"f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"));
EXPECT_TRUE(FilAddress::IsValidAddress(
"t410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"));
EXPECT_FALSE(FilAddress::IsValidAddress(
"f420frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"));
}

TEST(FilAddressUnitTest, FromPayload) {
Expand Down Expand Up @@ -133,6 +152,11 @@ TEST(FilAddressUnitTest, FromPayload) {
"f3wv3u6pmfi3j6pf3fhjkch372pkyg2tgtlb3jpu3eo6mnt7ttsft6x2xr54ct7fl2oz4o4t"
"pa4mvigcrayh4a"));

EXPECT_TRUE(ValidatePayload("8C60A3A9530DDD22F44C0B9C219E55E693335B51",
mojom::FilecoinAddressProtocol::DELEGATED,
mojom::kFilecoinMainnet,
"f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"));

auto empty_address = FilAddress::FromPayload(
{}, mojom::FilecoinAddressProtocol::SECP256K1, mojom::kFilecoinTestnet);
EXPECT_EQ(empty_address.EncodeAsString(), "");
Expand Down Expand Up @@ -247,6 +271,9 @@ TEST(FilAddressUnitTest, Comparison) {
FilAddress::FromAddress(
"t3wv3u6pmfi3j6pf3fhjkch372pkyg2tgtlb3jpu3eo6mnt7ttsft6x2xr54ct7fl2"
"oz4o4tpa4mvigcrayh4a"));
EXPECT_EQ(
FilAddress::FromAddress("f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"),
FilAddress::FromAddress("f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y"));
}

TEST(FilAddressUnitTest, GetBytes) {
Expand Down
31 changes: 24 additions & 7 deletions components/brave_wallet/browser/fil_requests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_util.h"
#include "brave/components/brave_wallet/browser/fil_transaction.h"
#include "brave/components/brave_wallet/browser/json_rpc_requests_helper.h"
#include "brave/components/brave_wallet/common/fil_address.h"
#include "brave/components/json/rs/src/lib.rs.h"

namespace brave_wallet {
Expand Down Expand Up @@ -40,7 +43,15 @@ std::string getEstimateGas(const std::string& from_address,
transaction.Set("Value", value);
transaction.Set("GasPremium", gas_premium.empty() ? "0" : gas_premium);
transaction.Set("GasFeeCap", gas_fee_cap.empty() ? "0" : gas_fee_cap);
transaction.Set("Method", 0);

auto parsed_addr = brave_wallet::FilAddress::FromAddress(to_address);
if (parsed_addr.protocol() == mojom::FilecoinAddressProtocol::DELEGATED) {
// https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0054.md#invokecontract-method-number-38444508371
transaction.Set("Method", "3844450837");
} else {
transaction.Set("Method", "0");
}

transaction.Set("Version", 0);
transaction.Set("Params", "");
transaction.Set("GasLimit", std::to_string(gas_limit));
Expand All @@ -65,7 +76,10 @@ std::string getEstimateGas(const std::string& from_address,
json = std::string(json::convert_string_value_to_int64("/params/0/GasLimit",
json.c_str(), false)
.c_str());
return std::string(json::convert_string_value_to_uint64("/params/0/Nonce",
json = std::string(json::convert_string_value_to_uint64("/params/0/Nonce",
json.c_str(), false)
.c_str());
return std::string(json::convert_string_value_to_uint64("/params/0/Method",
json.c_str(), false)
.c_str());
}
Expand All @@ -87,12 +101,12 @@ std::string getStateSearchMsgLimited(const std::string& cid, uint64_t period) {
}

absl::optional<std::string> getSendTransaction(const std::string& signed_tx) {
absl::optional<base::Value> parsed_tx = base::JSONReader::Read(signed_tx);
if (!parsed_tx || !parsed_tx->is_dict()) {
base::Value::List params;
auto signed_tx_value = FilTransaction::DeserializeSignedTx(signed_tx);
if (!signed_tx_value || !signed_tx_value->is_dict()) {
return absl::nullopt;
}
base::Value::List params;
params.Append(std::move(*parsed_tx));
params.Append(std::move(signed_tx_value.value()));

// TODO(cdesouza): This dictionary should be composed by GetJsonRpcDictionary.
base::Value::Dict dict;
Expand All @@ -101,7 +115,10 @@ absl::optional<std::string> getSendTransaction(const std::string& signed_tx) {
dict.Set("params", std::move(params));
dict.Set("id", 1);

return GetJSON(dict);
auto json = GetJSON(dict);
// Use substring replace instead of deserializing signed_tx to prevent
// long to double convertion.
return FilTransaction::ConvertSignedTxStringFieldsToInt64("/params/0", json);
}

} // namespace fil
Expand Down
78 changes: 78 additions & 0 deletions components/brave_wallet/browser/fil_requests_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,37 @@ TEST(FilRequestUnitTest, estimateGas) {
})");
}

TEST(FilRequestUnitTest, estimateGas_WhenSendToFEVM) {
CompareJSONs(
fil::getEstimateGas("from_address",
"t410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y",
"gas_premium", "gas_fee_cap", INT64_MAX, UINT64_MAX,
"max_fee", "value"),
R"({
"id": 1,
"jsonrpc": "2.0",
"method": "Filecoin.GasEstimateMessageGas",
"params": [
{
"From": "from_address",
"GasFeeCap": "gas_fee_cap",
"GasLimit": 9223372036854775807,
"GasPremium": "gas_premium",
"Method": 3844450837,
"Nonce": 18446744073709551615,
"Params": "",
"To": "t410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y",
"Value": "value",
"Version": 0
},
{
"MaxFee": "max_fee"
},
[]
]
})");
}

TEST(FilRequestUnitTest, getChainHead) {
EXPECT_EQ(fil::getChainHead(),
"{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"Filecoin.ChainHead\","
Expand All @@ -90,6 +121,53 @@ TEST(FilRequestUnitTest, getStateSearchMsgLimited) {
std::to_string(UINT64_MAX) + "]}");
}

TEST(FilRequestUnitTest, getSendTransaction_WhenSendToFEVM) {
auto send = fil::getSendTransaction(R"({
"Message": {
"From": "from",
"GasFeeCap": "3",
"GasLimit": 1,
"GasPremium": "2",
"Method": 3844450837,
"Params": "",
"Nonce": 1,
"To": "f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y",
"Value": "6",
"Version": 0
},
"Signature": {
"Type": 1,
"Data": "signed_tx"
}
})");
ASSERT_TRUE(send);
CompareJSONs(*send,
R"({
"id": 1,
"jsonrpc": "2.0",
"method": "Filecoin.MpoolPush",
"params": [{
"Message": {
"From": "from",
"GasFeeCap": "3",
"GasLimit": 1,
"GasPremium": "2",
"Method": 3844450837,
"Params": "",
"Nonce": 1,
"To": "f410frrqkhkktbxosf5cmboocdhsv42jtgw2rddjac2y",
"Value": "6",
"Version": 0
},
"Signature": {
"Type": 1,
"Data": "signed_tx"
}
}
]
})");
}

TEST(FilRequestUnitTest, getSendTransaction) {
auto send = fil::getSendTransaction(R"({
"Message": {
Expand Down
73 changes: 56 additions & 17 deletions components/brave_wallet/browser/fil_transaction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <string>
#include <utility>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
Expand Down Expand Up @@ -192,47 +193,86 @@ absl::optional<FilTransaction> FilTransaction::FromValue(
return tx;
}

absl::optional<std::string> FilTransaction::GetMessageToSign() const {
base::Value FilTransaction::GetMessageToSign() const {
auto value = ToValue();
value.Remove("MaxFee");
value.Set("Method", 0);
if (to_.protocol() == mojom::FilecoinAddressProtocol::DELEGATED) {
// https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0054.md#invokecontract-method-number-38444508371
value.Set("Method", "3844450837");
} else {
value.Set("Method", "0");
}
value.Set("Version", 0);
value.Set("Params", "");
const std::string* nonce_value = value.FindString("Nonce");
bool nonce_empty = nonce_value && nonce_value->empty();
// Nonce is empty usually for first transactions. We set it to 0 and skip
// conversion bellow to get correct signature.
// Nonce is empty usually for first transactions. We set it to 0
if (nonce_empty) {
value.Set("Nonce", 0);
value.Set("Nonce", "0");
}
return base::Value(std::move(value));
}

absl::optional<std::string> FilTransaction::GetMessageToSignJson() const {
std::string json;
base::JSONWriter::Write(value, &json);
if (!base::JSONWriter::Write(GetMessageToSign(), &json)) {
return absl::nullopt;
}

return ConvertMesssageStringFieldsToInt64("", json);
}

// static
absl::optional<std::string> FilTransaction::ConvertMesssageStringFieldsToInt64(
const std::string& path,
const std::string& json) {
std::string converted_json =
json::convert_string_value_to_int64("/GasLimit", json.c_str(), false)
json::convert_string_value_to_int64(path + "/GasLimit", json, true)
.c_str();
converted_json = json::convert_string_value_to_uint64(
path + "/Nonce", converted_json.c_str(), true)
.c_str();
converted_json = json::convert_string_value_to_uint64(
path + "/Method", converted_json.c_str(), true)
.c_str();
if (converted_json.empty()) {
return absl::nullopt;
}
if (!nonce_empty) {
converted_json = json::convert_string_value_to_uint64(
"/Nonce", converted_json.c_str(), false)
.c_str();
}
return converted_json;
}

// static
absl::optional<std::string> FilTransaction::ConvertSignedTxStringFieldsToInt64(
const std::string& path,
const std::string& json) {
return ConvertMesssageStringFieldsToInt64(path + "/Message", json);
}

// static
absl::optional<base::Value> FilTransaction::DeserializeSignedTx(
const std::string& signed_tx) {
std::string json =
json::convert_int64_value_to_string("/Message/GasLimit", signed_tx, true)
.c_str();
json =
json::convert_int64_value_to_string("/Message/Nonce", json, true).c_str();
json = json::convert_int64_value_to_string("/Message/Method", json, true)
.c_str();
return base::JSONReader::Read(json);
}

// https://spec.filecoin.io/algorithms/crypto/signatures/#section-algorithms.crypto.signatures
absl::optional<std::string> FilTransaction::GetSignedTransaction(
const std::vector<uint8_t>& private_key) const {
auto message = GetMessageToSign();
if (!message) {
auto message_json = GetMessageToSignJson();
if (!message_json) {
return absl::nullopt;
}
base::Value::Dict signature;
{
std::string data(filecoin::transaction_sign(
*message,
from_.network() == mojom::kFilecoinMainnet, *message_json,
rust::Slice<const uint8_t>{private_key.data(), private_key.size()}));
if (data.empty()) {
return absl::nullopt;
Expand All @@ -246,14 +286,13 @@ absl::optional<std::string> FilTransaction::GetSignedTransaction(
: SigType::BLSSigType;
signature.Set("Type", sig_type);
base::Value::Dict dict;
dict.Set("Message", "{message}");
dict.Set("Message", std::move(message));
dict.Set("Signature", std::move(signature));
std::string json;
if (!base::JSONWriter::Write(dict, &json)) {
return absl::nullopt;
}
base::ReplaceFirstSubstringAfterOffset(&json, 0, "\"{message}\"", *message);
return json;
return ConvertMesssageStringFieldsToInt64("/Message", json);
}

mojom::FilTxDataPtr FilTransaction::ToFilTxData() const {
Expand Down
17 changes: 16 additions & 1 deletion components/brave_wallet/browser/fil_transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,29 @@ class FilTransaction {
void set_gas_limit(int64_t gas_limit) { gas_limit_ = gas_limit; }
void set_max_fee(const std::string& max_fee) { max_fee_ = max_fee; }

absl::optional<std::string> GetMessageToSign() const;
absl::optional<std::string> GetMessageToSignJson() const;
base::Value GetMessageToSign() const;

base::Value::Dict ToValue() const;
mojom::FilTxDataPtr ToFilTxData() const;
absl::optional<std::string> GetSignedTransaction(
const std::vector<uint8_t>& private_key) const;
static absl::optional<FilTransaction> FromValue(
const base::Value::Dict& value);

// Deserializes JSON which contains value, provided by SignTransaction
// Wraps uint64_t fields to string
static absl::optional<base::Value> DeserializeSignedTx(
const std::string& signed_tx);
// Finds signed tx JSON by path and converts some string fields to uint64 form
static absl::optional<std::string> ConvertMesssageStringFieldsToInt64(
const std::string& path,
const std::string& json);
// Finds signed tx JSON by path and converts some string fields to uint64 form
static absl::optional<std::string> ConvertSignedTxStringFieldsToInt64(
const std::string& path,
const std::string& json);

private:
bool IsEqual(const FilTransaction& tx) const;

Expand Down
Loading

0 comments on commit 89a6629

Please sign in to comment.