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
3 changes: 2 additions & 1 deletion modules/sdk-coin-vet/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ export class Transaction extends BaseTransaction {
this.type === TransactionType.StakingDelegate ||
this.type === TransactionType.StakingUnlock ||
this.type === TransactionType.StakingWithdraw ||
this.type === TransactionType.StakingClaim
this.type === TransactionType.StakingClaim ||
this.type === TransactionType.StakingLock
) {
transactionBody.reserved = {
features: 1, // mark transaction as delegated i.e. will use gas payer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix, BN } from 'ethereumjs-util';
import { ZERO_VALUE_AMOUNT } from '../constants';

export class ValidatorRegistrationTransaction extends Transaction {
private _stakingContractAddress: string;
private _validator: string;
private _stakingPeriod: number;
private _amountToStake: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
Expand All @@ -35,6 +35,14 @@ export class ValidatorRegistrationTransaction extends Transaction {
this._stakingPeriod = period;
}

get amountToStake(): string {
return this._amountToStake;
}

set amountToStake(amount: string) {
this._amountToStake = amount;
}

get stakingContractAddress(): string {
return this._stakingContractAddress;
}
Expand Down Expand Up @@ -63,7 +71,7 @@ export class ValidatorRegistrationTransaction extends Transaction {
this._clauses = [
{
to: this.stakingContractAddress,
value: ZERO_VALUE_AMOUNT,
value: this.amountToStake,
data: addValidationData,
},
];
Expand All @@ -72,7 +80,7 @@ export class ValidatorRegistrationTransaction extends Transaction {
this._recipients = [
{
address: this.stakingContractAddress,
amount: ZERO_VALUE_AMOUNT,
amount: this.amountToStake,
},
];
}
Expand Down Expand Up @@ -107,11 +115,11 @@ locking their VET into the built-in staker contract. Allowed values are 60480 (7
dependsOn: this.dependsOn,
nonce: this.nonce,
data: this.transactionData,
value: ZERO_VALUE_AMOUNT,
value: this.amountToStake,
sender: this.sender,
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: ZERO_VALUE_AMOUNT,
amountToStake: this.amountToStake,
validatorAddress: this.validator,
stakingPeriod: this.stakingPeriod,
};
Expand Down Expand Up @@ -147,6 +155,10 @@ locking their VET into the built-in staker contract. Allowed values are 60480 (7
this.stakingContractAddress = addValidationClause.to;
}

if (addValidationClause.value) {
this.amountToStake = new BigNumber(addValidationClause.value).toFixed();
}

// Extract validator and period from addValidation data
if (addValidationClause.data) {
this.transactionData = addValidationClause.data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import assert from 'assert';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { TransactionType } from '@bitgo/sdk-core';
import { TransactionClause } from '@vechain/sdk-core';
import BigNumber from 'bignumber.js';

import { TransactionBuilder } from './transactionBuilder';
import { Transaction } from '../transaction/transaction';
Expand Down Expand Up @@ -96,6 +97,17 @@ export class ValidatorRegistrationBuilder extends TransactionBuilder {
return this;
}

/**
* Sets the amount to stake for this validator registration tx (VET amount being sent).
*
* @param {string} amount - The amount to stake in wei
* @returns {ValidatorRegistrationBuilder} This transaction builder
*/
amountToStake(amount: string): this {
this.validatorRegistrationTransaction.amountToStake = amount;
return this;
}

/**
* Sets the validator address for this validator registration tx.
* @param {string} address - The validator address
Expand Down Expand Up @@ -127,9 +139,16 @@ export class ValidatorRegistrationBuilder extends TransactionBuilder {
throw new Error('transaction not defined');
}
assert(transaction.stakingContractAddress, 'Staking contract address is required');

assert(transaction.stakingPeriod, 'Staking period is required');
assert(transaction.validator, 'Validator address is required');
assert(transaction.amountToStake, 'Staking amount is required');

// Validate staking amount is within allowed range
const amountInVET = new BigNumber(transaction.amountToStake).dividedBy(new BigNumber(10).pow(18));
if (amountInVET.isLessThan(25_000_000) || amountInVET.isGreaterThan(600_000_000)) {
throw new Error('Staking amount must be between 25M and 600M VET');
}

this.validateAddress({ address: transaction.stakingContractAddress });
}

Expand Down
4 changes: 3 additions & 1 deletion modules/sdk-coin-vet/test/resources/vet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const DELEGATION_TRANSACTION =
'0xf9010327880166f0952a071e2a820190f85ef85c941e02b2953adefec225cf0ec49805b1146a4429c180b84408bbb8240000000000000000000000000000000000000000000000000000000000003d4500000000000000000000000000000021cbe0de65ea10f7658effeea70727154a81808303b46c8088f341b4c6b5ff5294c101b8829f5d674b8b043e95907b544b16a2c399bc04c7ebfcb9fa49956e050c59bbd5a510c57c6c135a3f03b12fe388409216809124a06d3c4ce797b5d97c5cd899d73d01dc4b66e63aade59b67639e44caedd5783a8e4d4f3c224f911acc5f43c387b54d22b2332172363a48e186d3bd52abec67d84e3d9e8c9696b241d29810a4e14e5801';

export const VALIDATOR_REGISTRATION_TRANSACTION =
'0xf8fb2788016754d8d0e8099340f85ef85c9400000000000000000000000000005374616b657280b844c3c4b13800000000000000000000000059b67d37be55997c96ea6f1890f08f825a41203c000000000000000000000000000000000000000000000000000000000000ec40818082b4ae808301bc4ec101b882ad3cef39b277cdb926f50ac04cbf11a68cf4a30b13311605978d57d92e92ee171a8bb734676cad0f7cdcdd935cf78b71b02e2193390ab752b032a6219131bf230119acb7f53ca1cf868a05f5c7b841e7052c8d779f876e6a6a6e5aa3daf0cd82dc6842d5fa276d4abc950b686e3d685261cae290dd20a13223a832e1b88be9ff0500';
'0xf90106278801690812847d899f40f869f8679400000000000000000000000000005374616b65728b14adf4b7320334b9000000b844c3c4b13800000000000000000000000059b67d37be55997c96ea6f1890f08f825a41203c000000000000000000000000000000000000000000000000000000000000ec408180826b84808303576bc101b8825600996530a8204d10944b52b933e1ce57744793eaaa93922d6b6cfbf53759ce6bd8c55bdafd06ef8c30b9549747008c5d4caad226d712710825d88271164acf00b6519806de49deacbf8079c8fd8698216289946988ea6c1bf86cf73740e1a8b620c75392c2d8cb5b9e608301303435d7174e70f627e37cd59d94644e35468cca01';

export const EXIT_DELEGATION_TRANSACTION =
'0xf8db278801640bf461bc7e1840f83df83b941e02b2953adefec225cf0ec49805b1146a4429c180a469e79b7d0000000000000000000000000000000000000000000000000000000000003d2d81808303525f808305f65ac101b8820cb393317793011b0a205973c77761f5c5c8652c21fe0115f527d2e2f2c1b5fc72a048107b263764312e9323f2ace9f30ce0beed873d7ef7f5432943330d2d5000a4a5f6439503f235ac6a5e17b47ac26c9e0c9e3be9dbd4cec3266fea324eb9bf5f806cedca59ff4144deb0ca18c41d9d6d600a86bf3d4e7b930bcec9b04c2e7301';
Expand All @@ -35,6 +35,7 @@ export const CLAIM_REWARDS_TRANSACTION =
export const STAKING_LEVEL_ID = 8;
export const STAKING_AUTORENEW = true;
export const STAKING_CONTRACT_ADDRESS = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';
export const BUILT_IN_STAKER_CONTRACT_ADDRESS = '0x00000000000000000000000000005374616b6572';

export const VALID_TOKEN_SIGNABLE_PAYLOAD =
'f8762788014ead140e77bbc140f85ef85c940000000000000000000000000000456e6572677980b844a9059cbb000000000000000000000000e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec000000000000000000000000000000000000000000000000016345785d8a000081808252088082faf8c101';
Expand Down Expand Up @@ -227,6 +228,7 @@ export const DELEGATE_TOKEN_ID = '15685';
export const STAKING_PERIOD = 259200;
export const DELEGATE_VALIDATOR = '0xae99cb89767a09d53e589a40cb4016974aba4b94';
export const VALIDATOR_REGISTRATION_VALIDATOR = '0x59b67d37be55997c96ea6f1890f08f825a41203c';
export const VALIDATOR_REGISTRATION_AMOUNT = '25000000000000000000000000';

export const SIGNED_DELEGATE_RAW_HEX =
'0xf8fa278801671187de8af70440f85ef85c941e02b2953adefec225cf0ec49805b1146a4429c180b84408bbb8240000000000000000000000000000000000000000000000000000000000003d2d00000000000000000000000000563ec3cafbbe7e60b04b3190e6eca66579706d818082cb5680820190c101b882313e783169eae670b1210e65f59b7c30cfd6c140c377257413fef378f70549f738a22a172ccc4ac7854ebc9952845fa90f7a676e5e9d9e277724ef3968e32e8a00f05ddf913b2b8fb884dbd5d5133217666ec4a68d077dce1537d270f25caf440e0f11232ecc4a5859ab49b2442cc0b0c0c1488302556e6323eec0498f1a42e17301';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('VET Validator Registration Transaction', function () {
const factory = new TransactionBuilderFactory(coins.get('tvet'));
const stakingPeriod = 60480;
const validatorAddress = '0x9a7aFCACc88c106f3bbD6B213CD0821D9224d945';
const amountToStake = '25000000000000000000000000'; // 25000000 VET
const amountLessThanMinStake = '24000000000000000000000000'; // 24000000 VET
const amountGreaterThanMaxStake = '650000000000000000000000000'; // 650000000 VET

// Helper function to create a basic transaction builder with common properties
const createBasicTxBuilder = () => {
Expand All @@ -32,6 +35,7 @@ describe('VET Validator Registration Transaction', function () {
const txBuilder = factory.getValidatorRegistrationBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.amountToStake(amountToStake);
txBuilder.sender('0x9378c12BD7502A11F770a5C1F223c959B2805dA9');
txBuilder.chainTag(0x27); // Testnet chain tag
txBuilder.blockRef('0x0000000000000000');
Expand All @@ -52,13 +56,16 @@ describe('VET Validator Registration Transaction', function () {
);
validatorRegistrationTransaction.stakingPeriod.should.equal(stakingPeriod);
validatorRegistrationTransaction.validator.should.equal(validatorAddress);
validatorRegistrationTransaction.amountToStake.should.equal(amountToStake);

// Verify clauses
validatorRegistrationTransaction.clauses.length.should.equal(1);
should.exist(validatorRegistrationTransaction.clauses[0].to);
should.exist(validatorRegistrationTransaction.clauses[0].value);
validatorRegistrationTransaction.clauses[0].to?.should.equal(
VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET
);
validatorRegistrationTransaction.clauses[0].value?.should.equal(amountToStake);

// Verify transaction data is correctly encoded using ethereumABI
should.exist(validatorRegistrationTransaction.clauses[0].data);
Expand Down Expand Up @@ -88,6 +95,7 @@ describe('VET Validator Registration Transaction', function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.validator(validatorAddress);
txBuilder.amountToStake(amountToStake);

await txBuilder.build().should.be.rejectedWith('Staking contract address is required');
});
Expand All @@ -96,6 +104,7 @@ describe('VET Validator Registration Transaction', function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.validator(validatorAddress);
txBuilder.amountToStake(amountToStake);

await txBuilder.build().should.be.rejectedWith('Staking period is required');
});
Expand All @@ -104,10 +113,40 @@ describe('VET Validator Registration Transaction', function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.amountToStake(amountToStake);

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

it('should throw error when amount is missing', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.validator(validatorAddress);

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

it('should throw error when amount is less than minimum stake', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.validator(validatorAddress);
txBuilder.amountToStake(amountLessThanMinStake);

await txBuilder.build().should.be.rejectedWith('Staking amount must be between 25M and 600M VET');
});

it('should throw error when amount is greater than maximum stake', async function () {
const txBuilder = createBasicTxBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.validator(validatorAddress);
txBuilder.amountToStake(amountGreaterThanMaxStake);

await txBuilder.build().should.be.rejectedWith('Staking amount must be between 25M and 600M VET');
});

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

Expand All @@ -121,6 +160,7 @@ describe('VET Validator Registration Transaction', function () {
const txBuilder = factory.getValidatorRegistrationBuilder();
txBuilder.stakingContractAddress(VALIDATOR_REGISTRATION_STAKER_CONTRACT_ADDRESS_TESTNET);
txBuilder.stakingPeriod(stakingPeriod);
txBuilder.amountToStake(amountToStake);
txBuilder.chainTag(0x27);
txBuilder.blockRef('0x0000000000000000');
txBuilder.expiration(64);
Expand Down Expand Up @@ -151,6 +191,7 @@ describe('VET Validator Registration Transaction', function () {
txBuilder.stakingPeriod(stakingPeriod);
// Not setting chainTag
txBuilder.blockRef('0x0000000000000000');
txBuilder.amountToStake(amountToStake);
txBuilder.expiration(64);
txBuilder.gas(100000);
txBuilder.gasPriceCoef(0);
Expand Down Expand Up @@ -184,6 +225,7 @@ describe('VET Validator Registration Transaction', function () {
tx.stakingContractAddress.should.equal(testData.VALIDATOR_REGISTRATION_STAKER_CONTRACT);
tx.stakingPeriod.should.equal(testData.VALIDATOR_REGISTRATION_STAKING_PERIOD);
tx.validator.should.equal(testData.VALIDATOR_REGISTRATION_VALIDATOR);
tx.amountToStake.should.equal(testData.VALIDATOR_REGISTRATION_AMOUNT);
tx.validator.should.startWith('0x');
tx.validator.should.equal(tx.validator.toLowerCase());
should.exist(tx.inputs);
Expand Down
Loading