Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(abstract-eth): support txn bulding for v4 wallet on eth chain #4423

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
/modules/account-lib/ @BitGo/ethalt-team
/modules/sdk-coin-celo/ @BitGo/ethalt-team
/modules/sdk-coin-etc/ @BitGo/ethalt-team
/modules/abstract-eth/ @BitGo/ethalt-team
/modules/sdk-coin-eth/ @BitGo/ethalt-team
/modules/sdk-coin-eth2/ @BitGo/ethalt-team
/modules/sdk-coin-rbtc/ @BitGo/ethalt-team
Expand Down
1 change: 1 addition & 0 deletions modules/abstract-eth/src/lib/transactionBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
throw new BuildTransactionError('Missing transfer information');
}
const chainId = this._common.chainIdBN().toString();
this._transfer.walletVersion(this._walletVersion);
// This change is made to support new contracts with different encoding type
return this._transfer.signAndBuild(chainId, this.coinUsesNonPackedEncodingForTxData());
}
Expand Down
20 changes: 17 additions & 3 deletions modules/abstract-eth/src/lib/transferBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class TransferBuilder {
private _coin: Readonly<BaseCoin>;
private _chainId?: string;
private _coinUsesNonPackedEncodingForTxData?: boolean;
private _walletVersion?: number;

constructor(serializedData?: string) {
if (serializedData) {
Expand Down Expand Up @@ -48,6 +49,11 @@ export class TransferBuilder {
return this;
}

walletVersion(version: number): TransferBuilder {
this._walletVersion = version;
return this;
}

data(additionalData: string): TransferBuilder {
this._signature = this._EMPTY_HEX_VALUE;
this._data = additionalData;
Expand Down Expand Up @@ -95,7 +101,7 @@ export class TransferBuilder {
throw new InvalidParameterValueError('Invalid expiration time');
}

signAndBuild(chainId?: string, coinUsesNonPackedEncodingForTxData?: boolean): string {
signAndBuild(chainId: string, coinUsesNonPackedEncodingForTxData?: boolean): string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made chainId mandatory

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if this should be considered as a breaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we always pass chainId here

return this._transfer.signAndBuild(chainId, coinUsesNonPackedEncodingForTxData);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there we always pass chainId, so it won't affect wallet platform and WRW most likely. But if some of our user is directly using signAndBuild method, so I was thinking it as a breaking change from that perspective

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the API changes and is exposed publicly, it is a breaking change. Please mark the commit as such.

this._chainId = chainId;

// If the coin uses non-packed encoding for tx data, the operation hash is calculated differently
Expand Down Expand Up @@ -157,11 +163,12 @@ export class TransferBuilder {

protected getOperationData(): (string | Buffer)[][] {
let operationData;
const prefix = this.getOperationHashPrefix();
if (this._tokenContractAddress !== undefined) {
operationData = [
['string', 'address', 'uint', 'address', 'uint', 'uint'],
[
this.getTokenOperationHashPrefix(),
prefix,
new BN(ethUtil.stripHexPrefix(this._toAddress), 16),
this._amount,
new BN(ethUtil.stripHexPrefix(this._tokenContractAddress), 16),
Expand All @@ -176,7 +183,7 @@ export class TransferBuilder {
operationData = [
['string', 'address', 'uint', 'bytes', 'uint', 'uint'],
[
this.getNativeOperationHashPrefix(),
prefix,
toAddress,
this._amount,
Buffer.from(ethUtil.padToEven(ethUtil.stripHexPrefix(this._data)) || '', 'hex'),
Expand All @@ -188,6 +195,13 @@ export class TransferBuilder {
return operationData;
}

private getOperationHashPrefix(): string {
if (this._walletVersion === 4) {
return this._tokenContractAddress ? `${this._chainId}-ERC20` : `${this._chainId}`;
}
return this._tokenContractAddress ? this.getTokenOperationHashPrefix() : this.getNativeOperationHashPrefix();
}

/**
* Get the prefix used in generating an operation hash for sending tokens
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export abstract class BaseNFTTransferBuilder {
protected _coin: Readonly<BaseCoin>;
protected _nativeCoinOperationHashPrefix?: string;
protected _chainId?: string;
protected _walletVersion?: number;

public abstract build(): string;

Expand All @@ -38,6 +39,11 @@ export abstract class BaseNFTTransferBuilder {
throw new InvalidParameterValueError('Invalid expiration time');
}

walletVersion(version: number): this {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This function scheme sounds like a getter opposed to a setter. Maybe setWalletVersion?

this._walletVersion = version;
return this;
}

key(signKey: string): this {
this._signKey = signKey;
return this;
Expand Down Expand Up @@ -101,6 +107,9 @@ export abstract class BaseNFTTransferBuilder {
* @returns the string prefix
*/
protected getNativeOperationHashPrefix(): string {
if (this._walletVersion === 4) {
return this._chainId ?? 'ETHER';
}
return this._nativeCoinOperationHashPrefix ?? this._chainId ?? 'ETHER';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class ERC1155TransferBuilder extends BaseNFTTransferBuilder {
return this;
}

signAndBuild(chainId?: string): string {
signAndBuild(chainId: string): string {
this._chainId = chainId;
const hasMandatoryFields = this.hasMandatoryFields();
if (hasMandatoryFields) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class ERC721TransferBuilder extends BaseNFTTransferBuilder {
return contractCall.serialize();
}

signAndBuild(chainId?: string): string {
signAndBuild(chainId: string): string {
this._chainId = chainId;
if (this.hasMandatoryFields()) {
this._data = this.build();
Expand Down
10 changes: 6 additions & 4 deletions modules/abstract-eth/test/unit/transferBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from 'assert';
import should from 'should';
import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics';
import { KeyPair, TransferBuilder } from '../../src';

describe('Eth send multi sig builder', function () {
Expand All @@ -19,6 +20,7 @@ describe('Eth send multi sig builder', function () {

ethLikeCoins.forEach((coin) => {
it('should fail with an invalid key', () => {
const staticsCoin = coins.get(coin) as unknown as EthLikeNetwork;
const builder = new TransferBuilder()
.coin(coin)
.expirationTime(1590078260)
Expand All @@ -27,7 +29,7 @@ describe('Eth send multi sig builder', function () {
.contractSequenceId(2)
.key('invalidkey');
should(() => {
builder.signAndBuild();
builder.signAndBuild(`${staticsCoin.chainId}`);
}).throw('private key length is invalid');
});
});
Expand Down Expand Up @@ -64,17 +66,17 @@ describe('Eth send multi sig builder', function () {

it('should fail if a sequenceId param is missing', () => {
const builder = new TransferBuilder().amount(amount).to(toAddress).key(key);
assert.throws(() => builder.signAndBuild());
assert.throws(() => builder.signAndBuild(''));
});

it('should fail if a destination param is missing', () => {
const builder = new TransferBuilder().amount(amount).contractSequenceId(2).key(key);
assert.throws(() => builder.signAndBuild());
assert.throws(() => builder.signAndBuild(''));
});

it('should fail if a amount param is missing', () => {
const builder = new TransferBuilder().to(toAddress).contractSequenceId(2).key(key);
assert.throws(() => builder.signAndBuild());
assert.throws(() => builder.signAndBuild(''));
});
});
});
19 changes: 10 additions & 9 deletions modules/sdk-coin-arbeth/test/unit/transferBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import should from 'should';
import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics';
import { KeyPair, TransferBuilder } from '../../src';
import * as testData from '../resources';

Expand All @@ -8,7 +9,7 @@ describe('Arbeth send multi sig builder', function () {
'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2';
const key = new KeyPair({ prv: xprv }).getKeys().prv as string;
const amount = '100000000000000000'; // equivalent to 0.1 ether

const coin = coins.get('tarbeth') as unknown as EthLikeNetwork;
describe('should build', () => {
it('native coin transfer should succeed', async () => {
const builder = new TransferBuilder()
Expand All @@ -19,7 +20,7 @@ describe('Arbeth send multi sig builder', function () {
.contractSequenceId(2)
.key(key)
.data('0x');
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_DATA);
});

Expand All @@ -32,7 +33,7 @@ describe('Arbeth send multi sig builder', function () {
.contractSequenceId(0)
.key(key)
.data('0x');
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_SEQUENCE_ZERO_DATA);
});

Expand All @@ -45,7 +46,7 @@ describe('Arbeth send multi sig builder', function () {
.contractSequenceId(2)
.key(key)
.data('0x');
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_AMOUNT_ZERO_DATA);
});

Expand All @@ -57,7 +58,7 @@ describe('Arbeth send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_ARBETH_LINK_DATA);
});

Expand All @@ -69,7 +70,7 @@ describe('Arbeth send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(0)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA);
});

Expand All @@ -81,7 +82,7 @@ describe('Arbeth send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA);
});

Expand All @@ -92,14 +93,14 @@ describe('Arbeth send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.data('0x');
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_NO_KEY_DATA);
});

it('should build from a non signed serialized data', () => {
const builder = new TransferBuilder(testData.SEND_FUNDS_NO_KEY_DATA);
builder.coin('tarbeth').key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_DATA);
});
});
Expand Down
5 changes: 3 additions & 2 deletions modules/sdk-coin-avaxc/test/unit/transferBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getBuilder } from './getBuilder';
import * as testData from '../resources/avaxc';
import { TransactionType } from '@bitgo/sdk-core';
import { decodeTokenTransferData } from '@bitgo/sdk-coin-eth';
import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics';

const amount = '20000';
const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5';
Expand All @@ -20,7 +21,7 @@ const tokensNames = [
'avaxc:link',
'tavaxc:link',
];

const coin = coins.get('avaxc') as unknown as EthLikeNetwork;
let txBuilder: TransactionBuilder;
const contractAddress = testData.TEST_ACCOUNT.ethAddress;
const initTxBuilder = (): void => {
Expand All @@ -44,7 +45,7 @@ describe('AVAXERC20 Tokens', () => {
.to(toAddress)
.contractSequenceId(2)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
const decode = decodeTokenTransferData(result);
should.equal(decode.amount, '20000');
should.equal(decode.expireTime, 1590078260);
Expand Down
13 changes: 7 additions & 6 deletions modules/sdk-coin-celo/test/unit/transferBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import should from 'should';
import { KeyPair, TransferBuilder } from '../../src';
import * as testData from '../resources/celo';
import { coins, EthereumNetwork as EthLikeNetwork } from '@bitgo/statics';

describe('Celo send multi sig builder', function () {
const toAddress = '0x7325A3F7d4f9E86AE62Cf742426078C3755730d5';
const xprv =
'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2';
const key = new KeyPair({ prv: xprv }).getKeys().prv as string;
const amount = '100000000000000000'; // equivalent to 0.1 ether

const coin = coins.get('tcelo') as unknown as EthLikeNetwork;
describe('should build', () => {
it('celo token transfer should succeed', async () => {
const builder = new TransferBuilder()
Expand All @@ -18,7 +19,7 @@ describe('Celo send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_TOKEN_DATA);
});

Expand All @@ -30,7 +31,7 @@ describe('Celo send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(0)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_TOKEN_SEQUENCE_ZERO_DATA);
});

Expand All @@ -42,7 +43,7 @@ describe('Celo send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_TOKEN_AMOUNT_ZERO_DATA);
});

Expand All @@ -54,14 +55,14 @@ describe('Celo send multi sig builder', function () {
.to(toAddress)
.contractSequenceId(2)
.data('0x');
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_NO_KEY_DATA);
});

it('should build from a non signed serialized data', () => {
const builder = new TransferBuilder(testData.SEND_FUNDS_NO_KEY_DATA);
builder.key(key);
const result = builder.signAndBuild();
const result = builder.signAndBuild(`${coin.chainId}`);
should.equal(result, testData.SEND_FUNDS_DATA);
});
});
Expand Down
Loading
Loading