-
Notifications
You must be signed in to change notification settings - Fork 42
Remove TBTC minting from Vending Machine Deposit qualification #377
Conversation
We don't need ACL on DepositFunding after removing minting from proof function.
qualifyDeposit -> qualifyDepositTbtcWrapper Function is now a unqualified deposit -> TBTC wrapper dotToTbtc now mints full lot size tbtcToDot now burns full lot size Signer fees are provided during redemption.
qualifyDepositTbtcWrapper depends on dotToTbtc. Test dotToTbtc first
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
function qualifyDeposit( | ||
/// @notice Qualifies a deposit and mints TBTC. | ||
/// @dev User must allow VendingManchine to transfer DOT | ||
function qualifyDepositTbtcWrapper( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm - if we've removed both the qualification check and TBTC minting from this function, wouldn't it be much simpler to just call the vending machine from Deposit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't it be much simpler to just call the vending machine from Deposit?
I don't see how this would be simpler. We would still need a call to both contracts.
Since this wrapper uses a vending machine method to mint TBTC I think it belong in Vending Machine just fine.
I can see the argument for adding the function in Deposit though. It would also be marginally cheeper since large params aren't passed around.
@Shadowfiend any thoughts here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this would be simpler. We would still need a call to both contracts.
I can see the argument for adding the function in Deposit though.
It depends, I'm still on the fence - where is best to validate qualification? The vending machine right now fulfills a very simple purpose - swapping between fungible (TBTC) and non-fungible (DOT) forms of tBTC. We could add some additional wrapper methods, but we're already validating qualification in the deposit now as of #377.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like giving the vending machine a unqualifiedDotToTbtc
(my name for this function). I could likewise see it on the deposit, but from a caller's perspective it preserves the clear “only calls to the vending machine will mint TBTC” relationship, which I think is a strong relationship to keep clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To which end, I think the name of this function isn't particularly reflective of what it's doing :) I like unqualifiedDotToTbtc
for its symmetry with dotToTbtc
, or unqualifiedDepositToTbtc
if we want to operate on the address instead of the DOT, but am open to other ideas.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm terrible, sorry, one more note---I do think this function and any other wrapper functions should be grouped together at the end of the contract, unless that has unexpected consequences. We should also explicitly mark them as convenience functions/wrappers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok so to recap:
qualifyDepositTbtcWrapper
->unqualifiedDepositToTbtc
👍 👍- keep the wrapper in Vending machine for context clarity
- move & group related wrappers to end & label. (won't have unexpected issues here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the name, unqualifiedDepositToTbtc
, for now. My only gripe with it, is that qualification is only defined for funding now. We still need to be qualifying the redemption process - when we eventually approach this, what would be a name that makes sense? qualifiedDepositToBtc
, redeemQualifiedDeposit
? 🤔
Conceptually, I'm not sure it's the best model to think of the deposit as qualified, since we qualify it in two contexts: once during funding, and once during redemption. Qualification is something we do to ensure the integrity of the cross-chain transfers from miner attacks / reorgs, which includes TBTC -> BTC. It's the cross-chain proof which is being qualified, in funding and in redemption, rather the deposit itself.
These points are non-blocking to merging this, I just think it's easier to bring up in this context. 👍 Qualification might seem more like an aspect of the funding/redemption proofs, rather than the deposit itself. With this in mind, I had a go during the first review cycle of modelling this intuition into code (#378).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to point out the difference between a qualified deposit and qualifying a call.
A change of terminology might help avoid some confusion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redemption already requires proof, that's already captured both in the spec, the state machine diagram, and the code we have written. Managing dynamic proof thresholds isn't currently on the table for v1 for redemption or deposit funding.
For our purposes, I think we should consider a deposit qualified when the designated minimum proof has been provided of funding. Currently that minimum is 6 blocks of work. In the future, that minimum could be dynamic or not. I don't think we'll refer to a similar such process in redemption as “qualification”---let's instead consider qualification a property of deposits that refers to how confident we are that their backing funds are available. In that sense, redemption proof should probably flip a deposit back to unqualified, as a redeemed deposit has no confidence that its backing funds are available (since they aren't 😁).
With all that said, right now we're referring to this state as “active”. Open question for me if we want to stick to that term, frankly. I'm down either way, but “qualified” kind of feels more specific. Let's not worry about that for this PR, either way.
// 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. | ||
const depositValue = '1000000000000000000' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: is this something we could read from the contract? This is the equivalent of the lot size, yes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we should be able to.
|
||
await vendingMachine.qualifyDepositTbtcWrapper(testInstance.address, _version, _txInputVector, _txOutputVector, _txLocktime, _fundingOutputIndex, _merkleProof, _txIndexInBlock, _bitcoinHeaders) | ||
|
||
await assertBalance.tbtc(accounts[0], depositValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not blocking: let's remember we need to update this once we're escrowing the signing fees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And… Probably we should have a test once we're doing that to assert that we've escrowed said signing fees, as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not blocking: let's remember we need to update this once we're escrowing the signing fees.
This was only updated because of the requirement lift on fee minting during qualification. PR diff should show signer fees subtracted. Might need to scrap this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deposit shouldn't be minting any fees during qualification; the vending machine should be escrowing part of the minted TBTC for fees, assigned to the deposit for later distribution, during the regular DOT-to-TBTC flow.
That is, DOT-to-TBTC should be:
DOT -> Vending Machine
Vending Machine -> <lot size> - <signing fees> TBTC -> previous DOT owner
Vending Machine -> <signing fees> TBTC -> deposit fee escrow
This is the only case in which we should be escrowing right now.
In text:
Proving a deposit's funding is a completely separate process from vending TBTC. The vending machine has a convenience function whose sole purpose is to both pass a proof on to the deposit and vend TBTC. Vending TBTC mints <lot size>
TBTC for the deposit and sets aside <signing fee>
to the deposit for escrow, sending the remainder of the TBTC to the caller who sent the DOT in to the vending machine.
Left a couple of notes/questions. |
building on flowdock thread: If we need to mint TBTC again, minter authority must be considered.
|
@NicholasDotSol I seem to remember we used |
burn (lotsize - signer fee) on tbtcToDot
qualifyDepositTbtcWrapper -> unqualifiedDepositToTbtc get DepositValue dynamically add check for singer fee minting
Ready on this @liamzebedee |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One blocker here.
require(tbtcToken.balanceOf(msg.sender) >= getDepositValueLessSignerFee(), "Not enough TBTC for DOT exchange"); | ||
tbtcToken.burnFrom(msg.sender, getDepositValueLessSignerFee()); | ||
uint256 depositValueLessSignerFee = getDepositValueLessSignerFee(); | ||
require(tbtcToken.balanceOf(msg.sender) >= depositValueLessSignerFee, "Not enough TBTC for DOT exchange"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. We need to burn the lot size in TBTC, not lot size minus signer fee. That's how we maintain the peg---mint 1 TBTC for 1 DOT, burn 1 TBTC for 1 DOT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm. So we're not withholding the singerFee from the minted TBTC during dotToTbtc
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whether we are or not, one DOT costs 1 TBTC, or we risk breaking the peg. Remember the peg guarantees at least 1 BTC held for each TBTC. If we ever return a DOT without burning 1 TBTC, we've potentially got a problem. The escrow changes whether the minter gets the full TBTC, but it does not change that the DOT is only worth 1 TBTC---only how that 1 TBTC is split.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahhh. Missed this due to a bad assumption that everything equalizes during redemption / (and equalized due to burn during liquidation).
I think we need to change some vending machine design, or add a conditional. the issue i'm seeing is the following:
Whether we are or not, one DOT costs 1 TBTC
as a user I would expect to be able to call dotToTbtc <> tbtcToDot as many times as i want without losing a chunk of my TBTC every time.
if signer fee is held in dotToTbtc every time, it becomes a very expensive function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let’s punt signer fee to later once we’ve figured out the whole mess, but short version: fee should never be escrowed more than once. The burn here won’t change no matter what we do with signer fees: it will always equal the lot size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or add a conditional
i.e: dotToTbtc
only escrows signer fee if backing deposit has not received a signer fee.
Only first time swaps will escrow fee. I think it's worth the headache, especially since
distributeSignerFee
only approves signerFee
. So any extra TBTC is effectively locked forever with this design.
as an implementation note: for security reasons check would have to be _tbtc.balanceOf(backing deposit) > SignerFee
, not a zero balance check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, if we stick with escrowing, it should work exactly as you describe.
// TODO temporary helper function | ||
/// @notice Gets the Deposit lot size | ||
/// @return amount in TBTC | ||
function getDepositValueLessSignerFee() internal returns (uint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason we wouldn't just… Do this inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, think this looks cleaner though. Expecting to use one of these functions in redemption wrappers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm… Whether or not we escrow signing fees, we're unlikely to be needing the lot size minus signer fee except at mint-time. Everything else is done in terms of lot size. But we can wait and see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're unlikely to be needing the lot size minus signer fee except at mint-time
agree
But we can wait and see.
👍
also only escrow signer fee if not already escrowed
Update tbtcToDot burn value requirement Add test for escrow mint condition on dotToTbtc
// 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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still feels wrong---in particular, that we're potentially at nonzero escrow but are minting the full signer fee to the deposit, putting us at > signer fee escrow. I can't reason my way all the way through it right now, but I want us to work through it a little.
For now, let's roll with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't find any issues with it.
Escrow gets distributed as a final step before contract halt. Any TBTC more than signerFee left in the Deposit is just locked, and this will be user error (like sending to address(0)). There is no logical reason for more TBTC to be in the deposit. If someone wants to do that for some reason there's no sence in preventing them, they're not gaining anything so shrug.
Will take a deeper look at the state machine to make sure there are no corner cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okayy, let's do it.
Dismissing this as it seems like we've talked through any issues that were mentioned.
@@ -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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NicholasDotSol can you fix up getDepositValue
in #385?
Removing the signerFee minting from Vending also removes VendingMachine call restriction on qualifyDeposit. Instead the qualification can be done directly via DepositFunding #provideBTCFundingProof. To compensate Signer fees should be awarded on redemption.
qualifyDeposit
renamed toqualifyDepositTbtcWrapper
and acts as a wrapper to qualify a deposit and mint TBTC from DOT in one call.ref: #376