Skip to content

Commit

Permalink
Merge pull request #128 from lombard-finance/stake-and-bake-updates
Browse files Browse the repository at this point in the history
Add operator and updateable fee
  • Loading branch information
hashxtree authored Jan 13, 2025
2 parents fd64909 + 4f7a2e1 commit 8427689
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 20 deletions.
76 changes: 65 additions & 11 deletions contracts/stakeAndBake/StakeAndBake.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,26 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
error IncorrectPermitAmount();
/// @dev error thrown when the remaining amount after taking a fee is zero
error ZeroDepositAmount();
/// @dev error thrown when an unauthorized account calls an operator only function
error UnauthorizedAccount(address account);
/// @dev error thrown when operator is changed to zero address
error ZeroAddress();

event DepositorAdded(address indexed vault, address indexed depositor);
event DepositorRemoved(address indexed vault);
event BatchStakeAndBakeReverted(
address indexed owner,
StakeAndBakeData data
);
event FeeChanged(uint256 indexed oldFee, uint256 indexed newFee);
event OperatorRoleTransferred(
address indexed previousOperator,
address indexed newOperator
);

struct StakeAndBakeData {
/// @notice vault Address of the vault we will deposit the minted LBTC to
address vault;
/// @notice owner Address of the user staking and baking
address owner;
/// @notice permitPayload Contents of permit approval signed by the user
bytes permitPayload;
/// @notice depositPayload Contains the parameters needed to complete a deposit
Expand All @@ -50,6 +57,8 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
struct StakeAndBakeStorage {
LBTC lbtc;
mapping(address => IDepositor) depositors;
address operator;
uint256 fee;
}

// keccak256(abi.encode(uint256(keccak256("lombardfinance.storage.StakeAndBake")) - 1)) & ~bytes32(uint256(0xff))
Expand All @@ -62,14 +71,39 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
_disableInitializers();
}

function initialize(address lbtc_, address owner_) external initializer {
__Ownable_init(owner_);
__Ownable2Step_init();
modifier onlyOperator() {
if (_getStakeAndBakeStorage().operator != _msgSender()) {
revert UnauthorizedAccount(_msgSender());
}
_;
}

function initialize(
address lbtc_,
address owner_,
address operator_,
uint256 fee_
) external initializer {
__ReentrancyGuard_init();

__Ownable_init(owner_);
__Ownable2Step_init();

StakeAndBakeStorage storage $ = _getStakeAndBakeStorage();
$.lbtc = LBTC(lbtc_);
$.fee = fee_;
$.operator = operator_;
}

/**
* @notice Sets the claiming fee
* @param fee The fee to set
*/
function setFee(uint256 fee) external onlyOperator {
StakeAndBakeStorage storage $ = _getStakeAndBakeStorage();
uint256 oldFee = $.fee;
$.fee = fee;
emit FeeChanged(oldFee, fee);
}

/**
Expand Down Expand Up @@ -101,7 +135,11 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
function batchStakeAndBake(StakeAndBakeData[] calldata data) external {
for (uint256 i; i < data.length; ) {
try this.stakeAndBake(data[i]) {} catch {
emit BatchStakeAndBakeReverted(data[i].owner, data[i]);
Actions.DepositBtcAction memory action = Actions.depositBtc(
data[i].mintPayload[4:]
);
address owner = action.recipient;
emit BatchStakeAndBakeReverted(owner, data[i]);
}

unchecked {
Expand Down Expand Up @@ -138,11 +176,17 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
(uint256, uint256, uint8, bytes32, bytes32)
);

// Check the recipient.
Actions.DepositBtcAction memory action = Actions.depositBtc(
data.mintPayload[4:]
);
address owner = action.recipient;

// We check if we can simply use transferFrom.
// Otherwise, we permit the depositor to transfer the minted value.
if ($.lbtc.allowance(data.owner, address(this)) < permitAmount)
if ($.lbtc.allowance(owner, address(this)) < permitAmount)
$.lbtc.permit(
data.owner,
owner,
address(this),
permitAmount,
deadline,
Expand All @@ -151,10 +195,10 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
s
);

$.lbtc.transferFrom(data.owner, address(this), permitAmount);
$.lbtc.transferFrom(owner, address(this), permitAmount);

// Take the current maximum fee from the user.
uint256 feeAmount = $.lbtc.getMintFee();
uint256 feeAmount = $.fee;
$.lbtc.transfer($.lbtc.getTreasury(), feeAmount);

uint256 remainingAmount = permitAmount - feeAmount;
Expand All @@ -165,14 +209,24 @@ contract StakeAndBake is Ownable2StepUpgradeable, ReentrancyGuardUpgradeable {
$.lbtc.approve(address(depositor), remainingAmount);

// Finally, deposit LBTC to the given `vault`.
depositor.deposit(data.vault, data.owner, data.depositPayload);
depositor.deposit(data.vault, owner, data.depositPayload);
}

function getStakeAndBakeFee() external view returns (uint256) {
StakeAndBakeStorage storage $ = _getStakeAndBakeStorage();
return $.lbtc.getMintFee();
}

function transferOperatorRole(address newOperator) external onlyOwner {
if (newOperator == address(0)) {
revert ZeroAddress();
}
StakeAndBakeStorage storage $ = _getStakeAndBakeStorage();
address oldOperator = $.operator;
$.operator = newOperator;
emit OperatorRoleTransferred(oldOperator, newOperator);
}

function _getStakeAndBakeStorage()
private
pure
Expand Down
81 changes: 72 additions & 9 deletions test/StakeAndBake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('StakeAndBake', function () {
signer1: Signer,
signer2: Signer,
signer3: Signer,
operator: Signer,
treasury: Signer;
let stakeAndBake: StakeAndBake;
let tellerWithMultiAssetSupportDepositor: TellerWithMultiAssetSupportDepositor;
Expand All @@ -39,7 +40,7 @@ describe('StakeAndBake', function () {
let snapshotTimestamp: number;

before(async function () {
[deployer, signer1, signer2, signer3, treasury] =
[deployer, signer1, signer2, signer3, operator, treasury] =
await getSignersWithPrivateKeys();

const burnCommission = 1000;
Expand All @@ -53,6 +54,8 @@ describe('StakeAndBake', function () {
stakeAndBake = await deployContract<StakeAndBake>('StakeAndBake', [
await lbtc.getAddress(),
deployer.address,
operator.address,
1,
]);

teller = await deployContract<TellerWithMultiAssetSupportMock>(
Expand Down Expand Up @@ -122,9 +125,6 @@ describe('StakeAndBake', function () {
encode(['uint256'], [0]) // txid
);

// set max fee
await lbtc.setMintFee(fee);

// create permit payload
const block = await ethers.provider.getBlock('latest');
const timestamp = block!.timestamp;
Expand Down Expand Up @@ -152,11 +152,62 @@ describe('StakeAndBake', function () {
);
});

it('should allow owner to change operator', async function () {
await expect(stakeAndBake.transferOperatorRole(signer2.address))
.to.emit(stakeAndBake, 'OperatorRoleTransferred')
.withArgs(operator.address, signer2.address);
});

it('should not allow anyone else to change operator', async function () {
await expect(
stakeAndBake
.connect(signer2)
.transferOperatorRole(signer2.address)
).to.be.reverted;
});

it('should allow operator to change the fee', async function () {
await expect(stakeAndBake.connect(operator).setFee(2))
.to.emit(stakeAndBake, 'FeeChanged')
.withArgs(1, 2);
});

it('should not allow anyone else to change the fee', async function () {
await expect(stakeAndBake.setFee(2)).to.be.reverted;
});

it('should allow admin to add a depositor', async function () {
await expect(
stakeAndBake.addDepositor(signer1.address, signer2.address)
)
.to.emit(stakeAndBake, 'DepositorAdded')
.withArgs(signer1.address, signer2.address);
});

it('should not allow anyone else to add a depositor', async function () {
await expect(
stakeAndBake
.connect(signer1)
.addDepositor(signer1.address, signer2.address)
).to.be.reverted;
});

it('should allow admin to remove a depositor', async function () {
await expect(stakeAndBake.removeDepositor(signer1.address))
.to.emit(stakeAndBake, 'DepositorRemoved')
.withArgs(signer1.address);
});

it('should not allow anyone else to remove a depositor', async function () {
await expect(
stakeAndBake.connect(signer1).removeDepositor(signer1.address)
).to.be.reverted;
});

it('should stake and bake properly with the correct setup', async function () {
await expect(
stakeAndBake.stakeAndBake({
vault: await teller.getAddress(),
owner: signer2.address,
permitPayload: permitPayload,
depositPayload: depositPayload,
mintPayload: data.payload,
Expand Down Expand Up @@ -198,6 +249,7 @@ describe('StakeAndBake', function () {
depositValue - 50
);
});

it('should work with allowance', async function () {
await lbtc
.connect(signer2)
Expand All @@ -206,7 +258,6 @@ describe('StakeAndBake', function () {
await expect(
stakeAndBake.stakeAndBake({
vault: await teller.getAddress(),
owner: signer2.address,
permitPayload: permitPayload,
depositPayload: depositPayload,
mintPayload: data.payload,
Expand Down Expand Up @@ -248,6 +299,7 @@ describe('StakeAndBake', function () {
depositValue - 50
);
});

it('should batch stake and bake properly with the correct setup', async function () {
// NB for some reason trying to do this in a loop and passing around arrays of parameters
// makes the test fail, so i'm doing it the ugly way here
Expand Down Expand Up @@ -292,15 +344,13 @@ describe('StakeAndBake', function () {
stakeAndBake.batchStakeAndBake([
{
vault: await teller.getAddress(),
owner: signer2.address,
permitPayload: permitPayload,
depositPayload: depositPayload,
mintPayload: data.payload,
proof: data.proof,
},
{
vault: await teller.getAddress(),
owner: signer3.address,
permitPayload: permitPayload2,
depositPayload: depositPayload2,
mintPayload: data2.payload,
Expand Down Expand Up @@ -377,6 +427,7 @@ describe('StakeAndBake', function () {
depositValue - 50
);
});

it('should revert when an unknown depositor is invoked', async function () {
await expect(
stakeAndBake.removeDepositor(await teller.getAddress())
Expand All @@ -387,13 +438,25 @@ describe('StakeAndBake', function () {
await expect(
stakeAndBake.stakeAndBake({
vault: await teller.getAddress(),
owner: signer2.address,
permitPayload: permitPayload,
depositPayload: depositPayload,
mintPayload: data.payload,
proof: data.proof,
})
).to.be.revertedWithCustomError(stakeAndBake, 'VaultNotFound');
});

it('should revert when remaining amount is zero', async function () {
await stakeAndBake.connect(operator).setFee(10001);
await expect(
stakeAndBake.stakeAndBake({
vault: await teller.getAddress(),
permitPayload: permitPayload,
depositPayload: depositPayload,
mintPayload: data.payload,
proof: data.proof,
})
).to.be.revertedWithCustomError(stakeAndBake, 'ZeroDepositAmount');
});
});
});

0 comments on commit 8427689

Please sign in to comment.