Skip to content

Commit

Permalink
[Greenfield]: Add a Greenfield -> BSC bridge transaction (#3398)
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan authored Aug 28, 2023
1 parent 2b425c5 commit 76595fd
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TestGreenfieldSigner {
}

@Test
fun GreenfieldTransactionSigning() {
fun GreenfieldTransactionSigningSend() {
// Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19

val key =
Expand Down Expand Up @@ -69,4 +69,53 @@ class TestGreenfieldSigner {
)
assertEquals(output.errorMessage, "")
}

@Test
fun GreenfieldTransactionSigningTransferOut() {
// Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7
// BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a

val key =
PrivateKey("9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0".toHexByteArray())

val msgTransferOut = Greenfield.Message.BridgeTransferOut.newBuilder().apply {
fromAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"
toAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"
amount = Greenfield.Amount.newBuilder().apply {
amount = "5670000000000000"
denom = "BNB"
}.build()
}.build()

val greenfieldFee = Greenfield.Fee.newBuilder().apply {
gas = 1200
addAmounts(Greenfield.Amount.newBuilder().apply {
amount = "6000000000000"
denom = "BNB"
})
}.build()

val signingInput = Greenfield.SigningInput.newBuilder().apply {
signingMode = Greenfield.SigningMode.Eip712
encodingMode = Greenfield.EncodingMode.Protobuf
accountNumber = 15560
ethChainId = "5600"
cosmosChainId = "greenfield_5600-1"
sequence = 7
fee = greenfieldFee
mode = Greenfield.BroadcastMode.SYNC
privateKey = ByteString.copyFrom(key.data())
addMessages(Greenfield.Message.newBuilder().apply {
bridgeTransferOut = msgTransferOut
})
}.build()

val output = AnySigner.sign(signingInput, CoinType.GREENFIELD, Greenfield.SigningOutput.parser())

assertEquals(
output.serialized,
"{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc\"}"
)
assertEquals(output.errorMessage, "")
}
}
File renamed without changes.
15 changes: 15 additions & 0 deletions src/Cosmos/Protobuf/greenfield_tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
syntax = "proto3";
package greenfield.bridge;

import "coin.proto";

// MsgTransferOut is the Msg/TransferOut request type.
// Original: https://github.com/bnb-chain/greenfield/blob/f1183e57caeb1ba0d28836b4ed2e64d693d2364d/proto/greenfield/bridge/tx.proto
message MsgTransferOut {
// from address
string from = 1;
// to address
string to = 2;
// transfer token amount
cosmos.base.v1beta1.Coin amount = 3;
}
1 change: 1 addition & 0 deletions src/Greenfield/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ static constexpr uint64_t TIMEOUT_HEIGHT = 0;
static constexpr auto* TIMEOUT_HEIGHT_STR = "0";
static constexpr auto* FEE_GRANTER = "";
static constexpr auto* MSG_SEND_TYPE = "/cosmos.bank.v1beta1.MsgSend";
static constexpr auto* MSG_TRANSFER_OUT_TYPE = "/greenfield.bridge.MsgTransferOut";

} // namespace TW::Greenfield
3 changes: 0 additions & 3 deletions src/Greenfield/Protobuf/.gitignore

This file was deleted.

41 changes: 27 additions & 14 deletions src/Greenfield/ProtobufSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
#include "Base64.h"
#include "Constants.h"
#include "Cosmos/Protobuf/bank_tx.pb.h"
#include "Cosmos/Protobuf/greenfield_ethsecp256k1.pb.h"
#include "Cosmos/Protobuf/greenfield_tx.pb.h"
#include "Cosmos/Protobuf/tx.pb.h"
#include "PrivateKey.h"
#include "Protobuf/ethsecp256k1.pb.h"

namespace TW::Greenfield {

Expand Down Expand Up @@ -43,22 +44,34 @@ static cosmos::base::v1beta1::Coin convertCoin(const Proto::Amount& amount) {
}

static SigningResult<Any> convertMessage(const Proto::Message& msg) {
// At this moment, we support `MsgSend` only.
if (!msg.has_send_coins_message()) {
return SigningResult<Any>::failure(Common::Proto::SigningError::Error_invalid_params);
}

Any any;
const auto& send = msg.send_coins_message();

auto msgSend = cosmos::bank::v1beta1::MsgSend();
msgSend.set_from_address(send.from_address());
msgSend.set_to_address(send.to_address());

for (auto i = 0; i < send.amounts_size(); ++i) {
*msgSend.add_amount() = convertCoin(send.amounts(i));
switch (msg.message_oneof_case()) {
case Proto::Message::kSendCoinsMessage: {
const auto& send = msg.send_coins_message();

auto msgSend = cosmos::bank::v1beta1::MsgSend();
msgSend.set_from_address(send.from_address());
msgSend.set_to_address(send.to_address());

for (auto i = 0; i < send.amounts_size(); ++i) {
*msgSend.add_amount() = convertCoin(send.amounts(i));
}
any.PackFrom(msgSend, ProtobufAnyNamespacePrefix);
break;
}
case Proto::Message::kBridgeTransferOut: {
const auto& transferOut = msg.bridge_transfer_out();

auto msgTransferOut = greenfield::bridge::MsgTransferOut();
msgTransferOut.set_from(transferOut.from_address());
msgTransferOut.set_to(transferOut.to_address());
*msgTransferOut.mutable_amount() = convertCoin(transferOut.amount());

any.PackFrom(msgTransferOut, ProtobufAnyNamespacePrefix);
break;
}
}
any.PackFrom(msgSend, ProtobufAnyNamespacePrefix);

return SigningResult<Any>::success(std::move(any));
}
Expand Down
76 changes: 75 additions & 1 deletion src/Greenfield/SignerEip712.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,39 @@ json msgSendTypes() {
return makeEip712Types(msgTypes);
}

// `TypeMsg1Amount` and `Msg1` type names are chosen automatically at the function:
// https://github.com/bnb-chain/greenfield-cosmos-sdk/blob/master/x/auth/tx/eip712.go#L90
// Please note that all parameters repeat the same scheme as `greenfield.bridge.MsgTransferOut`.
//
// Use `https://dcellar.io/` with MetaMask to get proper names of types and
json msgTransferOutTypes() {
// `MsgSend` specific types.
TypesMap msgTypes = {
// `TypeMsg1Amount` type represents `cosmos.bank.v1beta1.MsgSend.amount`.
{"TypeMsg1Amount", json::array({
namedParam("amount", "string"),
namedParam("denom", "string"),
})},
{"Msg1", json::array({
namedParam("amount", "TypeMsg1Amount"),
namedParam("from", "string"),
namedParam("to", "string"),
namedParam("type", "string")
})},
{"Tx", json::array({
namedParam("account_number", "uint256"),
namedParam("chain_id", "uint256"),
namedParam("fee", "Fee"),
namedParam("memo", "string"),
namedParam("msg1", "Msg1"),
namedParam("sequence", "uint256"),
namedParam("timeout_height", "uint256")
})}
};

return makeEip712Types(msgTypes);
}

} // namespace internal::types

json feeToJsonData(const Proto::SigningInput& input, const std::string& feePayer) {
Expand Down Expand Up @@ -128,6 +161,11 @@ json SignerEip712::wrapMsgSendToTypedData(const Proto::SigningInput& input, cons
});
}

std::string typePrefix = MSG_SEND_TYPE;
if (!msgSendProto.type_prefix().empty()) {
typePrefix = msgSendProto.type_prefix();
}

return json{
{"types", internal::types::msgSendTypes()},
{"primaryType", "Tx"},
Expand All @@ -149,16 +187,52 @@ json SignerEip712::wrapMsgSendToTypedData(const Proto::SigningInput& input, cons
};
}

// Returns a JSON data of the `EIP712Domain` type using `MsgSend` transaction.
json SignerEip712::wrapMsgTransferOutToTypedData(const Proto::SigningInput& input, const Proto::Message_BridgeTransferOut& msgTransferOut) {
std::string typePrefix = MSG_TRANSFER_OUT_TYPE;
if (!msgTransferOut.type_prefix().empty()) {
typePrefix = msgTransferOut.type_prefix();
}

return json{
{"types", internal::types::msgTransferOutTypes()},
{"primaryType", "Tx"},
{"domain", domainDataJson(input.eth_chain_id())},
{"message", json{
{"account_number", std::to_string(input.account_number())},
{"chain_id", input.eth_chain_id()},
{"fee", feeToJsonData(input, msgTransferOut.from_address())},
{"memo", input.memo()},
{"msg1", json{
{"amount", json{
{"amount", msgTransferOut.amount().amount()},
{"denom", msgTransferOut.amount().denom()}
}},
{"from", msgTransferOut.from_address()},
{"to", msgTransferOut.to_address()},
{"type", typePrefix}
}},
{"sequence", std::to_string(input.sequence())},
{"timeout_height", TIMEOUT_HEIGHT_STR}
}}
};
}

SigningResult<json> SignerEip712::wrapTxToTypedData(const Proto::SigningInput& input) {
if (input.messages_size() != 1) {
return SigningResult<json>::failure(Common::Proto::SigningError::Error_invalid_params);
}

switch(input.messages(0).message_oneof_case()) {
case Proto::Message::kBridgeTransferOut: {
const auto &msgTransferOut = input.messages(0).bridge_transfer_out();
return SigningResult<json>::success(wrapMsgTransferOutToTypedData(input, msgTransferOut));
}
case Proto::Message::kSendCoinsMessage:
default:
default: {
const auto& msgSendProto = input.messages(0).send_coins_message();
return SigningResult<json>::success(wrapMsgSendToTypedData(input, msgSendProto));
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/Greenfield/SignerEip712.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct SignerEip712 {
/// Packs the `MsgSend` Tx input in a EIP712 object.
static json wrapMsgSendToTypedData(const Proto::SigningInput& input, const Proto::Message_Send& msgSendProto);

/// Packs the `MsgTransferOut` Tx input in a EIP712 object.
static json wrapMsgTransferOutToTypedData(const Proto::SigningInput& input, const Proto::Message_BridgeTransferOut& msgTransferOut);

/// Prepares the given `signature` to make it Ethereum compatible.
static void prepareSignature(Data& signature);
};
Expand Down
13 changes: 13 additions & 0 deletions src/proto/Greenfield.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,25 @@ message Message {
string from_address = 1;
string to_address = 2;
repeated Amount amounts = 3;
// Optional. Default `cosmos.bank.v1beta1.MsgSend`.
string type_prefix = 4;
}

// greenfield/MsgTransferOut
// Used to transfer BNB Greenfield to BSC blockchain.
message BridgeTransferOut {
// In most cases, `from_address` and `to_address` are equal.
string from_address = 1;
string to_address = 2;
Amount amount = 3;
// Optional. Default `greenfield.bridge.MsgTransferOut`.
string type_prefix = 4;
}

// The payload message
oneof message_oneof {
Send send_coins_message = 1;
BridgeTransferOut bridge_transfer_out = 2;
}
}

Expand Down
46 changes: 45 additions & 1 deletion swift/Tests/Blockchains/GreenfieldTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import WalletCore
import XCTest

class GreenfieldTests: XCTestCase {
func testSign() {
func testSignSend() {
// Successfully broadcasted: https://greenfieldscan.com/tx/ED8508F3C174C4430B8EE718A6D6F0B02A8C516357BE72B1336CF74356529D19

let sendCoinsMessage = GreenfieldMessage.Send.with {
Expand Down Expand Up @@ -51,4 +51,48 @@ class GreenfieldTests: XCTestCase {
XCTAssertJSONEqual(output.serialized, "{\"tx_bytes\": \"CqwBCpEBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnEKKjB4QTgxNWFlMGIwNmRDODAzMTgxMjE3NDVCRTQwZTdGOGM2NjU0ZTlmMxIqMHg4ZGJENmM3RWRlOTA2NDZhNjFCYmM2NDk4MzFiN2MyOThCRmQzN0EwGhcKA0JOQhIQMTIzNDUwMDAwMDAwMDAwMBIWVHJ1c3QgV2FsbGV0IHRlc3QgbWVtbxJzClYKTQomL2Nvc21vcy5jcnlwdG8uZXRoLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAhm/mQgs8vzaqBLW66HrqQNv86PYTBgXyElU1OiuKD/sEgUKAwjIBRIZChQKA0JOQhINNjAwMDAwMDAwMDAwMBCwCRpBwbRb1qEAWwaqVfmp1Mn7iMi7wwV/oPi2J2eW9NBIdNoky+ZL+uegS/kY+funCOrqVZ+Kbol9/djAV+bQaNUB0xw=\", \"mode\": \"BROADCAST_MODE_SYNC\"}")
XCTAssertEqual(output.errorMessage, "")
}

func testSignTransferOut() {
// Successfully broadcasted Greenfield: https://greenfieldscan.com/tx/38C29C530A74946CFD22EE07DA642F5EF9399BC9AEA59EC56A9B5BE16DE16CE7
// BSC (parent transaction): https://testnet.bscscan.com/tx/0x7f73c8a362e14e58cb5e0ec17616afc50eff7aa398db472383a6d017c8a5861a

let transferOutMessage = GreenfieldMessage.BridgeTransferOut.with {
$0.fromAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"
$0.toAddress = "0x9d1d97aDFcd324Bbd603D3872BD78e04098510b1"
$0.amount = GreenfieldAmount.with {
$0.amount = "5670000000000000"
$0.denom = "BNB"
}
}

let message = GreenfieldMessage.with {
$0.bridgeTransferOut = transferOutMessage
}

let fee = GreenfieldFee.with {
$0.gas = 1200
$0.amounts = [GreenfieldAmount.with {
$0.amount = "6000000000000"
$0.denom = "BNB"
}]
}

let input = GreenfieldSigningInput.with {
$0.signingMode = .eip712;
$0.encodingMode = .protobuf
$0.mode = .sync
$0.accountNumber = 15560
$0.ethChainID = "5600"
$0.cosmosChainID = "greenfield_5600-1"
$0.sequence = 7
$0.messages = [message]
$0.fee = fee
$0.privateKey = Data(hexString: "9066aa168c379a403becb235c15e7129c133c244e56a757ab07bc369288bcab0")!
}

let output: GreenfieldSigningOutput = AnySigner.sign(input: input, coin: .greenfield)

XCTAssertJSONEqual(output.serialized, "{\"mode\":\"BROADCAST_MODE_SYNC\",\"tx_bytes\":\"CpkBCpYBCiEvZ3JlZW5maWVsZC5icmlkZ2UuTXNnVHJhbnNmZXJPdXQScQoqMHg5ZDFkOTdhREZjZDMyNEJiZDYwM0QzODcyQkQ3OGUwNDA5ODUxMGIxEioweDlkMWQ5N2FERmNkMzI0QmJkNjAzRDM4NzJCRDc4ZTA0MDk4NTEwYjEaFwoDQk5CEhA1NjcwMDAwMDAwMDAwMDAwEnUKWApNCiYvY29zbW9zLmNyeXB0by5ldGguZXRoc2VjcDI1NmsxLlB1YktleRIjCiECee80Bk2hDbBGPHBIBha6AgcD7DpFAm3ve+vSCC9db8gSBQoDCMgFGAcSGQoUCgNCTkISDTYwMDAwMDAwMDAwMDAQsAkaQc4DDByhu80Uy/M3sQePvAmhmbFWZeGq359rugtskEiXKfCzSB86XmBi+l+Q5ETDS2folMxbufHSE8gM4WBCHzgc\"}")
XCTAssertEqual(output.errorMessage, "")
}
}
Loading

0 comments on commit 76595fd

Please sign in to comment.