Skip to content

Commit

Permalink
Merge pull request #176 from bancorprotocol/carbon-batcher
Browse files Browse the repository at this point in the history
Carbon Batcher - add StrategiesCreated event and nft withdraw
  • Loading branch information
ivanzhelyazkov authored Jan 22, 2025
2 parents c223ef4 + 24c8fe9 commit 913a1d2
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 1 deletion.
26 changes: 26 additions & 0 deletions contracts/utility/CarbonBatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,21 @@ contract CarbonBatcher is Upgradeable, Utils, ReentrancyGuard, IERC721Receiver {
ICarbonController private immutable _carbonController;
IVoucher private immutable _voucher;

/**
* @dev triggered when strategies have been created
*/
event StrategiesCreated(address indexed owner, uint256[] strategyIds);

/**
* @dev triggered when tokens have been withdrawn from the carbon batcher
*/
event FundsWithdrawn(Token indexed token, address indexed caller, address indexed target, uint256 amount);

/**
* @dev triggered when an NFT has been withdrawn from the carbon batcher
*/
event NFTWithdrawn(uint256 indexed tokenId, address indexed caller, address indexed target);

constructor(IVoucher voucherInit) validAddress(address(voucherInit)) {
_voucher = voucherInit;
_carbonController = ICarbonController(_voucher.controller());
Expand Down Expand Up @@ -119,6 +129,8 @@ contract CarbonBatcher is Upgradeable, Utils, ReentrancyGuard, IERC721Receiver {
payable(msg.sender).sendValue(txValueLeft);
}

emit StrategiesCreated(msg.sender, strategyIds);

return strategyIds;
}

Expand All @@ -145,6 +157,20 @@ contract CarbonBatcher is Upgradeable, Utils, ReentrancyGuard, IERC721Receiver {
emit FundsWithdrawn({ token: token, caller: msg.sender, target: target, amount: amount });
}

/**
* @notice withdraws voucher nft held by the contract and sends it to an account
* @notice note that this is a safety mechanism, shouldn't be necessary in normal operation
*
* requirements:
*
* - the caller is admin of the contract
*/
function withdrawNFT(uint256 tokenId, address target) external validAddress(target) onlyAdmin nonReentrant {
_voucher.safeTransferFrom(address(this), target, tokenId, "");

emit NFTWithdrawn({ tokenId: tokenId, caller: msg.sender, target: target });
}

/**
* @inheritdoc IERC721Receiver
*/
Expand Down
81 changes: 80 additions & 1 deletion test/forge/CarbonBatcher.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ contract CarbonBatcherTest is TestFixture {
Order order1
);

/**
* @dev triggered when strategies have been created
*/
event StrategiesCreated(address indexed owner, uint256[] strategyIds);

/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
Expand Down Expand Up @@ -178,6 +183,38 @@ contract CarbonBatcherTest is TestFixture {
vm.stopPrank();
}

function testBatchCreateShouldEmitBatchCreatedStrategiesEvent(uint256 strategyCount) public {
vm.startPrank(user1);
// create 1 to 10 strategies
strategyCount = bound(strategyCount, 1, 10);
// define strategy data
StrategyData[] memory strategies = new StrategyData[](strategyCount);
Order[2] memory orders = [generateTestOrder(), generateTestOrder()];
Token[2] memory tokens = [token0, token1];
for (uint256 i = 0; i < strategyCount; ++i) {
strategies[i] = StrategyData({ tokens: tokens, orders: orders });
}

// approve batch router
token0.safeApprove(address(carbonBatcher), 1e18);
token1.safeApprove(address(carbonBatcher), 1e18);

uint256[] memory strategyIds = new uint256[](strategyCount);

for (uint256 i = 0; i < strategyCount; ++i) {
strategyIds[i] = generateStrategyId(1, i + 1);
}

// expect to emit batch created strategies event
vm.expectEmit();
emit StrategiesCreated(user1, strategyIds);

// Create a batch of strategies
carbonBatcher.batchCreate(strategies);

vm.stopPrank();
}

/// @dev test batch create should transfer funds from user to carbon controller
function testBatchCreateUserShouldTransferFunds(uint128 liquidity0, uint128 liquidity1) public {
liquidity0 = uint128(bound(liquidity0, 1, MAX_SOURCE_AMOUNT));
Expand Down Expand Up @@ -280,9 +317,11 @@ contract CarbonBatcherTest is TestFixture {
}

/**
* @dev withdrawFunds tests
* @dev admin function tests
*/

// withdrawFunds tests

/// @dev test should revert when attempting to withdraw funds without the admin role
function testShouldRevertWhenAttemptingToWithdrawFundsWithoutTheAdminRole() public {
vm.prank(user2);
Expand Down Expand Up @@ -315,6 +354,46 @@ contract CarbonBatcherTest is TestFixture {
vm.stopPrank();
}

// nft withdraw tests

/// @dev test should revert when attempting to withdraw nft without the admin role
function testShouldRevertWhenAttemptingToWithdrawNFTWithoutTheAdminRole() public {
vm.prank(user2);
vm.expectRevert(AccessDenied.selector);
carbonBatcher.withdrawNFT(generateStrategyId(1, 1), user2);
}

/// @dev test should revert when attempting to withdraw nft to an invalid address
function testShouldRevertWhenAttemptingToWithdrawNFTToAnInvalidAddress() public {
vm.prank(admin);
vm.expectRevert(InvalidAddress.selector);
carbonBatcher.withdrawNFT(generateStrategyId(1, 1), payable(address(0)));
}

/// @dev test admin should be able to withdraw nft
function testAdminShouldBeAbleToWithdrawNFT() public {
vm.prank(user1);

uint256 strategyId = generateStrategyId(1, 1);
// safe mint an nft to carbon batcher
voucher.safeMintTest(address(carbonBatcher), strategyId);

vm.startPrank(admin);

// assert user1 has no voucher nfts
uint256[] memory tokenIds = voucher.tokensByOwner(user1, 0, 100);
assertEq(tokenIds.length, 0);

// withdraw nft to user1
carbonBatcher.withdrawNFT(strategyId, user1);

// assert user1 received the nft
tokenIds = voucher.tokensByOwner(user1, 0, 100);
assertEq(tokenIds.length, 1);
assertEq(tokenIds[0], generateStrategyId(1, 1));
vm.stopPrank();
}

/// @dev helper function to generate test order
function generateTestOrder() private pure returns (Order memory order) {
return Order({ y: 800000, z: 8000000, A: 736899889, B: 12148001999 });
Expand Down

0 comments on commit 913a1d2

Please sign in to comment.