Skip to content

Commit df1843f

Browse files
[MultiversX]: Add support for relayed (V3) transactions (#4243)
* For MultiversX, add support for Relayed V3 transactions. * Fix after review: getters of extraGasLimit*.
1 parent 78b0536 commit df1843f

13 files changed

Lines changed: 255 additions & 9 deletions

File tree

android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/multiversx/TestMultiversXSigner.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,38 @@ class TestMultiversXSigner {
8989
assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","options":2,"guardian":"$carolBech32"}""", output.encoded)
9090
}
9191

92+
@Test
93+
fun signGenericActionWithRelayer() {
94+
val privateKey = ByteString.copyFrom(PrivateKey(aliceSeedHex.toHexByteArray()).data())
95+
96+
val accounts = MultiversX.Accounts.newBuilder()
97+
.setSenderNonce(42)
98+
.setSender(aliceBech32)
99+
.setReceiver(bobBech32)
100+
.setRelayer(carolBech32)
101+
.build()
102+
103+
val genericAction = MultiversX.GenericAction.newBuilder()
104+
.setAccounts(accounts)
105+
.setValue("1000000000000000000")
106+
.setVersion(2)
107+
.build()
108+
109+
val signingInput = MultiversX.SigningInput.newBuilder()
110+
.setGenericAction(genericAction)
111+
.setGasPrice(1000000000)
112+
.setGasLimit(100000)
113+
.setChainId("1")
114+
.setPrivateKey(privateKey)
115+
.build()
116+
117+
val output = AnySigner.sign(signingInput, CoinType.MULTIVERSX, MultiversX.SigningOutput.parser())
118+
val expectedSignature = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003"
119+
120+
assertEquals(expectedSignature, output.signature)
121+
assertEquals("""{"nonce":42,"value":"1000000000000000000","receiver":"$bobBech32","sender":"$aliceBech32","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"$expectedSignature","relayer":"$carolBech32"}""", output.encoded)
122+
}
123+
92124
@Test
93125
fun signGenericActionUndelegate() {
94126
// Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65

src/MultiversX/Serialization.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ std::map<std::string, int> fields_order{
2323
{"version", 11},
2424
{"signature", 12},
2525
{"options", 13},
26-
{"guardian", 14}};
26+
{"guardian", 14},
27+
{"relayer", 15}};
2728

2829
struct FieldsSorter {
2930
bool operator()(const std::string& lhs, const std::string& rhs) const {
@@ -69,6 +70,10 @@ sorted_json preparePayload(const MultiversX::Transaction& transaction) {
6970
payload["guardian"] = json(transaction.guardian);
7071
}
7172

73+
if (!transaction.relayer.empty()) {
74+
payload["relayer"] = json(transaction.relayer);
75+
}
76+
7277
return payload;
7378
}
7479

src/MultiversX/Transaction.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
namespace TW::MultiversX {
88

99
Transaction::Transaction()
10-
: nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), guardian(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) {
10+
: nonce(0), sender(""), senderUsername(""), receiver(""), receiverUsername(""), guardian(""), relayer(""), value("0"), data(""), gasPrice(0), gasLimit(0), chainID(""), version(0), options(TransactionOptions::Default) {
1111
}
1212

1313
bool Transaction::hasGuardian() const {
1414
return !guardian.empty();
1515
}
1616

17+
bool Transaction::hasRelayer() const {
18+
return !relayer.empty();
19+
}
20+
1721
} // namespace TW::MultiversX

src/MultiversX/Transaction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Transaction {
2626
std::string receiver;
2727
std::string receiverUsername;
2828
std::string guardian;
29+
std::string relayer;
2930
std::string value;
3031
std::string data;
3132
uint64_t gasPrice;
@@ -37,6 +38,7 @@ class Transaction {
3738
Transaction();
3839

3940
bool hasGuardian() const;
41+
bool hasRelayer() const;
4042
};
4143

4244
} // namespace TW::MultiversX

src/MultiversX/TransactionFactory.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Transaction TransactionFactory::fromGenericAction(const Proto::SigningInput& inp
4141
transaction.receiver = action.accounts().receiver();
4242
transaction.receiverUsername = action.accounts().receiver_username();
4343
transaction.guardian = action.accounts().guardian();
44+
transaction.relayer = action.accounts().relayer();
4445
transaction.value = action.value();
4546
transaction.data = action.data();
4647
transaction.gasLimit = input.gas_limit();
@@ -62,6 +63,7 @@ Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& inpu
6263
transaction.receiver = transfer.accounts().receiver();
6364
transaction.receiverUsername = transfer.accounts().receiver_username();
6465
transaction.guardian = transfer.accounts().guardian();
66+
transaction.relayer = transfer.accounts().relayer();
6567
transaction.value = transfer.amount();
6668
transaction.data = transfer.data();
6769
transaction.gasPrice = coalesceGasPrice(input.gas_price());
@@ -70,7 +72,7 @@ Transaction TransactionFactory::fromEGLDTransfer(const Proto::SigningInput& inpu
7072
transaction.options = decideOptions(transaction);
7173

7274
// Estimate & set gasLimit:
73-
uint64_t estimatedGasLimit = computeGasLimit(0, 0, transaction.hasGuardian());
75+
uint64_t estimatedGasLimit = computeGasLimit(0, 0, transaction.hasGuardian(), transaction.hasRelayer());
7476
transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit);
7577

7678
return transaction;
@@ -90,6 +92,7 @@ Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& inpu
9092
transaction.receiver = transfer.accounts().receiver();
9193
transaction.receiverUsername = transfer.accounts().receiver_username();
9294
transaction.guardian = transfer.accounts().guardian();
95+
transaction.relayer = transfer.accounts().relayer();
9396
transaction.value = "0";
9497
transaction.data = data;
9598
transaction.gasPrice = coalesceGasPrice(input.gas_price());
@@ -99,7 +102,7 @@ Transaction TransactionFactory::fromESDTTransfer(const Proto::SigningInput& inpu
99102

100103
// Estimate & set gasLimit:
101104
uint64_t executionGasLimit = this->config.getGasCostESDTTransfer() + this->config.getAdditionalGasForESDTTransfer();
102-
uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian());
105+
uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian(), transaction.hasRelayer());
103106
transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit);
104107

105108
return transaction;
@@ -120,6 +123,7 @@ Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& i
120123
transaction.sender = transfer.accounts().sender();
121124
transaction.receiver = transfer.accounts().sender();
122125
transaction.guardian = transfer.accounts().guardian();
126+
transaction.relayer = transfer.accounts().relayer();
123127
transaction.value = "0";
124128
transaction.data = data;
125129
transaction.gasPrice = coalesceGasPrice(input.gas_price());
@@ -129,20 +133,24 @@ Transaction TransactionFactory::fromESDTNFTTransfer(const Proto::SigningInput& i
129133

130134
// Estimate & set gasLimit:
131135
uint64_t executionGasLimit = this->config.getGasCostESDTNFTTransfer() + this->config.getAdditionalGasForESDTNFTTransfer();
132-
uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian());
136+
uint64_t estimatedGasLimit = computeGasLimit(data.size(), executionGasLimit, transaction.hasGuardian(), transaction.hasRelayer());
133137
transaction.gasLimit = coalesceGasLimit(input.gas_limit(), estimatedGasLimit);
134138

135139
return transaction;
136140
}
137141

138-
uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian) {
142+
uint64_t TransactionFactory::computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian, bool hasRelayer) {
139143
uint64_t dataMovementGasLimit = this->config.getMinGasLimit() + this->config.getGasPerDataByte() * dataLength;
140144
uint64_t gasLimit = dataMovementGasLimit + executionGasLimit;
141145

142146
if (hasGuardian) {
143147
gasLimit += this->config.getExtraGasLimitForGuardedTransaction();
144148
}
145149

150+
if (hasRelayer) {
151+
gasLimit += this->config.getExtraGasLimitForRelayedTransaction();
152+
}
153+
146154
return gasLimit;
147155
}
148156

src/MultiversX/TransactionFactory.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class TransactionFactory {
4646
Transaction fromESDTNFTTransfer(const Proto::SigningInput& input);
4747

4848
private:
49-
uint64_t computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian);
49+
uint64_t computeGasLimit(size_t dataLength, uint64_t executionGasLimit, bool hasGuardian, bool hasRelayer);
5050
uint64_t coalesceGasLimit(uint64_t providedGasLimit, uint64_t estimatedGasLimit);
5151
uint64_t coalesceGasPrice(uint64_t gasPrice);
5252
std::string coalesceChainId(std::string chainID);

src/MultiversX/TransactionFactoryConfig.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,21 @@ void TransactionFactoryConfig::setMinGasLimit(uint32_t value) {
3737
}
3838

3939
uint32_t TransactionFactoryConfig::getExtraGasLimitForGuardedTransaction() const {
40-
return this->minGasLimit;
40+
return this->extraGasLimitForGuardedTransaction;
4141
}
4242

4343
void TransactionFactoryConfig::setExtraGasLimitForGuardedTransaction(uint32_t value) {
4444
this->extraGasLimitForGuardedTransaction = value;
4545
}
4646

47+
uint32_t TransactionFactoryConfig::getExtraGasLimitForRelayedTransaction() const {
48+
return this->extraGasLimitForRelayedTransaction;
49+
}
50+
51+
void TransactionFactoryConfig::setExtraGasLimitForRelayedTransaction(uint32_t value) {
52+
this->extraGasLimitForRelayedTransaction = value;
53+
}
54+
4755
uint64_t TransactionFactoryConfig::getMinGasPrice() const {
4856
return this->minGasPrice;
4957
}
@@ -97,6 +105,7 @@ TransactionFactoryConfig TransactionFactoryConfig::GetByTimestamp(uint64_t times
97105
config.setGasPerDataByte(1500);
98106
config.setMinGasLimit(50000);
99107
config.setExtraGasLimitForGuardedTransaction(50000);
108+
config.setExtraGasLimitForRelayedTransaction(50000);
100109
config.setMinGasPrice(1000000000);
101110
config.setGasCostESDTTransfer(200000);
102111
config.setGasCostESDTNFTTransfer(200000);

src/MultiversX/TransactionFactoryConfig.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class TransactionFactoryConfig {
1717
uint32_t gasPerDataByte;
1818
uint32_t minGasLimit;
1919
uint32_t extraGasLimitForGuardedTransaction;
20+
uint32_t extraGasLimitForRelayedTransaction;
2021
uint64_t minGasPrice;
2122

2223
/// GasSchedule entries of interest (only one at this moment), according to: https://github.com/multiversx/mx-chain-mainnet-config/blob/master/gasSchedules.
@@ -47,6 +48,9 @@ class TransactionFactoryConfig {
4748
uint32_t getExtraGasLimitForGuardedTransaction() const;
4849
void setExtraGasLimitForGuardedTransaction(uint32_t value);
4950

51+
uint32_t getExtraGasLimitForRelayedTransaction() const;
52+
void setExtraGasLimitForRelayedTransaction(uint32_t value);
53+
5054
uint64_t getMinGasPrice() const;
5155
void setMinGasPrice(uint64_t value);
5256

src/proto/MultiversX.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ message Accounts {
9292

9393
// Guardian address
9494
string guardian = 6;
95+
96+
// Relayer address
97+
string relayer = 7;
9598
}
9699

97100
// Input data necessary to create a signed transaction.

swift/Tests/Blockchains/MultiversXTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,35 @@ class MultiversXTests: XCTestCase {
8080
XCTAssertEqual(output.signature, expectedSignature)
8181
XCTAssertEqual(output.encoded, expectedEncoded)
8282
}
83+
84+
func testSignGenericActionWithRelayer() {
85+
let privateKey = PrivateKey(data: Data(hexString: aliceSeedHex)!)!
86+
87+
let input = MultiversXSigningInput.with {
88+
$0.genericAction = MultiversXGenericAction.with {
89+
$0.accounts = MultiversXAccounts.with {
90+
$0.senderNonce = 42
91+
$0.sender = aliceBech32
92+
$0.receiver = bobBech32
93+
$0.relayer = carolBech32
94+
}
95+
$0.value = "1000000000000000000"
96+
$0.data = ""
97+
$0.version = 2
98+
}
99+
$0.gasPrice = 1000000000
100+
$0.gasLimit = 100000
101+
$0.chainID = "1"
102+
$0.privateKey = privateKey.data
103+
}
104+
105+
let output: MultiversXSigningOutput = AnySigner.sign(input: input, coin: .multiversX)
106+
let expectedSignature = "f0137ce0303a33814691975598dab3b82bb91b017aa251640a48827edc48048aa0f916dd3e7915dd3be27db3304fc238a719123b6ae2285731ab24b794665003"
107+
let expectedEncoded = #"{"nonce":42,"value":"1000000000000000000","receiver":"\#(bobBech32)","sender":"\#(aliceBech32)","gasPrice":1000000000,"gasLimit":100000,"chainID":"1","version":2,"signature":"\#(expectedSignature)","relayer":"\#(carolBech32)"}"#
108+
109+
XCTAssertEqual(output.signature, expectedSignature)
110+
XCTAssertEqual(output.encoded, expectedEncoded)
111+
}
83112

84113
func testSignGenericActionUndelegate() {
85114
// Successfully broadcasted https://explorer.multiversx.com/transactions/3301ae5a6a77f0ab9ceb5125258f12539a113b0c6787de76a5c5867f2c515d65

0 commit comments

Comments
 (0)