Skip to content

Commit

Permalink
feat(sdk-coin-etc): testing unsigned sweep support for wrw
Browse files Browse the repository at this point in the history
TICKET: COIN-1419
  • Loading branch information
bhupendra11 committed Aug 22, 2024
1 parent 2a9fa19 commit f7df40d
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 32 deletions.
53 changes: 30 additions & 23 deletions modules/sdk-coin-etc/src/etc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import {
} from '@bitgo/abstract-eth';
import { BaseCoin, BitGoBase, common, getIsUnsignedSweep, Util, Recipient } from '@bitgo/sdk-core';
import { BaseCoin as StaticsBaseCoin, coins, EthereumNetwork as EthLikeNetwork, ethGasConfigs } from '@bitgo/statics';
import { TransactionBuilder, KeyPair as KeyPairLib, TransferBuilder } from './lib';
import { TransactionBuilder, KeyPair as KeyPairLib } from './lib';
import * as _ from 'lodash';
import { bip32 } from '@bitgo/utxo-lib';
import { BigNumber } from 'bignumber.js';
import { Buffer } from 'buffer';
import request from 'superagent';
Expand Down Expand Up @@ -66,11 +65,12 @@ export class Etc extends AbstractEthLikeCoin {
// Clean up whitespace from entered values
let userKey = params.userKey.replace(/\s/g, '');
const backupKey = params.backupKey.replace(/\s/g, '');

// Set new tx fees (using default config values from platform)
const gasLimit = new optionalDeps.ethUtil.BN(this.setGasLimit(params.gasLimit));
const gasPrice = params.eip1559
? new optionalDeps.ethUtil.BN(params.eip1559.maxFeePerGas)
: new optionalDeps.ethUtil.BN(this.setGasPrice(params.gasPrice));

if (!userKey.startsWith('xpub') && !userKey.startsWith('xprv')) {
try {
userKey = this.bitgo.decrypt({
Expand All @@ -81,12 +81,12 @@ export class Etc extends AbstractEthLikeCoin {
throw new Error(`Error decrypting user keychain: ${e.message}`);
}
}

let backupKeyAddress;
let backupSigningKey;
if (isUnsignedSweep) {
const backupHDNode = bip32.fromBase58(backupKey);
backupSigningKey = backupHDNode.publicKey;
backupKeyAddress = `0x${optionalDeps.ethUtil.publicToAddress(backupSigningKey, true).toString('hex')}`;
const backupKeyPair = new KeyPairLib({ pub: backupKey });
backupKeyAddress = backupKeyPair.getAddress();
} else {
// Decrypt backup private key and get address
let backupPrv;
Expand All @@ -107,16 +107,16 @@ export class Etc extends AbstractEthLikeCoin {
}
backupKeyAddress = keyPair.getAddress();
}

const backupKeyNonce = await this.getAddressNonce(backupKeyAddress);

// get balance of backupKey to ensure funds are available to pay fees
const backupKeyBalance = await this.queryAddressBalance(backupKeyAddress);
const totalGasNeeded = gasPrice.mul(gasLimit);

const totalGasNeeded = gasPrice.mul(gasLimit);
const weiToGwei = 10 ** 9;
if (backupKeyBalance.lt(totalGasNeeded)) {
throw new Error(
`Backup key address ${backupKeyAddress} has balance ${backupKeyBalance
`Backup key address ${backupKeyAddress} has balance ${backupKeyBalance
.div(new BN(weiToGwei))
.toString()} Gwei.` +
`This address must have a balance of at least ${(totalGasNeeded / weiToGwei).toString()}` +
Expand Down Expand Up @@ -156,34 +156,45 @@ export class Etc extends AbstractEthLikeCoin {
}
}

// Build unsigned transaction
const txInfo = {
recipient: recipients[0],
expireTime: getDefaultExpireTime(),
contractSequenceId: sequenceId,
operationHash: operationHash,
signature: signature,
operationHash,
signature,
gasLimit: gasLimit.toString(10),
tokenContractAddress: params.tokenContractAddress,
};

const txBuilder = this.getTransactionBuilder() as TransactionBuilder;
txBuilder.counter(backupKeyNonce);
txBuilder.contract(params.walletContractAddress);
const txFee = { fee: gasPrice.toString() };
let txFee;
if (params.eip1559) {
txFee = {
eip1559: {
maxPriorityFeePerGas: params.eip1559.maxPriorityFeePerGas,
maxFeePerGas: params.eip1559.maxFeePerGas,
},
};
} else {
txFee = { fee: gasPrice.toString() };
}
txBuilder.fee({
...txFee,
gasLimit: gasLimit.toString(),
});
const transferBuilder = txBuilder.transfer() as TransferBuilder;
transferBuilder
.coin(this.staticsCoin?.name as string)

txBuilder
.transfer()
.coin(this.getChain())
.amount(recipients[0].amount)
.contractSequenceId(sequenceId)
.expirationTime(getDefaultExpireTime())
.to(params.recoveryDestination);

const tx = await txBuilder.build();
if (isUnsignedSweep) {
const tx = await txBuilder.build();
const response: OfflineVaultTxInfo = {
txHex: tx.toBroadcastFormat(),
userKey,
Expand All @@ -202,13 +213,9 @@ export class Etc extends AbstractEthLikeCoin {
return response;
}

// sign the transaction
txBuilder
.transfer()
.coin(this.staticsCoin?.name as string)
.key(new KeyPairLib({ prv: userKey }).getKeys().prv as string);
const userKeyPair = new KeyPairLib({ prv: userKey });
txBuilder.transfer().key(userKeyPair.getKeys().prv!);
txBuilder.sign({ key: backupSigningKey });

const signedTx = await txBuilder.build();

return {
Expand Down
116 changes: 107 additions & 9 deletions modules/sdk-coin-etc/test/unit/etc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ describe('Wallet Recovery Wizard', function () {

beforeEach(function () {
sandbox = sinon.createSandbox();
const callBack = sandbox.stub(Etc.prototype, 'queryAddressBalance' as keyof Etc);
callBack.withArgs(sourceRootAddress).resolves(new BN('2190000000000000000'));
callBack.withArgs(backupKeyAddress).resolves(new BN('190000000000000000'));
callBack.withArgs('0x5273e0d869226ccf579a81b6d291fb3702ba9dec').resolves(new BN('0'));
});

afterEach(function () {
Expand All @@ -56,6 +52,10 @@ describe('Wallet Recovery Wizard', function () {
describe('Non-BitGo Recovery', function () {
beforeEach(function () {
tetcCoin = bitgo.coin('tetc') as Tetc;
const callBack = sandbox.stub(Etc.prototype, 'queryAddressBalance' as keyof Etc);
callBack.withArgs(sourceRootAddress).resolves(new BN('2190000000000000000'));
callBack.withArgs(backupKeyAddress).resolves(new BN('190000000000000000'));
callBack.withArgs('0x5273e0d869226ccf579a81b6d291fb3702ba9dec').resolves(new BN('0'));
});

it('should build a recovery transaction for non-bitgo recovery', async function () {
Expand Down Expand Up @@ -92,7 +92,7 @@ describe('Wallet Recovery Wizard', function () {
recoveryDestination: sourceRootAddress,
})
.should.be.rejectedWith(
'Backup key address 0x5273e0d869226ccf579a81b6d291fb3702ba9dec has balance 0 Gwei.This address must have a balance of at least 10000000 Gwei to perform recoveries. Try sending some funds to this address then retry.'
'Backup key address 0x5273e0d869226ccf579a81b6d291fb3702ba9dec has balance 0 Gwei.This address must have a balance of at least 10000000 Gwei to perform recoveries. Try sending some funds to this address then retry.'
);
});

Expand All @@ -105,9 +105,107 @@ describe('Wallet Recovery Wizard', function () {
});

// Add tests related to unsigned sweep here if any
describe('Unsigned sweep', function () {
beforeEach(function () {
tetcCoin = bitgo.coin('tetc') as Tetc;
});
describe('Unsigned Sweep', function () {
// const userXprv =
// 'xpub661MyMwAqRbcFQJReFreyrt9aVUCxedV9z7amMs5p7uvHxeiJyQ3Z1KweeWRQSYHgQs1j9X77Ajs1ABqdMWZV2fLxYtJQNY72CwwQwixfNE';
// const userXpub =
// 'xpub661MyMwAqRbcFQJReFreyrt9aVUCxedV9z7amMs5p7uvHxeiJyQ3Z1KweeWRQSYHgQs1j9X\n' +
// '77Ajs1ABqdMWZV2fLxYtJQNY72CwwQwixfNE';
// const backupXprv =
// 'xpub661MyMwAqRbcGYaF52itktGhGDfiL9CBBTh4TSXV6QqGgXRbhSS5DAaTbdCPJA425XwkvwyCKtTmoxcUTAUgKUf7Qr5Ks9gJP9DTfiV2PhU';
// const backupXpub =
// 'xpub661MyMwAqRbcGYaF52itktGhGDfiL9CBBTh4TSXV6QqGgXRbhSS5DAaTbdCPJA425Xwkvwy\n' +
// 'CKtTmoxcUTAUgKUf7Qr5Ks9gJP9DTfiV2PhU';
// const walletContractAddress = '0x7fcf95a9106a0ed3bd09e653c8ea3d5e489bfb23';
// // tetc wallet 1 receiveAddress 4
// const recoveryDestination = '0x321cbe223ff1c3d0c03b73b8c648ef2d91e4aaa1';
// const gasPrice = 25000000000;
//
// const backupKeyAddress = '0x1b9af47cc3048fe1d31ad72299611d3df3926755';
//
// beforeEach(function () {
// tetcCoin = bitgo.coin('tetc') as Tetc;
// const callBack = sandbox.stub(Etc.prototype, 'queryAddressBalance' as keyof Etc);
// callBack.withArgs(backupKeyAddress).resolves(new BN('2190000000000000000'));
// callBack.withArgs(walletContractAddress).resolves(new BN('567190000000000000000'));
// });
// it('should build unsigned sweep tx', async function () {
// const recovery: OfflineVaultTxInfo | RecoveryInfo = await tetcCoin.recover({
// userKey: userXpub,
// backupKey: backupXpub,
// walletContractAddress,
// recoveryDestination,
// gasPrice,
// });
// let parsedTx;
// if ('tx' in recovery) {
// parsedTx = parseTransaction(recovery.tx ?? '');
// }
// const decodedSendMultisigCallData = decodeTransaction(JSON.stringify(walletSimpleABI), parsedTx.data);
//
// const safeTransferFromCallData = decodedSendMultisigCallData.args[2];
// const safeTransferFromDestination = decodedSendMultisigCallData.args[0];
// safeTransferFromDestination.toLowerCase().should.equal(recoveryDestination);
// safeTransferFromCallData.should.be.equal('0x');
// recovery.should.not.be.undefined();
// recovery.should.have.properties('txHex', 'userKey', 'backupKey', 'recipients', 'walletContractAddress');
// if ('recipients' in recovery) {
// recovery.recipients.length.should.equal(1);
// recovery.recipients[0].address.should.equal(recoveryDestination);
// }
// if ('walletContractAddress' in recovery) {
// recovery.walletContractAddress.should.equal(walletContractAddress);
// }
// });
// it('should add a second signature', async function () {
// const recovery = await tetcCoin.recover({
// userKey: userXpub,
// backupKey: backupXpub,
// walletContractAddress,
// recoveryDestination,
// gasPrice,
// });
//
// const txPrebuild = {
// txHex: recovery.txHex,
// userKey: userXpub,
// backupKey: backupXpub,
// coin: recovery.coin,
// gasPrice: recovery.gasPrice,
// gasLimit: recovery.gasLimit,
// recipients: recovery.recipients,
// walletContractAddress: recovery.walletContractAddress,
// amount: recovery.amount,
// backupKeyNonce: recovery.backupKeyNonce,
// recipient: recovery.recipient,
// expireTime: recovery.expireTime,
// contractSequenceId: recovery.contractSequenceId,
// nextContractSequenceId: recovery.nextContractSequenceId,
// };
//
// const params = {
// txPrebuild,
// prv: userXprv,
// };
// // sign transaction once
// const halfSigned = await tetcCoin.signTransaction(params);
//
// const wrapper = {} as AvaxSignTransactionOptions;
// wrapper.txPrebuild = halfSigned;
// wrapper.isLastSignature = true;
// wrapper.walletContractAddress = walletContractAddress;
// wrapper.prv = backupXprv;
//
// // sign transaction twice with the "isLastSignature" flag
// const finalSignedTx = await tetcCoin.signTransaction(wrapper);
// finalSignedTx.should.have.property('txHex');
// const txBuilder = tetcCoin.getTransactionBuilder() as TransactionBuilder;
// txBuilder.from(finalSignedTx.txHex);
// const rebuiltTx = await txBuilder.build();
// rebuiltTx.signature.length.should.equal(2);
// rebuiltTx.outputs.length.should.equal(1);
// rebuiltTx.outputs[0].address.should.equal(txPrebuild.recipient.address);
// rebuiltTx.outputs[0].value.should.equal(txPrebuild.recipient.amount);
// });
});
});

0 comments on commit f7df40d

Please sign in to comment.