From 07dc417fe93b4a218c1ea9f2ccee2ca175996dd3 Mon Sep 17 00:00:00 2001 From: liamzebedee Date: Fri, 22 Nov 2019 19:07:18 +1000 Subject: [PATCH 1/4] Some pseudocode around optionally locked deposits --- .../contracts/deposit/DepositFunding.sol | 22 +++++++++++++++++++ .../contracts/deposit/DepositRedemption.sol | 3 +++ .../contracts/deposit/DepositUtils.sol | 3 +++ 3 files changed, 28 insertions(+) diff --git a/implementation/contracts/deposit/DepositFunding.sol b/implementation/contracts/deposit/DepositFunding.sol index 51956ca93..78d399b89 100644 --- a/implementation/contracts/deposit/DepositFunding.sol +++ b/implementation/contracts/deposit/DepositFunding.sol @@ -23,6 +23,28 @@ library DepositFunding { using DepositLiquidation for DepositUtils.Deposit; using OutsourceDepositLogging for DepositUtils.Deposit; + function isLocked() returns (bool) { + return tbtcDrawn == 0; + } + + function draw(uint amt) { + require(amt == 1 tbtc, "partial draw not permitted"); + mintTbtc(msg.sender, amt); + drawn += amt; + } + + function repay(uint amt) { + require(amt == 1 tbtc, "partial repay not permitted"); + burnTbtc(msg.sender, amt); + drawn -= amt; + } + + function setLocked(bool _locked) { + require(msg.sender == depositNft.ownerOf(this), "must be beneficiary"); + require(drawn == 0, "deposit must be fully paid in tbtc"); + locked = _locked; + } + /// @notice Deletes state after funding /// @dev This is called when we go to ACTIVE or setup fails without fraud function fundingTeardown(DepositUtils.Deposit storage _d) public { diff --git a/implementation/contracts/deposit/DepositRedemption.sol b/implementation/contracts/deposit/DepositRedemption.sol index 2b29b7c00..a8115c18f 100644 --- a/implementation/contracts/deposit/DepositRedemption.sol +++ b/implementation/contracts/deposit/DepositRedemption.sol @@ -71,6 +71,9 @@ library DepositRedemption { bytes8 _outputValueBytes, bytes20 _requesterPKH ) public { + if(_d.isLocked()) { + require(msg.sender == _d.beneficiary()); + } require(_d.inRedeemableState(), "Redemption only available from Active or Courtesy state"); require(_requesterPKH != bytes20(0), "cannot send value to zero pkh"); diff --git a/implementation/contracts/deposit/DepositUtils.sol b/implementation/contracts/deposit/DepositUtils.sol index 4d082362d..9c6255b74 100644 --- a/implementation/contracts/deposit/DepositUtils.sol +++ b/implementation/contracts/deposit/DepositUtils.sol @@ -56,6 +56,9 @@ library DepositUtils { /// @dev Holds a timestamp from the moment when the transaction digest /// was approved for signing mapping (bytes32 => uint256) approvedDigests; + + // Stateful locking mechanism + uint drawn; // amount of tbtc drawn from deposit } /// @notice Gets the current block difficulty From 57f9afce0c42c0f43fdd91694f1f1cc65625e124 Mon Sep 17 00:00:00 2001 From: liamzebedee Date: Fri, 22 Nov 2019 19:16:10 +1000 Subject: [PATCH 2/4] Add isQualified pseudocode to VendingMachine --- .../contracts/system/VendingMachine.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 implementation/contracts/system/VendingMachine.sol diff --git a/implementation/contracts/system/VendingMachine.sol b/implementation/contracts/system/VendingMachine.sol new file mode 100644 index 000000000..36068fde1 --- /dev/null +++ b/implementation/contracts/system/VendingMachine.sol @@ -0,0 +1,19 @@ +contract VendingMachine { + function isQualified(deposit, proof) { + // check deposit qualified + totalValue = 0 + unqualifiedDeposits = [...] + for deposit in unqualifiedDeposits: + totalValue += deposit.lotSize + + blockReward = 12.5 + minBlocksX = totalValue / blockReward + + proof = verifyProof(proof) + + // qualifier = 6 + n + // where 6 is the minimum number of confs to mint tbtc and + // n is the security margin that fluctuates according to opened deposit volume + require(proof.blocks >= Math.min(6, minBlocksX)); + } +} \ No newline at end of file From c6c14b39f5cf517f891ba7d3dd79a7f391fd178d Mon Sep 17 00:00:00 2001 From: liamzebedee Date: Fri, 22 Nov 2019 19:22:40 +1000 Subject: [PATCH 3/4] Fix dirty Sublime Text tabs+spaces orgy --- .../contracts/system/VendingMachine.sol | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/implementation/contracts/system/VendingMachine.sol b/implementation/contracts/system/VendingMachine.sol index 36068fde1..7ba56dc3c 100644 --- a/implementation/contracts/system/VendingMachine.sol +++ b/implementation/contracts/system/VendingMachine.sol @@ -1,19 +1,19 @@ contract VendingMachine { - function isQualified(deposit, proof) { - // check deposit qualified + function isQualified(deposit, proof) { + // check deposit qualified totalValue = 0 - unqualifiedDeposits = [...] - for deposit in unqualifiedDeposits: - totalValue += deposit.lotSize + unqualifiedDeposits = [...] + for deposit in unqualifiedDeposits: + totalValue += deposit.lotSize blockReward = 12.5 minBlocksX = totalValue / blockReward proof = verifyProof(proof) - - // qualifier = 6 + n - // where 6 is the minimum number of confs to mint tbtc and - // n is the security margin that fluctuates according to opened deposit volume + + // qualifier = 6 + n + // where 6 is the minimum number of confs to mint tbtc and + // n is the security margin that fluctuates according to opened deposit volume require(proof.blocks >= Math.min(6, minBlocksX)); - } + } } \ No newline at end of file From 6a3fc7b7b3a52e983d5fd3140ddba3c1a1c04340 Mon Sep 17 00:00:00 2001 From: liamzebedee Date: Mon, 25 Nov 2019 16:34:03 +1000 Subject: [PATCH 4/4] Pass 2 at vending machine design. - remove the lock and drawing functionality - refer to the deposit owner token (DOT), as the token with exclusive redemption rights - first pass at a clean API for the vending machine --- .../contracts/deposit/DepositFunding.sol | 22 ---- .../contracts/system/VendingMachine.sol | 124 ++++++++++++++++-- 2 files changed, 110 insertions(+), 36 deletions(-) diff --git a/implementation/contracts/deposit/DepositFunding.sol b/implementation/contracts/deposit/DepositFunding.sol index 78d399b89..51956ca93 100644 --- a/implementation/contracts/deposit/DepositFunding.sol +++ b/implementation/contracts/deposit/DepositFunding.sol @@ -23,28 +23,6 @@ library DepositFunding { using DepositLiquidation for DepositUtils.Deposit; using OutsourceDepositLogging for DepositUtils.Deposit; - function isLocked() returns (bool) { - return tbtcDrawn == 0; - } - - function draw(uint amt) { - require(amt == 1 tbtc, "partial draw not permitted"); - mintTbtc(msg.sender, amt); - drawn += amt; - } - - function repay(uint amt) { - require(amt == 1 tbtc, "partial repay not permitted"); - burnTbtc(msg.sender, amt); - drawn -= amt; - } - - function setLocked(bool _locked) { - require(msg.sender == depositNft.ownerOf(this), "must be beneficiary"); - require(drawn == 0, "deposit must be fully paid in tbtc"); - locked = _locked; - } - /// @notice Deletes state after funding /// @dev This is called when we go to ACTIVE or setup fails without fraud function fundingTeardown(DepositUtils.Deposit storage _d) public { diff --git a/implementation/contracts/system/VendingMachine.sol b/implementation/contracts/system/VendingMachine.sol index 7ba56dc3c..051c4b518 100644 --- a/implementation/contracts/system/VendingMachine.sol +++ b/implementation/contracts/system/VendingMachine.sol @@ -1,19 +1,115 @@ +import "./DepositOwnerToken.sol"; + contract VendingMachine { - function isQualified(deposit, proof) { - // check deposit qualified - totalValue = 0 - unqualifiedDeposits = [...] - for deposit in unqualifiedDeposits: - totalValue += deposit.lotSize - - blockReward = 12.5 - minBlocksX = totalValue / blockReward - - proof = verifyProof(proof) - + // The volume of TBTC yet to pass the qualifier. + uint public unqualifiedVolume = 0; + + // Mapping of deposit to qualification status. + mapping(uint256 => bool) qualified; + + // Constants + uint256 public constant BLOCK_REWARD = 125 * 10^8; // satoshis, TODO var is constant for sake of example. + + modifier onlyDepositOwner(uint256 _depositOwnerTokenId) { + require(DepositOwnerToken.ownerOf(_depositOwnerTokenId) == msg.sender); + } + + // After 1 conf, the DOT token can be dispensed + function dispenseDot( + uint256 _depositId, + bytes memory _bitcoinHeaders + ) public { + require(deposit.inFundingState(), "deposit in wrong state"); + + // Verify minimum 1 confirmation + uint minimumDifficultyFactor = TBTCConstants.getTxProofDifficultyFactor(); + uint _observedDiff = evaluateProofDifficulty(_d, _bitcoinHeaders); + require( + _observedDiff >= _reqDiff.mul(minimumDifficultyFactor), + "Insufficient accumulated difficulty in header chain" + ); + + // Mint the DOT + DepositOwnerToken.mint(msg.sender, _depositId); + unqualifiedVolume += deposit.lotSize(); + + // Set deposit, active? + deposit.setActive(); + } + + /** + * There are two flows here: + * 1) qualify, mint tbtc, destroy dot + * 2) qualify, keep dot + */ + function qualifyDot( + uint256 _depositOwnerTokenId, + bytes memory _bitcoinHeaders, + bool mintTbtc + ) public onlyDepositOwner(_depositOwnerTokenId) { + require(!qualified[_depositOwnerTokenId], "already qualified"); + + // Check qualification + Deposit deposit = DepositOwnerToken.getDeposit(_depositOwnerTokenId); + require(isQualified(deposit, _bitcoinHeaders), "deposit doesn't qualify for minting"); + + qualified[_depositOwnerTokenId] = true; + unqualifiedVolume -= deposit.lotSize(); + + if(mintTbtc) { + // Relinquish DOT + dotToTbtc(_depositOwnerTokenId); + } else { + // Keep DOT + } + } + + function redeemTbtc(uint256 _depositId) public { + tbtcToDot(); + deposit.redeem(); + } + + + // Repay TBTC drawn and receive DOT token + function tbtcToDot( + uint256 _depositId + ) public { + require(DepositOwnerToken.ownerOf(_depositOwnerTokenId) == address(this), "DOT not available for vending"); + + require(tbtc.burn(msg.sender, 1 tbtc)); + DepositOwnerToken.transferFrom(address(this), msg.sender, _depositId); + } + + // Relinquish DOT token and receive TBTC + function dotToTbtc( + uint256 _depositOwnerTokenId, + bytes memory _bitcoinHeaders + ) internal onlyDepositOwner(_depositOwnerTokenId) { + require(qualified[_depositOwnerTokenId], "dot not qualified"); + + Deposit deposit = DepositOwnerToken.getDeposit(_depositOwnerTokenId); + + require(DepositOwnerToken.transferFrom(msg.sender, _depositOwnerTokenId, this), "no permission to claim"); + mintTbtc(msg.sender, deposit.lotSize()); + mintBeneficiaryToken(msg.sender, deposit); + } + + // Returns whether a DOT is qualified to be minted into TBTC + function isQualified(Deposit storage _d, bytes memory _bitcoinHeaders) public view { + uint minimumDifficultyFactor = TBTCConstants.getTxProofDifficultyFactor(); + // The stopgate is an additional security margin, which limits the volume of TBTC minted per-block, + // as to disincentivise cross-chain miner arbitrage. + uint stopgateFactor = unqualifiedVolume / BLOCK_REWARD; + + // TODO modify evaluateProofDifficulty + uint _observedDiff = evaluateProofDifficulty(_d, _bitcoinHeaders); + // qualifier = 6 + n // where 6 is the minimum number of confs to mint tbtc and - // n is the security margin that fluctuates according to opened deposit volume - require(proof.blocks >= Math.min(6, minBlocksX)); + // n is the security margin (stopgate) that fluctuates according to opened deposit volume + require( + _observedDiff >= _reqDiff.mul(minimumDifficultyFactor + stopgateFactor), + "Insufficient accumulated difficulty in header chain" + ); } } \ No newline at end of file