diff --git a/contracts/TestProviderPool.sol b/contracts/TestProviderPool.sol index 54d7539..e390076 100644 --- a/contracts/TestProviderPool.sol +++ b/contracts/TestProviderPool.sol @@ -2,19 +2,17 @@ pragma solidity ^0.4.24; import "../contracts/ProviderPool.sol"; +// This contract adds public methods to be able to call internal methods from web3js contract TestProviderPool is ProviderPool { - // @dev convenience method to be able to call addProvider from web3js (it has internal visibility) function publicAddProvider(address _provider, uint _bondedAmount) public { addProvider(_provider, _bondedAmount); } - // @dev convenience method to be able to call updateProvider from web3js (it has internal visibility) function publicUpdateProvider(address _provider, uint _bondedAmount) public { updateProvider(_provider, _bondedAmount); } - // @dev convenience method to be able to call removeProvider from web3js (it has internal visibility) function publicRemoveProvider(address _provider) public { removeProvider(_provider); } diff --git a/contracts/TestTransmuteDPOS.sol b/contracts/TestTransmuteDPOS.sol new file mode 100644 index 0000000..fcf7c6f --- /dev/null +++ b/contracts/TestTransmuteDPOS.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +import "../contracts/TransmuteDPOS.sol"; + +// This contract adds public methods to be able to call internal methods from web3js +contract TestTransmuteDPOS is TransmuteDPOS { + function publicResignAsProvider(address _provider) public { + resignAsProvider(_provider); + } +} + diff --git a/contracts/TransmuteDPOS.sol b/contracts/TransmuteDPOS.sol index b2a5af0..bc6e748 100644 --- a/contracts/TransmuteDPOS.sol +++ b/contracts/TransmuteDPOS.sol @@ -63,7 +63,11 @@ contract TransmuteDPOS is TransmuteToken, RoundManager, ProviderPool { uint public numberOfProviders; mapping(address => Provider) public providers; - mapping (address => uint) public withdrawBlocks; + struct WithdrawInformation { + uint withdrawBlock; + uint amount; + } + mapping (address => WithdrawInformation) public withdrawInformations; // FIXME: Those are temporary values constructor() public { @@ -80,8 +84,8 @@ contract TransmuteDPOS is TransmuteToken, RoundManager, ProviderPool { require(_feeShare <= 100); Provider storage p = providers[msg.sender]; Delegator storage d = delegators[msg.sender]; + // Provider has to be a Delegator to himself require(d.delegateAddress == msg.sender); - require(d.amountBonded > 0); if (p.status == ProviderStatus.Unregistered) { numberOfProviders = numberOfProviders.add(1); addProvider(msg.sender, p.totalAmountBonded); @@ -96,14 +100,15 @@ contract TransmuteDPOS is TransmuteToken, RoundManager, ProviderPool { p.feeShare = _feeShare; } - function resignAsProvider() external { - require(providers[msg.sender].status != ProviderStatus.Unregistered); - removeProvider(msg.sender); - delete providers[msg.sender]; - emit ProviderResigned(msg.sender); + function resignAsProvider(address _provider) internal { + require(providers[_provider].status != ProviderStatus.Unregistered); + removeProvider(_provider); + delete providers[_provider]; + emit ProviderResigned(_provider); } function bond(address _provider, uint _amount) external { + require(_amount > 0); Provider storage p = providers[_provider]; // A delegator is only allowed to bond to an Unregistered provider if the provider is himself // otherwise _provider has to be associated with a Registered provider @@ -123,32 +128,43 @@ contract TransmuteDPOS is TransmuteToken, RoundManager, ProviderPool { function unbond() external { // Only Bonded Delegators can call the function require(delegatorStatus(msg.sender) == DelegatorStatus.Bonded); - // TODO: What if a Provider calls unbond() on himself ? - // Should he resign ? - // What about the tokens of the Delegators that bonded to him ? - // For now we prevent providers from calling this function - require(providers[msg.sender].status == ProviderStatus.Unregistered); - Delegator storage d = delegators[msg.sender]; Provider storage p = providers[d.delegateAddress]; // Sets the block number from which the Delegator will be able to withdraw() his tokens - withdrawBlocks[msg.sender] = block.number.add(unbondingPeriod); + uint withdrawBlock = block.number.add(unbondingPeriod); + uint amount = d.amountBonded; + withdrawInformations[msg.sender] = WithdrawInformation(withdrawBlock, amount); // Decrease the totalAmountBonded parameter of the provider - p.totalAmountBonded = p.totalAmountBonded.sub(d.amountBonded); - updateProvider(d.delegateAddress, p.totalAmountBonded); - emit DelegatorUnbonded(msg.sender, d.delegateAddress, d.amountBonded); - // Remove delegator from the list. He is no longer in the the Bonded State + p.totalAmountBonded = p.totalAmountBonded.sub(amount); + if (d.delegateAddress == msg.sender) { + // A Provider has to be a Delegator to himself + // Therefore if a Provider unbonds he should resign + resignAsProvider(msg.sender); + } else { + // Otherwise it should update the position of the Provider in the pool + updateProvider(d.delegateAddress, p.totalAmountBonded); + } + emit DelegatorUnbonded(msg.sender, d.delegateAddress, amount); + // Remove delegator from the list. He is now no longer in the the Bonded State delete delegators[msg.sender]; } + function withdraw() external { + WithdrawInformation storage withdrawInformation = withdrawInformations[msg.sender]; + require(withdrawInformation.withdrawBlock <= block.number); + require(delegatorStatus(msg.sender) == DelegatorStatus.UnbondedWithTokensToWithdraw); + this.transfer(msg.sender, withdrawInformation.amount); + delete withdrawInformations[msg.sender]; + } + // TODO: Create the same function for Providers // This will remove the need for ProviderStatus inside the Provider Struct function delegatorStatus(address _delegator) public view returns (DelegatorStatus) { if (delegators[_delegator].amountBonded != 0) { // If _delegator is in the mapping, he is Bonded return DelegatorStatus.Bonded; - } else if (withdrawBlocks[_delegator] != 0) { - // Else if _delegator has a withdrawBlock, he just called unbond() and didn't withdraw() yet + } else if (withdrawInformations[_delegator].withdrawBlock != 0) { + // Else if _delegator has some withdrawInformation, he just called unbond() and didn't withdraw() yet return DelegatorStatus.UnbondedWithTokensToWithdraw; } else { // Else he is Unbonded: either he didn't call bond() or he called bond() unbond() and withdraw() diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 2a1d4df..4370bb1 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,5 +1,5 @@ const TST = artifacts.require('./TransmuteToken.sol'); -const TransmuteDPOS = artifacts.require('./TransmuteDPOS.sol'); +const TestTransmuteDPOS = artifacts.require('./TestTransmuteDPOS.sol'); const RoundManager = artifacts.require('./RoundManager.sol'); const SortedDoublyLL = artifacts.require('./SortedDoublyLL.sol'); const TestProviderPool = artifacts.require('./TestProviderPool.sol'); @@ -7,8 +7,8 @@ const TestProviderPool = artifacts.require('./TestProviderPool.sol'); module.exports = deployer => { deployer.deploy(TST); deployer.deploy(SortedDoublyLL); - deployer.link(SortedDoublyLL, TransmuteDPOS); - deployer.deploy(TransmuteDPOS); + deployer.link(SortedDoublyLL, TestTransmuteDPOS); + deployer.deploy(TestTransmuteDPOS); deployer.deploy(RoundManager); deployer.link(SortedDoublyLL, TestProviderPool); deployer.deploy(TestProviderPool); diff --git a/test/TestTransmuteDPOS.spec.js b/test/TestTransmuteDPOS.spec.js index ab73301..3046480 100644 --- a/test/TestTransmuteDPOS.spec.js +++ b/test/TestTransmuteDPOS.spec.js @@ -1,4 +1,4 @@ -const TransmuteDPOS = artifacts.require('./TransmuteDPOS.sol'); +const TransmuteDPOS = artifacts.require('./TestTransmuteDPOS.sol'); const { blockMiner, assertFail } = require('./utils.js'); require('truffle-test-utils').init(); @@ -27,6 +27,17 @@ contract('TransmuteDPOS', accounts => { await tdpos.provider(pricePerStorageMineral, pricePerComputeMineral, blockRewardCut, feeShare, {from: provider}); } + async function initNew() { + tdpos = await TransmuteDPOS.new(); + contractAddress = tdpos.address; + for(let i = 0; i < 10; i++) { + await tdpos.mint(accounts[i], 1000, {from: accounts[0]}); + } + await tdpos.setMaxNumberOfProviders(PROVIDER_POOL_SIZE); + await blockMiner.mineUntilEndOfElectionPeriod(tdpos); + await tdpos.initializeRound(); + } + describe('provider', () => { before(async () => { @@ -50,17 +61,20 @@ contract('TransmuteDPOS', accounts => { it('should initially set totalBondedAmount to the amount the Provider bonded to himself', async () => { await approveBondProvider(22, 10, 1, 25, 42, accounts[0]); const provider = await tdpos.providers.call(accounts[0]); - assert.equal(42, provider[5]); // [5] is totalBondedAmount + const totalBondedAmount = provider[5]; + assert.equal(42, totalBondedAmount); }); it("should register a Provider's parameters", async () => { await approveBondProvider(10, 20, 2, 35, 1, accounts[1]); const provider = await tdpos.providers.call(accounts[1]); - assert.equal(provider[0], PROVIDER_REGISTERED); // [0] is providerStatus - assert.equal(provider[1].toNumber(), 10); // [1] is pricePerStorageMineral - assert.equal(provider[2].toNumber(), 20); // [2] is pricePerComputeMineral - assert.equal(provider[3].toNumber(), 2); // [3] is blockRewardCut - assert.equal(provider[4].toNumber(), 35); // [4] is feeShare + let [providerStatus, pricePerStorageMineral, pricePerComputeMineral, + blockRewardCut, feeShare] = provider; + assert.equal(PROVIDER_REGISTERED, providerStatus); + assert.equal(10, pricePerStorageMineral); + assert.equal(20, pricePerComputeMineral); + assert.equal(2, blockRewardCut); + assert.equal(35, feeShare); }); it('should fail with invalid parameters', async() => { @@ -83,7 +97,8 @@ contract('TransmuteDPOS', accounts => { await blockMiner.mineUntilLastBlockBeforeLockPeriod(tdpos); await tdpos.provider(23, 10, 1, 25, {from: accounts[0]}); const provider = await tdpos.providers(accounts[0]); - assert.equal(23, provider[1]); // [1] is pricePerStorageMineral + const pricePerStorageMineral = provider[1]; + assert.equal(23, pricePerStorageMineral); }); it('should fail during the lock period of an active round', async () => { @@ -126,7 +141,7 @@ contract('TransmuteDPOS', accounts => { assert.equal(false, await tdpos.containsProvider(accounts[3])); // Check the size of the pool increases by 1 let providerPool = await tdpos.providerPool.call(); - const previousSize = providerPool[3].toNumber(); // [3] is current size of the pool + const previousSize = providerPool[3].toNumber(); await approveBondProvider(21, 13, 3, 26, 1, accounts[3]); providerPool = await tdpos.providerPool.call(); assert.equal(previousSize + 1, providerPool[3]); @@ -136,8 +151,8 @@ contract('TransmuteDPOS', accounts => { it('should fail if Provider is Unregistered and size == maxSize', async () => { let providerPool = await tdpos.providerPool.call(); - const maxSize = providerPool[2].toNumber(); // [2] is maxSize - let currentSize = providerPool[3]; // [3] is current size + const maxSize = providerPool[2].toNumber(); + let currentSize = providerPool[3]; assert.isAbove(maxSize, currentSize.toNumber()); await approveBondProvider(20 ,10, 2, 25, 1, accounts[4]); providerPool = await tdpos.providerPool.call(); @@ -148,7 +163,8 @@ contract('TransmuteDPOS', accounts => { it('should work if Provider is Registered and size == maxSize', async () => { let provider = await tdpos.providers.call(accounts[4]); - assert.equal(20, provider[1]); // [1] is pricePerStorageMineral + const pricePerStorageMineral = provider[1]; + assert.equal(20, pricePerStorageMineral); await tdpos.provider(21 ,10, 2, 25, {from: accounts[4]}); provider = await tdpos.providers.call(accounts[4]); assert.equal(21, provider[1]); @@ -158,14 +174,7 @@ contract('TransmuteDPOS', accounts => { describe('resignAsProvider', () => { before(async () => { - tdpos = await TransmuteDPOS.new(); - contractAddress = tdpos.address; - for(let i = 0; i < 5; i++) { - await tdpos.mint(accounts[i], 1000, {from: accounts[0]}); - } - await tdpos.setMaxNumberOfProviders(PROVIDER_POOL_SIZE); - await blockMiner.mineUntilEndOfElectionPeriod(tdpos); - await tdpos.initializeRound(); + await initNew(); await approveBondProvider(22, 10, 1, 25, 1, accounts[0]); await approveBondProvider(22, 10, 1, 25, 1, accounts[1]); await approveBondProvider(22, 10, 1, 25, 1, accounts[2]); @@ -173,25 +182,28 @@ contract('TransmuteDPOS', accounts => { it('should remove the Provider from the provider mapping', async () => { const registeredProvider = await tdpos.providers.call(accounts[0]); - assert.equal(PROVIDER_REGISTERED, registeredProvider[0]); // [0] is providerStatus - await tdpos.resignAsProvider({from: accounts[0]}); + let status = registeredProvider[0]; + assert.equal(PROVIDER_REGISTERED, status); + await tdpos.publicResignAsProvider(accounts[0]); const resignedProvider = await tdpos.providers.call(accounts[0]); - assert.equal(PROVIDER_UNREGISTERED, resignedProvider[0]); // [0] is providerStatus - assert.equal(0, resignedProvider[1]); // [1] is pricePerStorageMineral - assert.equal(0, resignedProvider[2]); // [2] is pricePerComputeMineral - assert.equal(0, resignedProvider[3]); // [3] is blockRewardCut - assert.equal(0, resignedProvider[4]); // [4] is feeShare - assert.equal(0, resignedProvider[5]); // [5] is totalAmountBonded + let [providerStatus, pricePerStorageMineral, pricePerComputeMineral, + blockRewardCut, feeShare, totalAmountBonded] = resignedProvider; + assert.equal(PROVIDER_UNREGISTERED, providerStatus); + assert.equal(0, pricePerStorageMineral); + assert.equal(0, pricePerComputeMineral); + assert.equal(0, blockRewardCut); + assert.equal(0, feeShare); + assert.equal(0, totalAmountBonded); }); it('should remove the Provider from the providerPool', async () => { assert.equal(true, await tdpos.containsProvider(accounts[1])); - await tdpos.resignAsProvider({from: accounts[1]}); + await tdpos.publicResignAsProvider(accounts[1]); assert.equal(false, await tdpos.containsProvider(accounts[1])); }); it('should send a ProviderResigned event', async () => { - const result = await tdpos.resignAsProvider({from: accounts[2]}); + const result = await tdpos.publicResignAsProvider(accounts[2]); assert.web3Event(result, { event: 'ProviderResigned', args: { @@ -201,21 +213,14 @@ contract('TransmuteDPOS', accounts => { }); it("should fail if the transaction's sender is not a Provider", async () => { - await assertFail( tdpos.resignAsProvider({from: accounts[3]}) ); + await assertFail( tdpos.publicResignAsProvider(accounts[3]) ); }); }); describe('bond', () => { before(async () => { - tdpos = await TransmuteDPOS.new(); - contractAddress = tdpos.address; - for(let i = 0; i < 10; i++) { - await tdpos.mint(accounts[i], 1000, {from: accounts[0]}); - } - await tdpos.setMaxNumberOfProviders(PROVIDER_POOL_SIZE); - await blockMiner.mineUntilEndOfElectionPeriod(tdpos); - await tdpos.initializeRound(); + await initNew(); await approveBondProvider(22, 10, 1, 25, 1, accounts[0]); await approveBondProvider(10, 20, 2, 35, 1, accounts[1]); }); @@ -230,15 +235,21 @@ contract('TransmuteDPOS', accounts => { await tdpos.approve(contractAddress, 10, {from: accounts[5]}); await tdpos.bond(accounts[0], 10, {from: accounts[5]}); const firstDelegator = await tdpos.delegators.call(accounts[5]); - assert.equal(accounts[0], firstDelegator[0]); // [0] is delegateAddress - assert.equal(10, firstDelegator[1]); // [1] is amountBonded + let [delegateAddress, amountBonded] = firstDelegator; + assert.equal(accounts[0], delegateAddress); + assert.equal(10, amountBonded); }); - it('should increase the totalAmountBonded of the Provider', async () => { + it('should fail if amount is zero', async () => { await tdpos.approve(contractAddress, 20, {from: accounts[6]}); + await assertFail( tdpos.bond(accounts[0], 0, {from: accounts[6]}) ); + }); + + it('should increase the totalAmountBonded of the Provider', async () => { await tdpos.bond(accounts[0], 20, {from: accounts[6]}); const provider = await tdpos.providers.call(accounts[0]); - assert.equal(31, provider[5].toNumber()); // [5] is totalAmountBonded + const totalAmountBonded = provider[5]; + assert.equal(31, totalAmountBonded); }); it('should fail if the address is not a registered Provider address', async () => { @@ -280,7 +291,7 @@ contract('TransmuteDPOS', accounts => { it('should update the totalBondedAmount of the Provider in the providerPool if he is already registered', async () => { let provider = await tdpos.getProvider.call(accounts[0]); - let previousBondedAmount = provider[0].toNumber(); // [0] is bonded amount + const previousBondedAmount = provider[0].toNumber(); await tdpos.approve(contractAddress, 300, {from: accounts[7]}); await tdpos.bond(accounts[0], 300, {from: accounts[7]}); provider = await tdpos.getProvider.call(accounts[0]); @@ -304,14 +315,7 @@ contract('TransmuteDPOS', accounts => { describe('unbond', () => { before(async () => { - tdpos = await TransmuteDPOS.new(); - contractAddress = tdpos.address; - for(let i = 0; i < 10; i++) { - await tdpos.mint(accounts[i], 1000, {from: accounts[0]}); - } - await tdpos.setMaxNumberOfProviders(PROVIDER_POOL_SIZE); - await blockMiner.mineUntilEndOfElectionPeriod(tdpos); - await tdpos.initializeRound(); + await initNew(); await approveBondProvider(22, 10, 1, 25, 1, accounts[0]); await approveBondProvider(22, 10, 1, 25, 1, accounts[1]); await tdpos.approve(contractAddress, 300, {from: accounts[2]}); @@ -329,38 +333,50 @@ contract('TransmuteDPOS', accounts => { await assertFail( tdpos.unbond({from: accounts[9]}) ); }); - it("should set withdrawBlock for the Delegator's address", async () => { - assert.equal(0, await tdpos.withdrawBlocks.call(accounts[2])); + it("should set withdrawInformation for the Delegator's address", async () => { + let withdrawInformation = await tdpos.withdrawInformations.call(accounts[2]); + let withdrawBlock = withdrawInformation[0]; + assert.equal(0, withdrawBlock); await tdpos.unbond({from: accounts[2]}); const currentBlockNumber = web3.eth.blockNumber; const unbondingPeriod = (await tdpos.unbondingPeriod.call()).toNumber(); - const withdrawBlock = await tdpos.withdrawBlocks.call(accounts[2]); + withdrawInformation = await tdpos.withdrawInformations.call(accounts[2]); + withdrawBlock = withdrawInformation[0]; assert.equal(currentBlockNumber + unbondingPeriod, withdrawBlock); }); it('should decrease the totalBondedAmount of the Provider by the unbonded amount', async () => { - const totalBondedAmount = (await tdpos.providers(accounts[0]))[5].toNumber(); // [5] is totalAmountBonded - const bondedAmount = (await tdpos.delegators(accounts[3]))[1].toNumber(); // [1] is amountBonded + const totalBondedAmount = (await tdpos.providers(accounts[0]))[5]; + const bondedAmount = (await tdpos.delegators(accounts[3]))[1]; const newAmount = totalBondedAmount - bondedAmount; await tdpos.unbond({from: accounts[3]}); - assert.equal(newAmount, (await tdpos.providers([accounts[0]]))[5].toNumber()); - assert.equal(newAmount, (await tdpos.getProvider(accounts[0]))[0].toNumber()); // [0] is totalAmountBonded + assert.equal(newAmount, (await tdpos.providers([accounts[0]]))[5]); + assert.equal(newAmount, (await tdpos.getProvider(accounts[0]))[0]); + }); + + it('should resign the Provider if a Provider calls the function', async () => { + assert.equal(true, await tdpos.containsProvider(accounts[1])); + await tdpos.unbond({from: accounts[1]}); + assert.equal(false, await tdpos.containsProvider(accounts[1])); }); it('should remove the Delegator from the mapping', async () => { let delegator = await tdpos.delegators.call(accounts[4]); - assert.notEqual(0,delegator[0]); // [0] is delegateAddress; - assert.notEqual(0,delegator[1]); // [1] is amountBonded; + let [delegateAddress, amountBonded] = delegator; + assert.notEqual(0, delegateAddress); + assert.notEqual(0, amountBonded); await tdpos.unbond({from: accounts[4]}); delegator = await tdpos.delegators.call(accounts[4]); - assert.equal(0,delegator[0]); - assert.equal(0,delegator[1]); + [delegateAddress, amountBonded] = delegator; + assert.equal(0, delegateAddress); + assert.equal(0, amountBonded); }); it('should emit the DelegateUnbonded event', async () => { const delegator = await tdpos.delegators.call(accounts[5]); - assert.equal(accounts[0], delegator[0]); // [0] is delegateAddress; - assert.equal(300, delegator[1]); // [1] is amountBonded; + let [delegateAddress, amountBonded] = delegator; + assert.equal(accounts[0], delegateAddress); + assert.equal(300, amountBonded); const result = await tdpos.unbond({from: accounts[5]}); assert.web3Event(result, { event: 'DelegatorUnbonded', @@ -376,22 +392,16 @@ contract('TransmuteDPOS', accounts => { describe('delegatorStatus', () => { before(async () => { - tdpos = await TransmuteDPOS.new(); - contractAddress = tdpos.address; - for(let i = 0; i < 10; i++) { - await tdpos.mint(accounts[i], 1000, {from: accounts[0]}); - } - await tdpos.setMaxNumberOfProviders(PROVIDER_POOL_SIZE); - await blockMiner.mineUntilEndOfElectionPeriod(tdpos); - await tdpos.initializeRound(); + await initNew(); await approveBondProvider(22, 10, 1, 25, 1, accounts[0]); }); it('should return Unbonded if address is not a Delegator', async() => { // Assert that address is not a delegator const delegator = await tdpos.delegators.call(accounts[3]); - assert.equal(0,delegator[0]); - assert.equal(0,delegator[1]); + let [delegateAddress, amountBonded] = delegator; + assert.equal(0, delegateAddress); + assert.equal(0, amountBonded); assert.equal(DELEGATOR_UNBONDED, await tdpos.delegatorStatus(accounts[3])); }); @@ -408,8 +418,65 @@ contract('TransmuteDPOS', accounts => { assert.equal(DELEGATOR_UNBONDED_WITH_TOKENS_TO_WITHDRAW, await tdpos.delegatorStatus(accounts[4])); }); - // TODO when withdraw is implemented - // it('should return Unbonded if Delegator has called unbond() and withdraw()'); + it('should return Unbonded if Delegator has called unbond() and withdraw()', async () => { + assert.equal(DELEGATOR_UNBONDED_WITH_TOKENS_TO_WITHDRAW, await tdpos.delegatorStatus(accounts[4])); + const withdrawInformation = await tdpos.withdrawInformations(accounts[4]); + const withdrawBlock = withdrawInformation[0]; + await blockMiner.mineUntilBlock(withdrawBlock - 1); + await tdpos.withdraw({from: accounts[4]}); + assert.equal(DELEGATOR_UNBONDED, await tdpos.delegatorStatus(accounts[4])); + }); + }); + + describe('withdraw', () => { + + before(async () => { + await initNew(); + await approveBondProvider(22, 10, 1, 25, 1, accounts[0]); + await approveBondProvider(22, 10, 1, 25, 1, accounts[1]); + await tdpos.approve(contractAddress, 300, {from: accounts[2]}); + await tdpos.bond(accounts[0], 300, {from: accounts[2]}); + await tdpos.approve(contractAddress, 300, {from: accounts[3]}); + await tdpos.bond(accounts[0], 300, {from: accounts[3]}); + }); + + it('should fail if withdrawBlock has not been reached', async () => { + await tdpos.unbond({from: accounts[2]}); + assert.equal(DELEGATOR_UNBONDED_WITH_TOKENS_TO_WITHDRAW, await tdpos.delegatorStatus(accounts[2])); + const withdrawInformation = await tdpos.withdrawInformations(accounts[2]); + const withdrawBlock = withdrawInformation[0]; + await blockMiner.mineUntilBlock(withdrawBlock - 2); + // At this point we are 1 block away from withdrawBlock + await assertFail( tdpos.withdraw({from: accounts[2]}) ); + // At this point we reached withdrawBlock so it should work + await tdpos.withdraw({from: accounts[2]}); + assert.equal(DELEGATOR_UNBONDED, await tdpos.delegatorStatus(accounts[2])); + }); + + it('should fail if Delegator is in Bonded state', async () => { + assert.equal(DELEGATOR_BONDED, await tdpos.delegatorStatus(accounts[3])); + await assertFail( tdpos.withdraw({from: accounts[3]}) ); + }); + + it('should fail if Delegator is in Unbonded state', async () => { + assert.equal(DELEGATOR_UNBONDED, await tdpos.delegatorStatus(accounts[4])); + await assertFail( tdpos.withdraw({from: accounts[4]}) ); + }); + + it('should transfer the right amount of tokens back to the Delegator', async () => { + const previousBalance = await tdpos.balanceOf(accounts[3]); + await tdpos.unbond({from: accounts[3]}); + const withdrawInformation = await tdpos.withdrawInformations(accounts[3]); + const withdrawBlock = withdrawInformation[0]; + await blockMiner.mineUntilBlock(withdrawBlock - 1); + await tdpos.withdraw({from: accounts[3]}); + const newBalance = await tdpos.balanceOf(accounts[3]); + assert.equal(previousBalance.plus(300).toNumber(), await tdpos.balanceOf(accounts[3])); + }); + + it('should switch Delegator status to Unbonded', async() => { + assert.equal(DELEGATOR_UNBONDED, await tdpos.delegatorStatus(accounts[3])); + }); }); }); diff --git a/test/utils.js b/test/utils.js index 07ef199..a6b57a5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -21,6 +21,11 @@ class BlockMiner { const padding = electionPeriodLength - rateLockDeadline - 1 - currentBlockNumber % electionPeriodLength - 1; await this.mine(padding); } + + async mineUntilBlock(blockNumber) { + const padding = blockNumber - web3.eth.blockNumber; + await this.mine(padding); + } } module.exports.blockMiner = new BlockMiner();