Skip to content
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
2 changes: 1 addition & 1 deletion modules/account-lib/src/coin/near/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class Transaction extends BaseTransaction {
parsedAction = {
functionCall: {
methodName: functionCallObject.methodName,
args: JSON.parse(functionCallObject.args.toString()),
args: JSON.parse(Buffer.from(functionCallObject.args).toString()),
Comment thread
alebusse marked this conversation as resolved.
gas: functionCallObject.gas.toString(),
deposit: functionCallObject.deposit.toString(),
},
Expand Down
49 changes: 28 additions & 21 deletions modules/bitgo/src/v2/coins/near.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* @prettier
*/

import BigNumber from 'bignumber.js';
import * as accountLib from '@bitgo/account-lib';
import * as _ from 'lodash';
import { BitGo } from '../../bitgo';
Expand Down Expand Up @@ -109,9 +114,7 @@ export class Near extends BaseCoin {
* @returns {Object} object with generated pub, prv
*/
generateKeyPair(seed?: Buffer): KeyPair {
const keyPair = seed
? new accountLib.Near.KeyPair({ seed })
: new accountLib.Near.KeyPair();
const keyPair = seed ? new accountLib.Near.KeyPair({ seed }) : new accountLib.Near.KeyPair();
const keys = keyPair.getKeys();
if (!keys.prv) {
throw new Error('Missing prv in key generation.');
Expand Down Expand Up @@ -166,9 +169,7 @@ export class Near extends BaseCoin {
* Explain/parse transaction
* @param params
*/
async explainTransaction(
params: ExplainTransactionOptions,
): Promise<NearTransactionExplanation> {
async explainTransaction(params: ExplainTransactionOptions): Promise<NearTransactionExplanation> {
const factory = accountLib.register(this.getChain(), accountLib.Near.TransactionBuilderFactory);
let rebuiltTransaction: accountLib.BaseCoin.BaseTransaction;
const txRaw = params.txPrebuild.txHex;
Expand Down Expand Up @@ -208,7 +209,6 @@ export class Near extends BaseCoin {
throw new Error('missing public key parameter to sign transaction');
}


// if we are receiving addresses do not try to convert them
const signer = !nearUtils.isValidAddress(params.txPrebuild.key)
? new accountLib.Near.KeyPair({ pub: params.txPrebuild.key }).getAddress()
Expand All @@ -225,9 +225,7 @@ export class Near extends BaseCoin {
* @param callback
* @returns {Bluebird<SignedTransaction>}
*/
async signTransaction(
params: SignTransactionOptions,
): Promise<SignedTransaction> {
async signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
const factory = accountLib.register(this.getChain(), accountLib.Near.TransactionBuilderFactory);
const txBuilder = factory.from(params.txPrebuild.txHex);
txBuilder.sign({ key: params.prv });
Expand All @@ -252,9 +250,7 @@ export class Near extends BaseCoin {
throw new MethodNotImplementedError('Near recovery not implemented');
}

parseTransaction(
params: ParseTransactionOptions,
): Promise<ParsedTransaction> {
parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
throw new MethodNotImplementedError('Near parse transaction not implemented');
}

Expand All @@ -263,22 +259,33 @@ export class Near extends BaseCoin {
}

async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
let totalAmount = new BigNumber(0);
const coinConfig = coins.get(this.getChain());
const { txPrebuild: txPrebuild } = params;
const { txPrebuild: txPrebuild, txParams: txParams } = params;
const transaction = new accountLib.Near.Transaction(coinConfig);
const rawTx = txPrebuild.txHex;
if (!rawTx) {
throw new Error('missing required tx prebuild property txHex');
}

transaction.fromRawTransaction(rawTx);

// TO-DO: new explainTransaction to be implemented in account-lib

const explainedTx = transaction.explainTransaction();

// users do not input recipients for consolidation requests as they are generated by the server
if (txParams.recipients !== undefined) {
const filteredRecipients = txParams.recipients?.map((recipient) => _.pick(recipient, ['address', 'amount']));
const filteredOutputs = explainedTx.outputs.map((output) => _.pick(output, ['address', 'amount']));

if (!_.isEqual(filteredOutputs, filteredRecipients)) {
throw new Error('Tx outputs does not match with expected txParams recipients');
}
for (const recipients of txParams.recipients) {
totalAmount = totalAmount.plus(recipients.amount);
}
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
throw new Error('Tx total amount does not match with expected total amount field');
}
}
return true;
}

getAddressFromPublicKey(Pubkey: string): string {
return new accountLib.Near.KeyPair({ pub: Pubkey }).getAddress();
}
}
222 changes: 209 additions & 13 deletions modules/bitgo/test/v2/unit/coins/near.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,40 @@ import * as accountLib from '@bitgo/account-lib';
import { TestBitGo } from '../../../lib/test_bitgo';
import { randomBytes } from 'crypto';
import { rawTx, accounts, validatorContractAddress, blockHash } from '../../fixtures/coins/near';
import * as _ from 'lodash';
import * as sinon from 'sinon';

describe('NEAR:', function () {
let bitgo;
let basecoin;
let newTxPrebuild;
let newTxParams;
const factory = accountLib.register('tnear', accountLib.Near.TransactionBuilderFactory);

const txPrebuild = {
txHex: rawTx.transfer.unsigned,
txInfo: {},
};

const txParams = {
recipients: [
{
address: '9f7b0675db59d19b4bd9c8c72eaabba75a9863d02b30115b8b3c3ca5c20f0254',
amount: '1000000000000000000000000',
},
],
};

before(function () {
bitgo = new TestBitGo({ env: 'mock' });
bitgo.initializeTestVars();
basecoin = bitgo.coin('tnear');
newTxPrebuild = () => {
return _.cloneDeep(txPrebuild);
};
newTxParams = () => {
return _.cloneDeep(txParams);
};
});

it('should retun the right info', function () {
Expand Down Expand Up @@ -106,40 +130,212 @@ describe('NEAR:', function () {
});

describe('Verify transaction: ', () => {
it('should succeed to verify transaction in base64 encoding', async () => {
const txParams = {
};
const amount = '1000000';
const gas = '125000000000000';

// TO-DO wait for verifyTransaction using explainTranasaction
const txPrebuild = {
txHex: rawTx.transfer.unsigned,
txInfo: {
it('should succeed to verify unsigned transaction in base64 encoding', async () => {

},
const txPrebuild = newTxPrebuild();
const txParams = newTxParams();
const verification = {};
const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification });
isTransactionVerified.should.equal(true);
});

it('should succeed to verify signed transaction in base64 encoding', async () => {

const txPrebuild = {
txHex: rawTx.transfer.signed,
txInfo: {},
};

const txParams = newTxParams();
const verification = {};

const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification });
isTransactionVerified.should.equal(true);
});

it('should succeed to verify transaction in hex encoding', async () => {
it('should fail verify transactions when have different recipients', async () => {

const txPrebuild = newTxPrebuild();

const txParams = {
recipients: [
{
address: '9f7b0675db59d19b4bd9c8c72eaabba75a9863d02b30115b8b3c3ca5c20f0254',
amount: '1000000000000000000000000',
},
{
address: '9f7b0675db59d19b4bd9c8c72eaabba75a9863d02b30115b8b3c3ca5c20f0254',
amount: '2000000000000000000000000',
},
],
};

// TO-DO wait for verifyTransaction using explainTranasaction
const txPrebuild = {
txHex: rawTx.transfer.hexUnsigned,
txInfo: {
const verification = {};

await basecoin.verifyTransaction({ txParams, txPrebuild, verification })
.should.be.rejectedWith('Tx outputs does not match with expected txParams recipients');
});

it('should fail verify transactions when total amount does not match with expected total amount field', async () => {

const explainedTx = {
id: '5jTEPuDcMCeEgp1iyEbNBKsnhYz4F4c1EPDtRmxm3wCw',
displayOrder: [
'outputAmount',
'changeAmount',
'outputs',
'changeOutputs',
'fee',
'type',
],
outputAmount: '90000',
changeAmount: '0',
changeOutputs: [],
outputs: [
{
address: '9f7b0675db59d19b4bd9c8c72eaabba75a9863d02b30115b8b3c3ca5c20f0254',
amount: '1000000000000000000000000',
},
],
fee: {
fee: '',
},
type: 0,
};

const stub = sinon.stub(accountLib.Near.Transaction.prototype, 'explainTransaction');
const txPrebuild = newTxPrebuild();
const txParams = newTxParams();
const verification = {};
stub.returns(explainedTx);

await basecoin.verifyTransaction({ txParams, txPrebuild, verification })
.should.be.rejectedWith('Tx total amount does not match with expected total amount field');
stub.restore();
});

it('should succeed to verify transaction in hex encoding', async () => {

const txParams = newTxParams();
const txPrebuild = newTxPrebuild();
const verification = {};

const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification });
isTransactionVerified.should.equal(true);
});

it('should convert serialized hex string to base64', async function () {
const txParams = newTxParams();
const txPrebuild = newTxPrebuild();
const verification = {};
txPrebuild.txHex = Buffer.from(txPrebuild.txHex, 'base64').toString('hex');
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild, verification });
validTransaction.should.equal(true);
});

it('should verify when input `recipients` is absent', async function () {
const txParams = newTxParams();
txParams.recipients = undefined;
const txPrebuild = newTxPrebuild();
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
validTransaction.should.equal(true);
});

it('should fail verify when txHex is invalid', async function () {
const txParams = newTxParams();
txParams.recipients = undefined;
const txPrebuild = {};
await basecoin.verifyTransaction({ txParams, txPrebuild })
.should.rejectedWith('missing required tx prebuild property txHex');
});

it('should succeed to verify transactions when recipients has extra data', async function () {
const txPrebuild = newTxPrebuild();
const txParams = newTxParams();
txParams.data = 'data';

const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
validTransaction.should.equal(true);
});

it('should verify activate staking transaction', async function () {
const txBuilder = factory.getStakingActivateBuilder();
txBuilder
.amount(amount)
.gas(gas)
.sender(accounts.account1.address, accounts.account1.publicKey)
.receiverId(validatorContractAddress)
.recentBlockHash(blockHash.block1)
.nonce(1);
txBuilder.sign({ key: accounts.account1.secretKey });
const tx = await txBuilder.build();
const txToBroadcastFormat = tx.toBroadcastFormat();
const txPrebuild = {
txHex: txToBroadcastFormat,
};
const txParams = {
recipients: [
{
address: 'lavenderfive.pool.f863973.m0',
amount: '1000000',
},
],
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
validTransaction.should.equal(true);
});

it('should verify deactivate staking transaction', async function () {
const txBuilder = factory.getStakingDeactivateBuilder();
txBuilder
.amount(amount)
.gas(gas)
.sender(accounts.account1.address, accounts.account1.publicKey)
.receiverId(validatorContractAddress)
.recentBlockHash(blockHash.block1)
.nonce(1);
txBuilder.sign({ key: accounts.account1.secretKey });
const tx = await txBuilder.build();
const txToBroadcastFormat = tx.toBroadcastFormat();
const txPrebuild = {
txHex: txToBroadcastFormat,
};
const txParams = {
recipients: [],
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
validTransaction.should.equal(true);
});

it('should verify withdraw staking transaction', async function () {
const txBuilder = factory.getStakingWithdrawBuilder();
txBuilder
.amount(amount)
.gas(gas)
.sender(accounts.account1.address, accounts.account1.publicKey)
.receiverId(validatorContractAddress)
.recentBlockHash(blockHash.block1)
.nonce(1);
txBuilder.sign({ key: accounts.account1.secretKey });
const tx = await txBuilder.build();
const txToBroadcastFormat = tx.toBroadcastFormat();
const txPrebuild = {
txHex: txToBroadcastFormat,
};
const txParams = {
recipients: [
{
address: '61b18c6dc02ddcabdeac56cb4f21a971cc41cc97640f6f85b073480008c53a0d',
amount: '1000000',
},
],
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild });
validTransaction.should.equal(true);
});
});

describe('Explain Transactions:', () => {
Expand Down