Skip to content
Draft
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/sdk-coin-vet/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const STARGATE_NFT_ADDRESS = '0x1856c533ac2d94340aaa8544d35a5c1d4a21dee7'
export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6cddb0e';

export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
export const STARGATE_CONTRACT_ADDRESS_TESTNET = '0x1E02B2953AdEfEC225cF0Ec49805b1146a4429C1';

export const AVG_GAS_UNITS = '21000';
export const EXPIRATION = 400;
Expand Down
2 changes: 2 additions & 0 deletions modules/sdk-coin-vet/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ export interface VetTransactionData {
tokenId?: string; // Added for unstaking and burn NFT transactions
stakingContractAddress?: string;
amountToStake?: string;
levelId?: number; // NFT tier level
nftTokenId?: number; // Used as tier level (levelId) for stakeAndDelegate method (not the actual NFT token ID)
autorenew?: boolean; // Autorenew flag for stakeAndDelegate method
nftCollectionId?: string;
claimRewardsData?: ClaimRewardsData;
validatorAddress?: string;
}

export interface VetTransactionExplanation extends BaseTransactionExplanation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import { VetTransactionData } from '../iface';
import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import { addHexPrefix, BN } from 'ethereumjs-util';
import { ZERO_VALUE_AMOUNT } from '../constants';

export class DelegateClauseTransaction extends Transaction {
private _stakingContractAddress: string;
private _tokenId: number;
private _delegateForever = true;
private _tokenId: string;
private _validator: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._type = TransactionType.StakingDelegate;
}

get validator(): string {
return this._validator;
}

set validator(address: string) {
this._validator = address;
}

get stakingContractAddress(): string {
return this._stakingContractAddress;
}
Expand All @@ -27,22 +35,14 @@ export class DelegateClauseTransaction extends Transaction {
this._stakingContractAddress = address;
}

get tokenId(): number {
get tokenId(): string {
return this._tokenId;
}

set tokenId(tokenId: number) {
set tokenId(tokenId: string) {
this._tokenId = tokenId;
}

get delegateForever(): boolean {
return this._delegateForever;
}

set delegateForever(delegateForever: boolean) {
this._delegateForever = delegateForever;
}

buildClauses(): void {
if (!this.stakingContractAddress) {
throw new Error('Staking contract address is not set');
Expand All @@ -54,7 +54,11 @@ export class DelegateClauseTransaction extends Transaction {
throw new Error('Token ID is not set');
}

const data = this.getDelegateData(this.tokenId, this.delegateForever);
if (this.validator === undefined || this.validator === null) {
throw new Error('Validator address is not set');
}

const data = this.getDelegateData(this.tokenId, this.validator);
this._transactionData = data;

// Create the clause for delegation
Expand All @@ -80,10 +84,10 @@ export class DelegateClauseTransaction extends Transaction {
* @param {number} tokenId - The Token ID for delegation
* @returns {string} - The encoded transaction data
*/
getDelegateData(levelId: number, delegateForever = true): string {
getDelegateData(tokenId: string, validatorAddress: string): string {
const methodName = 'delegate';
const types = ['uint256', 'bool'];
const params = [levelId, delegateForever];
const types = ['uint256', 'address'];
const params = [new BN(tokenId), validatorAddress];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);
Expand All @@ -107,8 +111,8 @@ export class DelegateClauseTransaction extends Transaction {
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: ZERO_VALUE_AMOUNT,
nftTokenId: this.tokenId,
autorenew: this.delegateForever,
tokenId: this.tokenId,
validatorAddress: this.validator,
};

return json;
Expand Down Expand Up @@ -144,7 +148,7 @@ export class DelegateClauseTransaction extends Transaction {
this.transactionData = clause.data;
const decoded = utils.decodeDelegateClauseData(clause.data);
this.tokenId = decoded.tokenId;
this.delegateForever = decoded.delegateForever;
this.validator = decoded.validator;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class StakeClauseTransaction extends Transaction {
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: this.amountToStake,
nftTokenId: this.levelId,
levelId: this.levelId,
};

return json;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,28 @@ export class DelegateTxnBuilder extends TransactionBuilder {
/**
* Sets the token ID for this delegate tx.
*
* @param {number} levelId - The level ID for staking
* @param {number} levelId - The NFT token ID
* @returns {DelegateTxnBuilder} This transaction builder
*/
tokenId(tokenId: number): this {
tokenId(tokenId: string): this {
this.delegateTransaction.tokenId = tokenId;
return this;
}

/**
* Sets the validator address for this delegate tx.
* @param {string} address - The validator address
* @returns {DelegateTxnBuilder} This transaction builder
*/
validator(address: string): this {
if (!address) {
throw new Error('Validator address is required');
}
this.validateAddress({ address });
this.delegateTransaction.validator = address;
return this;
}

/**
* Sets the transaction data for this delegate tx.
*
Expand All @@ -115,7 +129,7 @@ export class DelegateTxnBuilder extends TransactionBuilder {
assert(transaction.stakingContractAddress, 'Staking contract address is required');

assert(transaction.tokenId, 'Token ID is required');
assert(transaction.delegateForever, 'delegate forever flag is required');
assert(transaction.validator, 'validator address is required');
this.validateAddress({ address: transaction.stakingContractAddress });
}

Expand Down
12 changes: 6 additions & 6 deletions modules/sdk-coin-vet/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,21 @@ export class Utils implements BaseUtils {
}

/**
* Decodes delegate transaction data to extract tokenId and delegateForever
* Decodes delegate transaction data to extract tokenId and validatorAddress
*
* @param {string} data - The encoded transaction data
* @returns {object} - Object containing tokenId and delegateForever
* @returns {object} - Object containing levelId and validator address
*/
decodeDelegateClauseData(data: string): { tokenId: number; delegateForever: boolean } {
decodeDelegateClauseData(data: string): { tokenId: string; validator: string } {
try {
const parameters = data.slice(10);

// Decode using ethereumjs-abi directly
const decoded = EthereumAbi.rawDecode(['uint256', 'bool'], Buffer.from(parameters, 'hex'));
const decoded = EthereumAbi.rawDecode(['uint256', 'address'], Buffer.from(parameters, 'hex'));

return {
tokenId: Number(decoded[0]),
delegateForever: Boolean(decoded[1]),
tokenId: String(decoded[0]),
validator: String(decoded[1]),
};
} catch (error) {
throw new Error(`Failed to decode delegation data: ${error.message}`);
Expand Down
2 changes: 1 addition & 1 deletion modules/sdk-coin-vet/test/resources/vet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const STAKING_TRANSACTION =
'0xf901032788015d55fcf2457e7c40f866f864941856c533ac2d94340aaa8544d35a5c1d4a21dee7880de0b6b3a7640000b844d8da3bbf0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000181808265848083094c53c101b882efcb9ea88e908d1a142db96c1b44dd056ea194f1ad45670c100a8c52348cc7b20387741260ebe7fe9b7594f96693c88662fa60edba5992332728222b0bdd8a30008535368bd901319eb4513d16bebc428dc8454d32a19eeb76372849a6134ebbba79f1eeceea1f6546574b945c05489222cb451f5b0e2901b0c687b750e833aeb800';

export const STAKE_CLAUSE_TRANSACTION =
'0xf8e3278801618b7b1354c4ca40f845f843941ec1d168574603ec35b9d229843b7c2b44bcb770880de0b6b3a7640000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808305fd5a808307b278c101b882b380970580d957b8e7989aa9aa9281e57245fa3835cb2aaae6475b4062bb4f1c2dd2ca694df6503d5dfd654579130b3484bee75420247cf8f6b6b4b76b7939f101db6e6cef5a27375274741f3c0aba4be13a9e086337c3290866afe049efcdaa2d3227c9e12b52627c4d71f5b667821f9d33adcc4c97fdc28b93c34013d32e242300';
'0xf8e5278801638298c53767ac40f847f845941e02b2953adefec225cf0ec49805b1146a4429c18a021e19e0c9bab2400000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808306338a80830f340bc101b882b4970b0c160552162de719b9ed1fa6268bbfe9b36fd4e4a5c13e956cf539e60478f69179e8db4a3106fdbe775e3d923510b16f48da56c66076d1f66ffd822abf01ebc040c795b1e17cb0ca5ba747e3a181b4eefea7db5378f64f82361bdb7745da45aba2ace3db9822b675474552f13849052987c431cd4867813c2cf635302b1101';

export const DELEGATION_TRANSACTION =
'0xf8fb278801618aa3e0a55fc940f85ef85c947240e3bc0d26431512d5b67dbd26d199205bffe880b8443207555d00000000000000000000000000000000000000000000000000000000000187690000000000000000000000000000000000000000000000000000000000000001818082e43a808306af07c101b882fb8030f6e2ef6563ff3b0e7e2a2292c1db5fc41c7ab9f598bad370c5cfd3dc32286ae8d709e941c0312c8cd33a3505156b44d1639c73980ffa66bc72f37820f2001c0e6b6e76a6a4d806c377a0a279053eb6ea4356bd235f4396585bb071d70f992c639d45c53431a3c1493a52a136203905e42c671dd384ee5f5ead0a70cb607001';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { coins } from '@bitgo/statics';
import { TransactionBuilderFactory, Transaction, DelegateClauseTransaction } from '../../src/lib';
import should from 'should';
import { DELEGATE_CLAUSE_METHOD_ID, STARGATE_DELEGATION_ADDRESS_TESTNET } from '../../src/lib/constants';
import { DELEGATE_CLAUSE_METHOD_ID, STARGATE_CONTRACT_ADDRESS_TESTNET } from '../../src/lib/constants';
import EthereumAbi from 'ethereumjs-abi';
import * as testData from '../resources/vet';
import { BN } from 'ethereumjs-util';

describe('VET Delegation Transaction', function () {
const factory = new TransactionBuilderFactory(coins.get('tvet'));
const tokenId = 100201; // Test level ID
const delegateForever = true; // Test delegateForever flag
const tokenId = '100201'; // Test token ID
const validatorAddress = '0x9a7aFCACc88c106f3bbD6B213CD0821D9224d945';

// Helper function to create a basic transaction builder with common properties
const createBasicTxBuilder = () => {
Expand All @@ -20,12 +21,13 @@ describe('VET Delegation Transaction', function () {
txBuilder.gas(100000);
txBuilder.gasPriceCoef(0);
txBuilder.nonce('12345');
txBuilder.validator(validatorAddress);
return txBuilder;
};

it('should build a delegate transaction', async function () {
const txBuilder = factory.getStakingDelegateBuilder();
txBuilder.stakingContractAddress(STARGATE_DELEGATION_ADDRESS_TESTNET);
txBuilder.stakingContractAddress(STARGATE_CONTRACT_ADDRESS_TESTNET);
txBuilder.tokenId(tokenId);
txBuilder.sender('0x9378c12BD7502A11F770a5C1F223c959B2805dA9');
txBuilder.chainTag(0x27); // Testnet chain tag
Expand All @@ -34,21 +36,22 @@ describe('VET Delegation Transaction', function () {
txBuilder.gas(100000);
txBuilder.gasPriceCoef(0);
txBuilder.nonce('12345');
txBuilder.validator(validatorAddress);

const tx = await txBuilder.build();
should.exist(tx);
tx.should.be.instanceof(Transaction);
tx.should.be.instanceof(DelegateClauseTransaction);

const delegationTx = tx as DelegateClauseTransaction;
delegationTx.stakingContractAddress.should.equal(STARGATE_DELEGATION_ADDRESS_TESTNET);
delegationTx.stakingContractAddress.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);
delegationTx.tokenId.should.equal(tokenId);
delegationTx.delegateForever.should.equal(delegateForever);
delegationTx.validator.should.equal(validatorAddress);

// Verify clauses
delegationTx.clauses.length.should.equal(1);
should.exist(delegationTx.clauses[0].to);
delegationTx.clauses[0].to?.should.equal(STARGATE_DELEGATION_ADDRESS_TESTNET);
delegationTx.clauses[0].to?.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);

// Verify transaction data is correctly encoded using ethereumABI
should.exist(delegationTx.clauses[0].data);
Expand All @@ -57,8 +60,8 @@ describe('VET Delegation Transaction', function () {

// Verify the encoded data matches what we expect from ethereumABI
const methodName = 'delegate';
const types = ['uint256', 'bool'];
const params = [tokenId, delegateForever];
const types = ['uint256', 'address'];
const params = [new BN(tokenId), validatorAddress];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);
Expand All @@ -68,24 +71,33 @@ describe('VET Delegation Transaction', function () {

// Verify recipients
delegationTx.recipients.length.should.equal(1);
delegationTx.recipients[0].address.should.equal(STARGATE_DELEGATION_ADDRESS_TESTNET);
delegationTx.recipients[0].address.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);
});

describe('Failure scenarios', function () {
it('should throw error when stakingContractAddress is missing', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.tokenId(tokenId);
txBuilder.validator(validatorAddress);

await txBuilder.build().should.be.rejectedWith('Staking contract address is required');
});

it('should throw error when tokenId is missing', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(STARGATE_DELEGATION_ADDRESS_TESTNET);
txBuilder.stakingContractAddress(STARGATE_CONTRACT_ADDRESS_TESTNET);

await txBuilder.build().should.be.rejectedWith('Token ID is required');
});

it('should throw error when validator address is missing', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(STARGATE_CONTRACT_ADDRESS_TESTNET);
txBuilder.tokenId(tokenId);

await txBuilder.build().should.be.rejectedWith('Validator address is required');
});

it('should throw error when stakingContractAddress is invalid', async function () {
const txBuilder = createBasicTxBuilder();

Expand All @@ -97,14 +109,15 @@ describe('VET Delegation Transaction', function () {

it('should build transaction with undefined sender but include it in inputs', async function () {
const txBuilder = factory.getStakingDelegateBuilder();
txBuilder.stakingContractAddress(STARGATE_DELEGATION_ADDRESS_TESTNET);
txBuilder.stakingContractAddress(STARGATE_CONTRACT_ADDRESS_TESTNET);
txBuilder.tokenId(tokenId);
txBuilder.chainTag(0x27);
txBuilder.blockRef('0x0000000000000000');
txBuilder.expiration(64);
txBuilder.gas(100000);
txBuilder.gasPriceCoef(0);
txBuilder.nonce('12345');
txBuilder.validator(validatorAddress);
// Not setting sender

const tx = await txBuilder.build();
Expand All @@ -117,12 +130,12 @@ describe('VET Delegation Transaction', function () {

// Verify the transaction has the correct output
delegationTx.outputs.length.should.equal(1);
delegationTx.outputs[0].address.should.equal(STARGATE_DELEGATION_ADDRESS_TESTNET);
delegationTx.outputs[0].address.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);
});

it('should use network default chainTag when not explicitly set', async function () {
const txBuilder = factory.getStakingDelegateBuilder();
txBuilder.stakingContractAddress(STARGATE_DELEGATION_ADDRESS_TESTNET);
txBuilder.stakingContractAddress(STARGATE_CONTRACT_ADDRESS_TESTNET);
txBuilder.tokenId(tokenId);
// Not setting chainTag
txBuilder.blockRef('0x0000000000000000');
Expand All @@ -131,6 +144,7 @@ describe('VET Delegation Transaction', function () {
txBuilder.gasPriceCoef(0);
txBuilder.nonce('12345');
txBuilder.sender('0x9378c12BD7502A11F770a5C1F223c959B2805dA9');
txBuilder.validator(validatorAddress);

const tx = await txBuilder.build();
tx.should.be.instanceof(DelegateClauseTransaction);
Expand All @@ -140,6 +154,7 @@ describe('VET Delegation Transaction', function () {
delegationTx.chainTag.should.equal(39);
});

// TODO(SC-3926): this test needs to be updated with delegate txn data once its done in coins sandbox
it('should build a signed tx and validate its toJson', async function () {
const txBuilder = factory.from(testData.DELEGATION_TRANSACTION);
const tx = txBuilder.transaction as DelegateClauseTransaction;
Expand All @@ -153,7 +168,7 @@ describe('VET Delegation Transaction', function () {
toJson.chainTag.should.equal(39);
// in delegate txn, nftTokenId indicates the tokenId
toJson.nftTokenId?.should.equal(tokenId);
toJson.autorenew?.should.equal(true);
toJson.validatorAddress?.should.equal(validatorAddress);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as testData from '../resources/vet';
describe('VET Staking Transaction', function () {
const factory = new TransactionBuilderFactory(coins.get('tvet'));
const stakingContractAddress = STARGATE_NFT_ADDRESS_TESTNET;
const amountToStake = '1000000000000000000'; // 1 VET in wei
const amountToStake = '10000000000000000000000'; // 10000 VET in wei
const levelId = 8; // Test level ID

// Helper function to create a basic transaction builder with common properties
Expand Down Expand Up @@ -240,8 +240,8 @@ describe('VET Staking Transaction', function () {
const txBuilder = factory.from(testData.STAKE_CLAUSE_TRANSACTION);
const tx = txBuilder.transaction as StakeClauseTransaction;
const toJson = tx.toJson();
toJson.id.should.equal('0x2f96e4c16d70bd3e2dabec29a07eb3d6066691ba5b812d6e897676f6ebc0a798');
toJson.stakingContractAddress?.should.equal('0x1ec1d168574603ec35b9d229843b7c2b44bcb770');
toJson.id.should.equal('0x7148f62b42ecfdcb7ee62ac0654514e4b5f65f2fe5fdee79d4d29f56ab1722eb');
toJson.stakingContractAddress?.should.equal('0x1E02B2953AdEfEC225cF0Ec49805b1146a4429C1');
toJson.amountToStake?.should.equal('0xde0b6b3a7640000');
toJson.nonce.should.equal('504440');
toJson.gas.should.equal(392538);
Expand Down
Loading