From 253ac9cd19365ec327890c5af400adb9bca1909b Mon Sep 17 00:00:00 2001 From: Nicholas Evans Date: Thu, 21 Nov 2019 08:17:05 -0600 Subject: [PATCH 1/3] Early Vending machine sketch Pseudocode, regualr code and comments used to outline behaviour of an early vending machine --- .../contracts/deposit/DepositFunding.sol | 4 +- .../contracts/deposit/DepositUtils.sol | 2 +- implementation/contracts/system/Vending.sol | 94 +++++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 implementation/contracts/system/Vending.sol diff --git a/implementation/contracts/deposit/DepositFunding.sol b/implementation/contracts/deposit/DepositFunding.sol index 51956ca93..99f68ca17 100644 --- a/implementation/contracts/deposit/DepositFunding.sol +++ b/implementation/contracts/deposit/DepositFunding.sol @@ -337,9 +337,7 @@ library DepositFunding { _d.setActive(); _d.logFunded(); - returnFunderBond(_d); - - mintTBTC(_d); + // mint Dposit Owner NFT return true; } diff --git a/implementation/contracts/deposit/DepositUtils.sol b/implementation/contracts/deposit/DepositUtils.sol index 4d082362d..f4e373eda 100644 --- a/implementation/contracts/deposit/DepositUtils.sol +++ b/implementation/contracts/deposit/DepositUtils.sol @@ -98,7 +98,7 @@ library DepositUtils { require(_observedDiff != ValidateSPV.getErrInvalidChain(), "Invalid headers chain"); require(_observedDiff != ValidateSPV.getErrLowWork(), "Insufficient work in a header"); - /* TODO: make this better than 6 */ + /* TODO: Redo fir single proof */ require( _observedDiff >= _reqDiff.mul(TBTCConstants.getTxProofDifficultyFactor()), "Insufficient accumulated difficulty in header chain" diff --git a/implementation/contracts/system/Vending.sol b/implementation/contracts/system/Vending.sol new file mode 100644 index 000000000..f97793aa6 --- /dev/null +++ b/implementation/contracts/system/Vending.sol @@ -0,0 +1,94 @@ +pragma solidity ^0.5.10; + +import Deposit, TBTCToken, DepositOwnerNFT, DepositBEenficiaryNFT; + +contract Vending { + + // Check if a Deposit Owner Token (DOT) is qualified + function isQualified(tokenId) public view returns (bool); + + // Qualify a DOT + function qualifyDepositOwnerToken(tokenID, proofRequirements) public view returns (bool){ + require(tokenId has not been redeemed); + require(within timeout) // expand on timeout in this context + //proof checks out + //proofRequirements <> getQualificationRequirements() + } + + // get qualification requirements, currently just number of + // confirmations required. This is 6 + X. X depends on volume + function getQualificationRequirements() public view returns (uint256){ + // return current number of blocks needed for qualification + } + + // Use a DOT to obtain TBTC (ERC20) + // If locked bool == true, maintain exclusive redemption rights, + // keep control of DOT and receive TBTC. The DOT is marked as redeemed. Ownership of the DOT + // now represents the right to redeem the specific custodied UTXO. + // If locked bool == false, Forfeit exclusive redemption rights, Swap DOT + // for Deposit Beneficiary NFT, and receive TBTC. + // DOTs controlled by Vending machine do not need to be marked as redeemed due to + // the TBTC burning requirement enforced by getDepositOwnerNFT() + // this function can be separated into getTBTCLocked() and getTBTCUnlocked() + function GetTBTC(bool locked, uint256 tokenID, proofRequirements ) public { + require(isQualified(tokenId, proofRequirements)) + require(within timeout) // expand on timeout in this context + if(!locked){ + // swap Deposit Owner NFT for TBTC + // supply Deposit beneficiary NFT + // return + } + // supply TBTC, but no deposit beneficiary NFT + } + + // pay required TBTC to retrieve given DOT (in unredeemed state) + function getDepositOwnerNFT(uint256 tokenid) public returns (address) { + require(sended approved sufficient TBTC to be burned); + if(tokenId == 0){ + // decide which NFT to return. oldest/random + // some market potential here? (note async here will break redemption wrapper) + // Market can be external system impacting queue order of UTXO redemption. + // could offer an alternative to signer self redemption by paying for redemption priority. + // Paying for DOT queue priority would have to somehow lock the DOT to prevent getDepositOwnerNFT() + // on the specific DOT (since Ownership of the DOT does not guarantee imminent redemption) + // only in redemption Wrapper: getDepositOwnerNFT(RedemptionQueue.Next()) -- not async, cool. + } + // check vending machine for given NFT, revert if it doesn't exist. + // transfer Deposit owner NFT + // return tokenID + } + + // pay required TBTC to retrieve non-specific DOT (in unredeemed state) + function getDepositOwnerNFT() public returns (address) { + getDepositOwnerNFT(0); + } + + // redeem a Deposit with TBTC, specific DOT preference. + function redemptionWrapper(uint256 _TokenID) public { + uint256 TokenId; + if(ownerOf(_TokenId) == msg.sender){ + tokenId = _TokenID; + } + else{ + TokenId = getDepositOwnerNFT(_TokenID); + } + redeemDepositOwnerToken(TokenId) + } + + // redeem a Deposit using TBTC. No Specific DOT preference + function redemptionWrapper() public{ + uint256 TokenId = getDepositOwnerNFT(0); + redeemDepositOwnerToken(TokenId) + } + + // redeem a Dposit using an unredeemed DOT. not TBTC requirement + function redeemDepositOwnerToken(uint256 TokenID) public { + require(msg.sender == tokenId owner); + require(TokenID is valid and unredeemed) + Deposit(address(tokenId)).requestRedemption() //temp + // msg.sender issues here? + // will need an approval chain for TBTC burn approval requirements + // might need restructure on Deposit to send + // beneficiary reward to correct msg.sender instead of address(this) pass var? + } +} \ No newline at end of file From 32cbe79d588e279ea6a5907df7d7a04eabc3846a Mon Sep 17 00:00:00 2001 From: Nicholas Evans Date: Thu, 21 Nov 2019 08:25:10 -0600 Subject: [PATCH 2/3] fix typo --- implementation/contracts/deposit/DepositUtils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/contracts/deposit/DepositUtils.sol b/implementation/contracts/deposit/DepositUtils.sol index f4e373eda..cf4060acf 100644 --- a/implementation/contracts/deposit/DepositUtils.sol +++ b/implementation/contracts/deposit/DepositUtils.sol @@ -98,7 +98,7 @@ library DepositUtils { require(_observedDiff != ValidateSPV.getErrInvalidChain(), "Invalid headers chain"); require(_observedDiff != ValidateSPV.getErrLowWork(), "Insufficient work in a header"); - /* TODO: Redo fir single proof */ + /* TODO: Redo for single proof */ require( _observedDiff >= _reqDiff.mul(TBTCConstants.getTxProofDifficultyFactor()), "Insufficient accumulated difficulty in header chain" From 798f1040e05ddd87c95ff272785eee35a65a426a Mon Sep 17 00:00:00 2001 From: Nicholas Evans Date: Fri, 22 Nov 2019 17:08:20 -0600 Subject: [PATCH 3/3] Add Sample test file for Vending machine --- implementation/contracts/system/Vending.sol | 5 +- implementation/test/VendingTest.js | 161 ++++++++++++++++++++ 2 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 implementation/test/VendingTest.js diff --git a/implementation/contracts/system/Vending.sol b/implementation/contracts/system/Vending.sol index f97793aa6..38f0d2715 100644 --- a/implementation/contracts/system/Vending.sol +++ b/implementation/contracts/system/Vending.sol @@ -30,9 +30,8 @@ contract Vending { // DOTs controlled by Vending machine do not need to be marked as redeemed due to // the TBTC burning requirement enforced by getDepositOwnerNFT() // this function can be separated into getTBTCLocked() and getTBTCUnlocked() - function GetTBTC(bool locked, uint256 tokenID, proofRequirements ) public { - require(isQualified(tokenId, proofRequirements)) - require(within timeout) // expand on timeout in this context + function GetTBTC(bool locked, uint256 tokenID ) public { + require(isQualified(tokenId)) if(!locked){ // swap Deposit Owner NFT for TBTC // supply Deposit beneficiary NFT diff --git a/implementation/test/VendingTest.js b/implementation/test/VendingTest.js new file mode 100644 index 000000000..a676d11f5 --- /dev/null +++ b/implementation/test/VendingTest.js @@ -0,0 +1,161 @@ +contract('Vending', (accounts) => { + + // check if a deposit is qualified. Will be a + describe('isQualified', async () => { + it('Returns true for qualified Token', async () => { + const TokenId = 1 + // mint a new token with given ID to accounts[1] + DepositOwnerTokenStub.new(TokenId, accounts[1]) + DepositOwnerTokenStub(tokenId).setQualified(true) + const qualified = Vending.isQualified(tokenId) + assert.equal(qualified, true) + }) + + it('Returns false for unqualified Token', async () => { + const TokenId = 1 + // mint a new token with given ID to accounts[1] + DepositOwnerTokenStub.new(TokenId, accounts[1]) + const qualified = Vending.isQualified(tokenId) + assert.equal(qualified, false) + }) + + it('Reverts wtih bad token', async () => { + const TokenId = 2 + await expectThrow( + Vending.isQualified(TokenId) + ) + }) + }) + + describe('qualifyDepositOwnerToken', async () => { + it('Qualifies a Token to be used by the vending machine', async () => { + const TokenId = 1 + + // required proofs for qualifying token. This will not be just one variable + // and should include merkle proof, block headers etc + const proofRequirements = '0x00' + + DepositOwnerTokenStub.new(TokenId, accounts[1]) + Vending.qualifyDepositOwnerToken(TokenId, proofRequirements) + + const check = DepositOwnerTokenStub.isQualified(TokenId) + assert.equal(check, true) + }) + + + it('Fails to qualify a token with bad proof', async () => { + const TokenId = 1 + + // required proofs for qualifying token. This will not be just one variable + // and should include merkle proof, block headers etc + const proofRequirements = '0xBAD' + + DepositOwnerTokenStub.new(TokenId, accounts[1]) + await expectThrow( + Vending.qualifyDepositOwnerToken(TokenId, proofRequirements) + ) + }) + }) + + describe('getQualificationRequirements', async () => { + it('Returns qualification requirements for a given Deposit Owner Token', async () => { + + // artificially set vending machine volume. This assumes a vending stub + Vending.setVolume(X) // + + const actualRequirements = Vending.getQualificationRequirements() + + const expectedRequirements = //Calculate expected requirements here + + assert.equal(expectedRequirements, actualRequirements) + }) + }) + + describe('GetTBTC unlocked', async () => { + it('correctly grants TBTC and handles NFT tokens', async () => { + + const TokenId = 1 + DepositOwnerTokenStub.new(TokenId, accounts[0]) + DepositOwnerTokenStub(tokenId).setQualified(false) + + // -- CHECK PRIOR BALANCES + + Vending.getTBTC(false, TokenId) + + // - CHECK BALANCES AGAIN + + assert(TBTC balance increased correctly) + assert(DOT is now owned by vending machine) + assert(accounts[0] no ownes correct Deposit Beneficiary Token) + assert(DOT is still unredeemed) + + }) + }) + describe('GetTBTC locked', async () => { + it('correctly grants TBTC and handles NFT tokens', async () => { + + const TokenId = 1 + DepositOwnerTokenStub.new(TokenId, accounts[0]) + DepositOwnerTokenStub(tokenId).setQualified(true) + + // -- CHECK PRIOR BALANCES + + Vending.getTBTC(false, TokenId) + + // - CHECK BALANCES AGAIN + + assert(TBTC balance increased correctly) + assert(DOT is now owned by accounts[0]) + assert(Deposit Beneficiary Token balance is unchanged) + assert(DOT is redeemed) + + }) + }) + describe('getDepositOwnerNFT', async () => { + it('correctly grants Deposit Owner Token', async () => { + + const TokenId = 1 + DepositOwnerTokenStub.new(TokenId, accounts[0]) + DepositOwnerTokenStub.transferTo(TokenId, Vending.address) + + TBTCToken.transferTo(accounts[0], requiredTBTC) + TBTCToken.approve(Vending, requiredTBTC) + DepositOwnerTokenStub.approve(Vending) + + Vending.getDepositOwnerNFT(1) + + assert(TBTC was correctly burned) + assert(accounts[0] now owns Deposit OWner Token with ID 1) + assert(Deposit Owner Token 1 is unredeemed) + + }) + it('Fails to grants Deposit Owner Token with insufficient funds', async () => { + + const TokenId = 1 + DepositOwnerTokenStub.new(TokenId, Vending.address) + await expectThrow( + Vending.getDepositOwnerNFT(1) + ) + }) + }) + + describe('redemptionWrapper', async () => { + it('correctly initiates Deposit redemption', async () => { + + const TokenId = 1 + DepositOwnerTokenStub.new(TokenId, accounts[0]) + DepositOwnerTokenStub.transferTo(TokenId, Vending.address) + + TBTCToken.transferTo(accounts[0], requiredTBTC) + TBTCToken.approve(Vending, requiredTBTC) + DepositOwnerTokenStub.approve(Vending) + + Vending.redemptionWrapper(1) + + assert(TBTC was correctly burned) + assert(DepositOwnerToken is burned) + assert(Deposit in redemption state) + + }) + }) +})