@chorus-one/ton
Version:
All-in-one tooling for building staking dApps on TON
134 lines (120 loc) • 4.39 kB
text/typescript
import { TonNominatorPoolStaker } from '@chorus-one/ton'
import { Address, TupleItem, TupleReader } from '@ton/core'
import { describe, it } from 'mocha'
import { use, assert, expect, spy } from 'chai'
import { chaiAsPromised } from 'chai-promised'
import spies from 'chai-spies'
// Use chai-as-promised plugin for async tests
use(chaiAsPromised)
use(spies)
describe('TonStaker', () => {
const delegatorAddress = '0QDsF87nkTYgkvu1z5xveCEGTRnZmEVaVT0gdxoeyaNvmoCr'
const validatorAddress = 'kf8SWCvzf6eJK4Q0ZOe14PqDdsT5wk0_Ni0wAThL0cVorNVU'
const staker = new TonNominatorPoolStaker({
rpcUrl: 'https://ton.fake.website',
addressDerivationConfig: {
walletContractVersion: 4,
workchain: 0,
bounceable: false,
testOnly: true,
urlSafe: true,
isBIP39: false
}
})
const getPoolDataResponse: TupleItem[] = [
{ type: 'tuple', items: [] },
{ type: 'int', value: BigInt(1) }, // nominators count
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'int', value: BigInt(10) }, // max nominator count
{ type: 'tuple', items: [] },
{ type: 'int', value: BigInt(0) }, // nominator stake
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] },
{ type: 'tuple', items: [] }
]
const getNominatorListResponse: TupleItem[] = [{ type: 'tuple', items: [] }]
spy.on(staker, ['getClient'], () => {
return {
runMethod: async (
_address: Address,
name: string,
_stack?: TupleItem[]
): Promise<{ gas_used: number; stack: TupleReader }> => {
let stackmock: TupleItem[]
switch (name) {
case 'get_pool_data': {
stackmock = getPoolDataResponse
break
}
case 'list_nominators': {
stackmock = getNominatorListResponse
break
}
default:
throw new Error(`Unknown method: ${name}`)
}
return {
gas_used: 0,
stack: new TupleReader(stackmock)
}
}
}
})
it('should generate correct unsigned delegate tx', async () => {
const { tx } = await staker.buildStakeTx({
delegatorAddress,
validatorAddress,
amount: '0.5'
})
assert.isDefined(tx.messages, 'tx.messages should be defined')
if (tx.messages) {
assert.equal(tx.messages[0]?.amount, BigInt('500000000'))
assert.equal(tx.messages[0]?.bounceable, true)
assert.equal(tx.messages[0]?.payload, 'd')
assert.equal(tx.messages[0]?.address, 'kf8SWCvzf6eJK4Q0ZOe14PqDdsT5wk0_Ni0wAThL0cVorNVU')
}
})
it('should handle amount fuzzing correctly', async () => {
await Promise.all(
[
['0', '0'], // zero handlung
['1', '1000000000'], // 1 * denomMultiplier = denomMultiplier
['1.2', '1200000000'], // most common case
['0.123456789', '123456789'], // max precision
['1000000000', '1000000000000000000'], // unlikely edge case to send 1 000 000 000 macro tokens
['0.1234567891', '', 'Invalid number'], // over max precision
['', '0'], // empty string defaults to 0
['abc', '', 'Cannot convert abc to a BigInt'], // text is not a valid number
['-1', '', 'amount to stake (-1) is less than the minimum stake required (0)'] // negive amounts are not allowed
].map(async (testData) => {
const [amount, expectedAmount, expectedError] = testData
const runTest = async (amount: string): Promise<bigint> => {
const { tx } = await staker.buildStakeTx({
delegatorAddress,
validatorAddress,
amount
})
if (tx.messages === undefined) {
throw new Error('tx.message is undefined')
}
return tx.messages[0]?.amount
}
if (expectedError) {
await expect(runTest(amount)).to.be.rejectedWith(expectedError)
} else {
const returnedAmount = await runTest(amount)
// check if we have the expected amount
assert.equal(returnedAmount.toString(), expectedAmount)
}
})
)
})
})