Skip to content

Commit

Permalink
Merge branch 'main' of github.com:thesis/acre into error-after-deposi…
Browse files Browse the repository at this point in the history
…t-action
  • Loading branch information
kkosiorowska committed Apr 10, 2024
2 parents d4d8f0e + bd4f25f commit f0a798b
Show file tree
Hide file tree
Showing 69 changed files with 1,556 additions and 221 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "@keep-network/tbtc-v2/contracts/integrator/AbstractTBTCDepositor.sol";

import {stBTC} from "./stBTC.sol";

/// @title Acre Bitcoin Depositor contract.
/// @title Bitcoin Depositor contract.
/// @notice The contract integrates Acre staking with tBTC minting.
/// User who wants to stake BTC in Acre should submit a Bitcoin transaction
/// to the most recently created off-chain ECDSA wallets of the tBTC Bridge
Expand All @@ -35,10 +35,7 @@ import {stBTC} from "./stBTC.sol";
/// Depositor address. After tBTC is minted to the Depositor, on the stake
/// finalization tBTC is staked in Acre and stBTC shares are emitted
/// to the staker.
contract AcreBitcoinDepositor is
AbstractTBTCDepositor,
Ownable2StepUpgradeable
{
contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable {
using SafeERC20 for IERC20;

/// @notice State of the stake request.
Expand Down Expand Up @@ -146,7 +143,7 @@ contract AcreBitcoinDepositor is
_disableInitializers();
}

/// @notice Acre Bitcoin Depositor contract initializer.
/// @notice Bitcoin Depositor contract initializer.
/// @param bridge tBTC Bridge contract instance.
/// @param tbtcVault tBTC Vault contract instance.
/// @param _tbtcToken tBTC token contract instance.
Expand Down
168 changes: 168 additions & 0 deletions core/contracts/BitcoinRedeemer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

import "@thesis-co/solidity-contracts/contracts/token/IReceiveApproval.sol";

import "./stBTC.sol";
import "./bridge/ITBTCToken.sol";
import {ZeroAddress} from "./utils/Errors.sol";

/// @title Bitcoin Redeemer
/// @notice This contract facilitates redemption of stBTC tokens to Bitcoin through
/// tBTC redemption process.
contract BitcoinRedeemer is Ownable2StepUpgradeable, IReceiveApproval {
/// Interface for tBTC token contract.
ITBTCToken public tbtcToken;

/// stBTC token contract.
stBTC public stbtc;

/// Address of the TBTCVault contract.
address public tbtcVault;

/// Emitted when the TBTCVault contract address is updated.
/// @param oldTbtcVault Address of the old TBTCVault contract.
/// @param newTbtcVault Address of the new TBTCVault contract.
event TbtcVaultUpdated(address oldTbtcVault, address newTbtcVault);

/// Emitted when redemption is requested.
/// @param owner Owner of stBTC tokens.
/// @param shares Number of stBTC tokens.
/// @param tbtcAmount Number of tBTC tokens.
event RedemptionRequested(
address indexed owner,
uint256 shares,
uint256 tbtcAmount
);

/// Reverts if the tBTC Token address is zero.
error TbtcTokenZeroAddress();

/// Reverts if the stBTC address is zero.
error StbtcZeroAddress();

/// Reverts if the TBTCVault address is zero.
error TbtcVaultZeroAddress();

/// Attempted to call receiveApproval for not supported token.
error UnsupportedToken(address token);

/// Attempted to call receiveApproval by supported token.
error CallerNotAllowed(address caller);

/// Attempted to call receiveApproval with empty data.
error EmptyExtraData();

/// Attempted to call redeemSharesAndUnmint with unexpected tBTC token owner.
error UnexpectedTbtcTokenOwner();

/// Reverts when approveAndCall to tBTC contract fails.
error ApproveAndCallFailed();

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Initializes the contract with tBTC token and stBTC token addresses.
/// @param _tbtcToken The address of the tBTC token contract.
/// @param _stbtc The address of the stBTC token contract.
/// @param _tbtcVault The address of the TBTCVault contract.
function initialize(
address _tbtcToken,
address _stbtc,
address _tbtcVault
) public initializer {
__Ownable2Step_init();
__Ownable_init(msg.sender);

if (address(_tbtcToken) == address(0)) {
revert TbtcTokenZeroAddress();
}
if (address(_stbtc) == address(0)) {
revert StbtcZeroAddress();
}
if (address(_tbtcVault) == address(0)) {
revert TbtcVaultZeroAddress();
}

tbtcToken = ITBTCToken(_tbtcToken);
stbtc = stBTC(_stbtc);
tbtcVault = _tbtcVault;
}

/// @notice Redeems shares for tBTC and requests bridging to Bitcoin.
/// @param from Shares token holder executing redemption.
/// @param amount Amount of shares to redeem.
/// @param token stBTC token address.
/// @param extraData Redemption data in a format expected from
/// `redemptionData` parameter of Bridge's `receiveBalanceApproval`
/// function.
function receiveApproval(
address from,
uint256 amount,
address token,
bytes calldata extraData
) external {
if (token != address(stbtc)) revert UnsupportedToken(token);
if (msg.sender != token) revert CallerNotAllowed(msg.sender);
if (extraData.length == 0) revert EmptyExtraData();

redeemSharesAndUnmint(from, amount, extraData);
}

/// @notice Updates TBTCVault contract address.
/// @param newTbtcVault New TBTCVault contract address.
function updateTbtcVault(address newTbtcVault) external onlyOwner {
if (newTbtcVault == address(0)) {
revert ZeroAddress();
}

emit TbtcVaultUpdated(tbtcVault, newTbtcVault);

tbtcVault = newTbtcVault;
}

/// @notice Initiates the redemption process by exchanging stBTC tokens for
/// tBTC tokens and requesting bridging to Bitcoin.
/// @dev Redeems stBTC shares to receive tBTC and requests redemption of tBTC
/// to Bitcoin via tBTC Bridge.
/// Redemption data in a format expected from `redemptionData` parameter
/// of Bridge's `receiveBalanceApproval`.
/// It uses tBTC token owner which is the TBTCVault contract as spender
/// of tBTC requested for redemption.
/// @dev tBTC Bridge redemption process has a path where request can timeout.
/// It is a scenario that is unlikely to happen with the current Bridge
/// setup. This contract remains upgradable to have flexibility to handle
/// adjustments to tBTC Bridge changes.
/// @dev Redemption data should include a `redeemer` address matching the
/// address of the staker who is redeeming the shares. In case anything
/// goes wrong during the tBTC unminting process, the redeemer will be
/// able to claim the tBTC tokens back from the tBTC Bank contract.
/// @param owner The owner of the stBTC tokens.
/// @param shares The number of stBTC tokens to redeem.
/// @param tbtcRedemptionData Additional data required for the tBTC redemption.
/// See `redemptionData` parameter description of `Bridge.requestRedemption`
/// function.
function redeemSharesAndUnmint(
address owner,
uint256 shares,
bytes calldata tbtcRedemptionData
) internal {
// TBTC Token contract owner resolves to the TBTCVault contract.
if (tbtcToken.owner() != tbtcVault) revert UnexpectedTbtcTokenOwner();

uint256 tbtcAmount = stbtc.redeem(shares, address(this), owner);

// slither-disable-next-line reentrancy-events
emit RedemptionRequested(owner, shares, tbtcAmount);

if (
!tbtcToken.approveAndCall(tbtcVault, tbtcAmount, tbtcRedemptionData)
) {
revert ApproveAndCallFailed();
}
}
}
25 changes: 25 additions & 0 deletions core/contracts/bridge/ITBTCToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

/// @title Interface of TBTC token contract.
/// @notice This interface defines functions of TBTC token contract used by Acre
/// contracts.
interface ITBTCToken {
/// @notice Calls `receiveApproval` function on spender previously approving
/// the spender to withdraw from the caller multiple times, up to
/// the `amount` amount. If this function is called again, it
/// overwrites the current allowance with `amount`. Reverts if the
/// approval reverted or if `receiveApproval` call on the spender
/// reverted.
/// @return True if both approval and `receiveApproval` calls succeeded.
/// @dev If the `amount` is set to `type(uint256).max` then
/// `transferFrom` and `burnFrom` will not reduce an allowance.
function approveAndCall(
address spender,
uint256 amount,
bytes memory extraData
) external returns (bool);

/// @dev Returns the address of the contract owner.
function owner() external view returns (address);
}
39 changes: 35 additions & 4 deletions core/contracts/stBTC.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.21;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "@thesis-co/solidity-contracts/contracts/token/IReceiveApproval.sol";

import "./Dispatcher.sol";
import "./PausableOwnable.sol";
import "./lib/ERC4626Fees.sol";
Expand All @@ -19,6 +21,7 @@ import {ZeroAddress} from "./utils/Errors.sol";
/// of yield-bearing vaults. This contract facilitates the minting and
/// burning of shares (stBTC), which are represented as standard ERC20
/// tokens, providing a seamless exchange with tBTC tokens.
// slither-disable-next-line missing-inheritance
contract stBTC is ERC4626Fees, PausableOwnable {
using SafeERC20 for IERC20;

Expand All @@ -42,8 +45,9 @@ contract stBTC is ERC4626Fees, PausableOwnable {
uint256 public exitFeeBasisPoints;

/// Emitted when the treasury wallet address is updated.
/// @param treasury New treasury wallet address.
event TreasuryUpdated(address treasury);
/// @param oldTreasury Address of the old treasury wallet.
/// @param newTreasury Address of the new treasury wallet.
event TreasuryUpdated(address oldTreasury, address newTreasury);

/// Emitted when deposit parameters are updated.
/// @param minimumDepositAmount New value of the minimum deposit amount.
Expand Down Expand Up @@ -101,9 +105,10 @@ contract stBTC is ERC4626Fees, PausableOwnable {
if (newTreasury == address(this)) {
revert DisallowedAddress();
}
treasury = newTreasury;

emit TreasuryUpdated(newTreasury);
emit TreasuryUpdated(treasury, newTreasury);

treasury = newTreasury;
}

/// @notice Updates minimum deposit amount.
Expand Down Expand Up @@ -170,6 +175,32 @@ contract stBTC is ERC4626Fees, PausableOwnable {
emit ExitFeeBasisPointsUpdated(newExitFeeBasisPoints);
}

/// @notice Calls `receiveApproval` function on spender previously approving
/// the spender to withdraw from the caller multiple times, up to
/// the `amount` amount. If this function is called again, it
/// overwrites the current allowance with `amount`. Reverts if the
/// approval reverted or if `receiveApproval` call on the spender
/// reverted.
/// @return True if both approval and `receiveApproval` calls succeeded.
/// @dev If the `amount` is set to `type(uint256).max` then
/// `transferFrom` and `burnFrom` will not reduce an allowance.
function approveAndCall(
address spender,
uint256 value,
bytes memory extraData
) external returns (bool) {
if (approve(spender, value)) {
IReceiveApproval(spender).receiveApproval(
msg.sender,
value,
address(this),
extraData
);
return true;
}
return false;
}

/// @notice Mints shares to receiver by depositing exactly amount of
/// tBTC tokens.
/// @dev Takes into account a deposit parameter, minimum deposit amount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* solhint-disable func-name-mixedcase */
pragma solidity ^0.8.21;

import {AcreBitcoinDepositor} from "../AcreBitcoinDepositor.sol";
import {BitcoinDepositor} from "../BitcoinDepositor.sol";
import {MockBridge, MockTBTCVault} from "@keep-network/tbtc-v2/contracts/test/TestTBTCDepositor.sol";
import {IBridge} from "@keep-network/tbtc-v2/contracts/integrator/IBridge.sol";
import {IBridgeTypes} from "@keep-network/tbtc-v2/contracts/integrator/IBridge.sol";
Expand Down
43 changes: 43 additions & 0 deletions core/contracts/test/TestTBTC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../bridge/ITBTCToken.sol";

contract TestTBTC is ITBTCToken, ERC20 {
event ApproveAndCallCalled(
address spender,
uint256 amount,
bytes extraData
);

bool public approveAndCallResult = true;

address public owner;

constructor(string memory name, string memory symbol) ERC20(name, symbol) {
owner = address(1);
}

function mint(address account, uint256 value) external {
_mint(account, value);
}

function approveAndCall(
address spender,
uint256 amount,
bytes memory extraData
) external returns (bool) {
emit ApproveAndCallCalled(spender, amount, extraData);

return approveAndCallResult;
}

function setApproveAndCallResult(bool value) public {
approveAndCallResult = value;
}

function setOwner(address newOwner) public {
owner = newOwner;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ import "@keep-network/tbtc-v2/contracts/integrator/AbstractTBTCDepositor.sol";

import {stBTC} from "../../stBTC.sol";

/// @title AcreBitcoinDepositorV2
/// @dev This is a contract used to test Acre Bitcoin Depositor upgradeability.
/// It is a copy of AcreBitcoinDepositor contract with some differences
/// @title BitcoinDepositorV2
/// @dev This is a contract used to test Bitcoin Depositor upgradeability.
/// It is a copy of BitcoinDepositor contract with some differences
/// marked with `TEST:` comments.
contract AcreBitcoinDepositorV2 is
AbstractTBTCDepositor,
Ownable2StepUpgradeable
{
contract BitcoinDepositorV2 is AbstractTBTCDepositor, Ownable2StepUpgradeable {
using SafeERC20 for IERC20;

/// @notice State of the stake request.
Expand Down
Loading

0 comments on commit f0a798b

Please sign in to comment.