diff --git a/implementation/contracts/deposit/DepositFunding.sol b/implementation/contracts/deposit/DepositFunding.sol index b04acef62..381e055ee 100644 --- a/implementation/contracts/deposit/DepositFunding.sol +++ b/implementation/contracts/deposit/DepositFunding.sol @@ -287,7 +287,7 @@ library DepositFunding { bytes memory _merkleProof, uint256 _txIndexInBlock, bytes memory _bitcoinHeaders - ) public returns (bool) { //TODO Implement ACL for Vending Machine + ) public returns (bool) { require(_d.inAwaitingBTCFundingProof(), "Not awaiting funding"); diff --git a/implementation/contracts/system/VendingMachine.sol b/implementation/contracts/system/VendingMachine.sol index 96da9a3c8..b25331a9d 100644 --- a/implementation/contracts/system/VendingMachine.sol +++ b/implementation/contracts/system/VendingMachine.sol @@ -21,35 +21,6 @@ contract VendingMachine { depositOwnerToken = DepositOwnerToken(_depositOwnerToken); } - /// @notice Qualifies a deposit for minting TBTC. - function qualifyDeposit( - address payable _depositAddress, - bytes4 _txVersion, - bytes memory _txInputVector, - bytes memory _txOutputVector, - bytes4 _txLocktime, - uint8 _fundingOutputIndex, - bytes memory _merkleProof, - uint256 _txIndexInBlock, - bytes memory _bitcoinHeaders - ) public { - Deposit _d = Deposit(_depositAddress); - require( - _d.provideBTCFundingProof( - _txVersion, - _txInputVector, - _txOutputVector, - _txLocktime, - _fundingOutputIndex, - _merkleProof, - _txIndexInBlock, - _bitcoinHeaders - ), - "failed to provide funding proof"); - // mint the signer fee to the Deposit - tbtcToken.mint(_depositAddress, DepositUtils.signerFee()); - } - /// @notice Determines whether a deposit is qualified for minting TBTC. /// @param _depositAddress the address of the deposit function isQualified(address payable _depositAddress) public returns (bool) { @@ -63,8 +34,9 @@ contract VendingMachine { require(depositOwnerToken.exists(_dotId), "Deposit Owner Token does not exist"); require(isQualified(address(_dotId)), "Deposit must be qualified"); - require(tbtcToken.balanceOf(msg.sender) >= getDepositValueLessSignerFee(), "Not enough TBTC for DOT exchange"); - tbtcToken.burnFrom(msg.sender, getDepositValueLessSignerFee()); + uint256 getDepositValue = getDepositValue(); + require(tbtcToken.balanceOf(msg.sender) >= getDepositValue, "Not enough TBTC for DOT exchange"); + tbtcToken.burnFrom(msg.sender, getDepositValue); // TODO do we need the owner check below? transferFrom can be approved for a user, which might be an interesting use case. require(depositOwnerToken.ownerOf(_dotId) == address(this), "Deposit is locked"); @@ -79,16 +51,66 @@ contract VendingMachine { require(isQualified(address(_dotId)), "Deposit must be qualified"); depositOwnerToken.transferFrom(msg.sender, address(this), _dotId); - tbtcToken.mint(msg.sender, getDepositValueLessSignerFee()); + + // If the backing Deposit does not have a signer fee in escrow, mint it. + if(tbtcToken.balanceOf(address(_dotId)) < DepositUtils.signerFee()){ + tbtcToken.mint(msg.sender, getDepositValueLessSignerFee()); + tbtcToken.mint(address(_dotId), DepositUtils.signerFee()); + } + else{ + tbtcToken.mint(msg.sender, getDepositValue()); + } + } + + // WRAPPERS + + /// @notice Qualifies a deposit and mints TBTC. + /// @dev User must allow VendingManchine to transfer DOT + function unqualifiedDepositToTbtc( + address payable _depositAddress, + bytes4 _txVersion, + bytes memory _txInputVector, + bytes memory _txOutputVector, + bytes4 _txLocktime, + uint8 _fundingOutputIndex, + bytes memory _merkleProof, + uint256 _txIndexInBlock, + bytes memory _bitcoinHeaders + ) public { + Deposit _d = Deposit(_depositAddress); + require( + _d.provideBTCFundingProof( + _txVersion, + _txInputVector, + _txOutputVector, + _txLocktime, + _fundingOutputIndex, + _merkleProof, + _txIndexInBlock, + _bitcoinHeaders + ), + "failed to provide funding proof"); + + dotToTbtc(uint256(_depositAddress)); } + // HELPERS + // TODO temporary helper function /// @notice Gets the Deposit lot size less signer fees /// @return amount in TBTC - function getDepositValueLessSignerFee() internal returns (uint) { + function getDepositValue() internal returns (uint) { uint256 _multiplier = TBTCConstants.getSatoshiMultiplier(); - uint256 _signerFee = DepositUtils.signerFee(); uint256 _totalValue = TBTCConstants.getLotSize().mul(_multiplier); + return _totalValue; + } + + // TODO temporary helper function + /// @notice Gets the Deposit lot size + /// @return amount in TBTC + function getDepositValueLessSignerFee() internal returns (uint) { + uint256 _signerFee = DepositUtils.signerFee(); + uint256 _totalValue = getDepositValue(); return _totalValue.sub(_signerFee); } } \ No newline at end of file diff --git a/implementation/test/VendingMachineTest.js b/implementation/test/VendingMachineTest.js index 9a9bfa63f..b6eacaf55 100644 --- a/implementation/test/VendingMachineTest.js +++ b/implementation/test/VendingMachineTest.js @@ -75,8 +75,9 @@ contract('VendingMachine', (accounts) => { let assertBalance let dotId - // For lack of a better design, this is the amount of TBTC exchanged for DOT's. - const depositValueLessSignerFee = '995000000000000000' + // this is the amount of TBTC exchanged for a DOT. + let depositValue + let signerFee before(async () => { // VendingMachine relies on linked libraries, hence we use deploySystem for consistency. @@ -95,57 +96,11 @@ contract('VendingMachine', (accounts) => { tbtcSystemStub.forceMint(accounts[4], web3.utils.toBN(deployed.TestDeposit.address)) dotId = await web3.utils.toBN(testInstance.address) - }) - - describe('#qualifyDeposit', async () => { - before(async () => { - await tbtcSystemStub.setCurrentDiff(currentDifficulty) - await testInstance.setState(utils.states.AWAITING_BTC_FUNDING_PROOF) - await testInstance.setSigningGroupPublicKey(_signerPubkeyX, _signerPubkeyY) - }) - - beforeEach(async () => { - await createSnapshot() - }) - - afterEach(async () => { - await restoreSnapshot() - }) - - it('qualifies a Deposit', async () => { - const blockNumber = await web3.eth.getBlock('latest').number - await vendingMachine.qualifyDeposit(testInstance.address, _version, _txInputVector, _txOutputVector, _txLocktime, _fundingOutputIndex, _merkleProof, _txIndexInBlock, _bitcoinHeaders) - - const UTXOInfo = await testInstance.getUTXOInfo.call() - assert.equal(UTXOInfo[0], _outValueBytes) - assert.equal(UTXOInfo[2], _expectedUTXOoutpoint) - - const signingGroupRequestedAt = await testInstance.getSigningGroupRequestedAt.call() - assert(signingGroupRequestedAt.eqn(0), 'signingGroupRequestedAt not updated') - - const fundingProofTimerStart = await testInstance.getFundingProofTimerStart.call() - assert(fundingProofTimerStart.eqn(0), 'fundingProofTimerStart not updated') - - const depositState = await testInstance.getState.call() - expect(depositState).to.eq.BN(utils.states.ACTIVE) - - const eventList = await tbtcSystemStub.getPastEvents('Funded', { fromBlock: blockNumber, toBlock: 'latest' }) - assert.equal(eventList.length, 1) - }) - - it('mints signer fee to the deposit', async () => { - const initialBalance = await tbtcToken.balanceOf(testInstance.address) - - await vendingMachine.qualifyDeposit(testInstance.address, _version, _txInputVector, _txOutputVector, _txLocktime, _fundingOutputIndex, _merkleProof, _txIndexInBlock, _bitcoinHeaders) - - const signerFee = await deployed.TestDepositUtils.signerFee() - - const finalBalance = await tbtcToken.balanceOf(testInstance.address) - const expectedBalance = new BN(initialBalance).add(new BN(signerFee)) - - expect(finalBalance).to.eq.BN(expectedBalance) - }) + const lotSize = await deployed.TBTCConstants.getLotSize() + const satoshiMultiplier = await deployed.TBTCConstants.getSatoshiMultiplier() + signerFee = await deployed.TestDepositUtils.signerFee.call() + depositValue = lotSize.mul(satoshiMultiplier) }) describe('#isQualified', async () => { @@ -181,7 +136,18 @@ contract('VendingMachine', (accounts) => { await vendingMachine.dotToTbtc(dotId) - await assertBalance.tbtc(accounts[0], depositValueLessSignerFee) + await assertBalance.tbtc(accounts[0], depositValue.sub(signerFee)) + }) + + it('mints full lot size if backing deposit has signer fee escrowed', async () => { + await tbtcToken.forceMint(testInstance.address, signerFee) + + await depositOwnerToken.forceMint(accounts[0], dotId) + await depositOwnerToken.approve(vendingMachine.address, dotId, { from: accounts[0] }) + + await vendingMachine.dotToTbtc(dotId) + + await assertBalance.tbtc(accounts[0], depositValue) }) it('fails if deposit not qualified', async () => { @@ -226,8 +192,8 @@ contract('VendingMachine', (accounts) => { it('converts TBTC to DOT', async () => { await depositOwnerToken.forceMint(vendingMachine.address, dotId) - await tbtcToken.forceMint(accounts[0], depositValueLessSignerFee) - await tbtcToken.approve(vendingMachine.address, depositValueLessSignerFee, { from: accounts[0] }) + await tbtcToken.forceMint(accounts[0], depositValue) + await tbtcToken.approve(vendingMachine.address, depositValue, { from: accounts[0] }) const fromBlock = await web3.eth.getBlockNumber() await vendingMachine.tbtcToDot(dotId) @@ -236,7 +202,7 @@ contract('VendingMachine', (accounts) => { const tbtcBurntEvent = events[0] expect(tbtcBurntEvent.returnValues.from).to.equal(accounts[0]) expect(tbtcBurntEvent.returnValues.to).to.equal(utils.address0) - expect(tbtcBurntEvent.returnValues.value).to.equal(depositValueLessSignerFee) + expect(tbtcBurntEvent.returnValues.value).to.equal(depositValue.toString()) expect( await depositOwnerToken.ownerOf(dotId) @@ -273,8 +239,8 @@ contract('VendingMachine', (accounts) => { // Deposit is locked if the Deposit Owner Token is not owned by the vending machine const depositOwner = accounts[1] await depositOwnerToken.forceMint(depositOwner, dotId) - await tbtcToken.forceMint(accounts[0], depositValueLessSignerFee) - await tbtcToken.approve(vendingMachine.address, depositValueLessSignerFee, { from: accounts[0] }) + await tbtcToken.forceMint(accounts[0], depositValue) + await tbtcToken.approve(vendingMachine.address, depositValue, { from: accounts[0] }) await expectThrow( vendingMachine.tbtcToDot(dotId), @@ -282,4 +248,54 @@ contract('VendingMachine', (accounts) => { ) }) }) + + describe('#unqualifiedDepositToTbtc', async () => { + before(async () => { + await tbtcSystemStub.setCurrentDiff(currentDifficulty) + await testInstance.setState(utils.states.AWAITING_BTC_FUNDING_PROOF) + await testInstance.setSigningGroupPublicKey(_signerPubkeyX, _signerPubkeyY) + }) + + beforeEach(async () => { + await createSnapshot() + }) + + afterEach(async () => { + await restoreSnapshot() + }) + + it('qualifies a Deposit', async () => { + await depositOwnerToken.forceMint(accounts[0], dotId) + await depositOwnerToken.approve(vendingMachine.address, dotId, { from: accounts[0] }) + const blockNumber = await web3.eth.getBlock('latest').number + + await vendingMachine.unqualifiedDepositToTbtc(testInstance.address, _version, _txInputVector, _txOutputVector, _txLocktime, _fundingOutputIndex, _merkleProof, _txIndexInBlock, _bitcoinHeaders) + + const UTXOInfo = await testInstance.getUTXOInfo.call() + assert.equal(UTXOInfo[0], _outValueBytes) + assert.equal(UTXOInfo[2], _expectedUTXOoutpoint) + + const signingGroupRequestedAt = await testInstance.getSigningGroupRequestedAt.call() + assert(signingGroupRequestedAt.eqn(0), 'signingGroupRequestedAt not updated') + + const fundingProofTimerStart = await testInstance.getFundingProofTimerStart.call() + assert(fundingProofTimerStart.eqn(0), 'fundingProofTimerStart not updated') + + const depositState = await testInstance.getState.call() + expect(depositState).to.eq.BN(utils.states.ACTIVE) + + const eventList = await tbtcSystemStub.getPastEvents('Funded', { fromBlock: blockNumber, toBlock: 'latest' }) + assert.equal(eventList.length, 1) + }) + + it('mints TBTC to the DOT owner and siger fee to Deposit', async () => { + await depositOwnerToken.forceMint(accounts[0], dotId) + await depositOwnerToken.approve(vendingMachine.address, dotId, { from: accounts[0] }) + + await vendingMachine.unqualifiedDepositToTbtc(testInstance.address, _version, _txInputVector, _txOutputVector, _txLocktime, _fundingOutputIndex, _merkleProof, _txIndexInBlock, _bitcoinHeaders) + + await assertBalance.tbtc(accounts[0], depositValue.sub(signerFee)) + await assertBalance.tbtc(testInstance.address, signerFee) + }) + }) })