diff --git a/solidity/.nvmrc b/solidity/.nvmrc new file mode 100644 index 000000000..a77793ecc --- /dev/null +++ b/solidity/.nvmrc @@ -0,0 +1 @@ +lts/hydrogen diff --git a/solidity/contracts/integrator/AbstractTBTCDepositor.sol b/solidity/contracts/integrator/AbstractTBTCDepositor.sol index 845582070..6f32b7130 100644 --- a/solidity/contracts/integrator/AbstractTBTCDepositor.sol +++ b/solidity/contracts/integrator/AbstractTBTCDepositor.sol @@ -64,7 +64,11 @@ import "./ITBTCVault.sol"; /// } /// /// function finalizeProcess(uint256 depositKey) external { -/// (uint256 tbtcAmount, bytes32 extraData) = _finalizeDeposit(depositKey); +/// ( +/// uint256 initialDepositAmount, +/// uint256 tbtcAmount, +/// bytes32 extraData +/// ) = _finalizeDeposit(depositKey); /// /// // Do something with the minted TBTC using context /// // embedded in the extraData. @@ -166,7 +170,10 @@ abstract contract AbstractTBTCDepositor { /// @notice Finalizes a deposit by calculating the amount of TBTC minted /// for the deposit /// @param depositKey Deposit key identifying the deposit. - /// @return tbtcAmount Approximate amount of TBTC minted for the deposit. + /// @return initialDepositAmount Amount of funding transaction deposit. In + /// TBTC token decimals precision. + /// @return tbtcAmount Approximate amount of TBTC minted for the deposit. In + /// TBTC token decimals precision. /// @return extraData 32-byte deposit extra data. /// @dev Requirements: /// - The deposit must be initialized but not finalized @@ -179,7 +186,11 @@ abstract contract AbstractTBTCDepositor { // slither-disable-next-line dead-code function _finalizeDeposit(uint256 depositKey) internal - returns (uint256 tbtcAmount, bytes32 extraData) + returns ( + uint256 initialDepositAmount, + uint256 tbtcAmount, + bytes32 extraData + ) { require(pendingDeposits[depositKey], "Deposit not initialized"); @@ -203,6 +214,8 @@ abstract contract AbstractTBTCDepositor { // slither-disable-next-line reentrancy-no-eth delete pendingDeposits[depositKey]; + initialDepositAmount = deposit.amount * SATOSHI_MULTIPLIER; + tbtcAmount = _calculateTbtcAmount(deposit.amount, deposit.treasuryFee); // slither-disable-next-line reentrancy-events diff --git a/solidity/contracts/test/TestTBTCDepositor.sol b/solidity/contracts/test/TestTBTCDepositor.sol index 60b58a569..fd1cd78d9 100644 --- a/solidity/contracts/test/TestTBTCDepositor.sol +++ b/solidity/contracts/test/TestTBTCDepositor.sol @@ -11,7 +11,11 @@ import "../integrator/ITBTCVault.sol"; contract TestTBTCDepositor is AbstractTBTCDepositor { event InitializeDepositReturned(uint256 depositKey); - event FinalizeDepositReturned(uint256 tbtcAmount, bytes32 extraData); + event FinalizeDepositReturned( + uint256 initialDepositAmount, + uint256 tbtcAmount, + bytes32 extraData + ); function initialize(address _bridge, address _tbtcVault) external { __AbstractTBTCDepositor_initialize(_bridge, _tbtcVault); @@ -27,8 +31,16 @@ contract TestTBTCDepositor is AbstractTBTCDepositor { } function finalizeDepositPublic(uint256 depositKey) external { - (uint256 tbtcAmount, bytes32 extraData) = _finalizeDeposit(depositKey); - emit FinalizeDepositReturned(tbtcAmount, extraData); + ( + uint256 initialDepositAmount, + uint256 tbtcAmount, + bytes32 extraData + ) = _finalizeDeposit(depositKey); + emit FinalizeDepositReturned( + initialDepositAmount, + tbtcAmount, + extraData + ); } function calculateTbtcAmountPublic( @@ -44,7 +56,8 @@ contract MockBridge is IBridge { mapping(uint256 => IBridgeTypes.DepositRequest) internal _deposits; - uint64 internal _depositTxMaxFee = 1 * 1e7; // 0.1 BTC + uint64 internal _depositTreasuryFeeDivisor = 50; // 1/50 == 100 bps == 2% == 0.02 + uint64 internal _depositTxMaxFee = 1000; // 1000 satoshi = 0.00001 BTC event DepositRevealed(uint256 depositKey); @@ -73,14 +86,22 @@ contract MockBridge is IBridge { "Deposit already revealed" ); + bytes memory fundingOutput = fundingTx + .outputVector + .extractOutputAtIndex(reveal.fundingOutputIndex); + + uint64 fundingOutputAmount = fundingOutput.extractValue(); + IBridgeTypes.DepositRequest memory request; request.depositor = msg.sender; - request.amount = uint64(10 * 1e8); // 10 BTC + request.amount = fundingOutputAmount; /* solhint-disable-next-line not-rely-on-time */ request.revealedAt = uint32(block.timestamp); request.vault = reveal.vault; - request.treasuryFee = uint64(1 * 1e8); // 1 BTC + request.treasuryFee = _depositTreasuryFeeDivisor > 0 + ? fundingOutputAmount / _depositTreasuryFeeDivisor + : 0; request.sweptAt = 0; request.extraData = extraData; @@ -89,7 +110,7 @@ contract MockBridge is IBridge { emit DepositRevealed(depositKey); } - function sweepDeposit(uint256 depositKey) external { + function sweepDeposit(uint256 depositKey) public { require(_deposits[depositKey].revealedAt != 0, "Deposit not revealed"); require(_deposits[depositKey].sweptAt == 0, "Deposit already swept"); /* solhint-disable-next-line not-rely-on-time */ @@ -120,6 +141,10 @@ contract MockBridge is IBridge { depositRevealAheadPeriod = 0; } + function setDepositTreasuryFeeDivisor(uint64 value) external { + _depositTreasuryFeeDivisor = value; + } + function setDepositTxMaxFee(uint64 value) external { _depositTxMaxFee = value; } @@ -152,7 +177,7 @@ contract MockTBTCVault is ITBTCVault { _requests[depositKey].requestedAt = uint64(block.timestamp); } - function finalizeOptimisticMintingRequest(uint256 depositKey) external { + function finalizeOptimisticMintingRequest(uint256 depositKey) public { require( _requests[depositKey].requestedAt != 0, "Request does not exist" diff --git a/solidity/test/integrator/AbstractTBTCDepositor.test.ts b/solidity/test/integrator/AbstractTBTCDepositor.test.ts index 7e4486097..72a144462 100644 --- a/solidity/test/integrator/AbstractTBTCDepositor.test.ts +++ b/solidity/test/integrator/AbstractTBTCDepositor.test.ts @@ -213,13 +213,14 @@ describe("AbstractTBTCDepositor", () => { context("when deposit is finalized by the Bridge", () => { // The expected tbtcAmount is calculated as follows: // - // - Deposit amount = 10 BTC (hardcoded in MockBridge) - // - Treasury fee = 1 BTC (hardcoded in MockBridge) - // - Optimistic minting fee = 1% (default value used in MockTBTCVault) - // - Transaction max fee = 0.1 BTC (default value used in MockBridge) + // - Deposit amount = 10000 satoshi (hardcoded in funding transaction fixture) + // - Treasury fee = 2% (default value used in MockBridge) + // - Optimistic minting fee = 1% (default value used in MockTBTCVault) + // - Transaction max fee = 1000 satoshi (default value used in MockBridge) // - // ((10 BTC - 1 BTC) * 0.99) - 0.1 BTC = 8.81 BTC = 8.81 * 1e8 sat = 8.81 * 1e18 TBTC - const expectedTbtcAmount = to1ePrecision(881, 16).toString() + // ((10000 sat - 200 sat) * 0.99) - 2000 sat = 8702 sat = 8702 * 1e10 TBTC + const expectedInitialDepositAmount = to1ePrecision(10000, 10) + const expectedTbtcAmount = to1ePrecision(8702, 10).toString() context("when the deposit is swept", () => { let tx: ContractTransaction @@ -257,7 +258,11 @@ describe("AbstractTBTCDepositor", () => { it("should return proper values", async () => { await expect(tx) .to.emit(depositor, "FinalizeDepositReturned") - .withArgs(expectedTbtcAmount, fixture.extraData) + .withArgs( + expectedInitialDepositAmount, + expectedTbtcAmount, + fixture.extraData + ) }) }) @@ -303,7 +308,11 @@ describe("AbstractTBTCDepositor", () => { it("should return proper values", async () => { await expect(tx) .to.emit(depositor, "FinalizeDepositReturned") - .withArgs(expectedTbtcAmount, fixture.extraData) + .withArgs( + expectedInitialDepositAmount, + expectedTbtcAmount, + fixture.extraData + ) }) }) }) @@ -311,6 +320,17 @@ describe("AbstractTBTCDepositor", () => { }) describe("_calculateTbtcAmount", () => { + before(async () => { + await createSnapshot() + + // Set the transaction max fee to 0.1 BTC. + await bridge.setDepositTxMaxFee(10000000) + }) + + after(async () => { + await restoreSnapshot() + }) + context("when all fees are non-zero", () => { it("should return the correct amount", async () => { const depositAmount = to1ePrecision(10, 8) // 10 BTC @@ -321,7 +341,7 @@ describe("AbstractTBTCDepositor", () => { // - Deposit amount = 10 BTC // - Treasury fee = 1 BTC // - Optimistic minting fee = 1% (default value used in MockTBTCVault) - // - Transaction max fee = 0.1 BTC (default value used in MockBridge) + // - Transaction max fee = 0.1 BTC (set in MockBridge) // // ((10 BTC - 1 BTC) * 0.99) - 0.1 BTC = 8.81 BTC = 8.81 * 1e8 sat = 8.81 * 1e18 TBTC const expectedTbtcAmount = to1ePrecision(881, 16) @@ -377,7 +397,7 @@ describe("AbstractTBTCDepositor", () => { // - Deposit amount = 10 BTC // - Treasury fee = 0 BTC // - Optimistic minting fee = 1% (default value used in MockTBTCVault) - // - Transaction max fee = 0.1 BTC (default value used in MockBridge) + // - Transaction max fee = 0.1 BTC (set in MockBridge) // // ((10 BTC - 0 BTC) * 0.99) - 0.1 BTC = 9.8 BTC = 9.8 * 1e8 sat = 9.8 * 1e18 TBTC const expectedTbtcAmount = to1ePrecision(98, 17) @@ -412,7 +432,7 @@ describe("AbstractTBTCDepositor", () => { // - Deposit amount = 10 BTC // - Treasury fee = 1 BTC // - Optimistic minting fee = 0% (set in MockTBTCVault) - // - Transaction max fee = 0.1 BTC (default value used in MockBridge) + // - Transaction max fee = 0.1 BTC (set in MockBridge) // // ((10 BTC - 1 BTC) * 1) - 0.1 BTC = 8.9 BTC = 8.9 * 1e8 sat = 8.9 * 1e18 TBTC const expectedTbtcAmount = to1ePrecision(89, 17)