Juicebox Protocol. How to prepare for an audit of an already deployed project?
Most of the time, during public Bug bounty contests, the project has not only been deployed already but also audited once or more times.
Therefore, you have to get used to analyzing the project and creating a summary from their website and documentation.
And the good thing is that in the meanwhile, you are reading about it, researching their existing codebase, and placing things together in order for it to make sense.
So, here’s how I have prepared my summary for the Juicebox Buyback Delegate audit.
Content
Overview
Architecture. Core and Surface contracts
Juicebox Buyback delegate.
→ What is a Buyback contract?
→ What is a Data source contract?
→ What is a Delegate contract?
→ JBXBuybackDelegate.sol
Overview
The Juicebox protocol is a framework for funding and operating projects openly on Ethereum. It is versatile and allows you to build anything from an NFT project to a boutique crypto law firm, meaning, almost any idea you want to implement.
You can launch a DAO with governance
All-in-one crowdfunding with powerful treasury management and redemptions.
And as mentioned above, you can build and launch your NFT project
Juicebox is a governance-minimal protocol. There are only a few levers that can be tuned, none of which impose changes for users without their consent.
And of course, the governance smart contract is responsible to adjust these levers.
Their governance token is the JBX token.
Architecture
The protocol is made up of 7 core contracts and 3 surface contracts.
Core contracts
Some of the most relevant core contracts are the following:
JBTokenStore. — Manages token minting and burning for all projects
Step-by-step explanation of mintFor
function mintFor(
address _holder,
uint256 _projectId,
uint256 _amount,
bool _preferClaimedTokens
) external override onlyController(_projectId) { ... }
Step-by-step explanation of burnFrom
function burnFrom(
address _holder,
uint256 _projectId,
uint256 _amount,
bool _preferClaimedTokens
) external override onlyController(_projectId) { ... }
Step-by-step explanation of issueFor
function issueFor(
uint256 _projectId,
string calldata _name,
string calldata _symbol
)
external
override
requirePermission(projects.ownerOf(_projectId), _projectId, JBOperations.ISSUE)
returns (IJBToken token)
{ ... }
JBSplitsStore. — Stores information about how arbitrary distributions should be split. The information is represented as a JBSplit data structure.
A project can store splits for an arbitrary number of groups, such as for payout distributions or for reserved token distributions.
A split can specify an address, a Juicebox project, a contract that adheres to the
IJBSplitAllocator
interface, or the address that calls the transaction to distribute payouts or reserved tokens as its recipient.By default, splits can be changed at any time for any funding cycle configuration. A project’s owner can also independently lock a split to a funding cycle configuration for a customizable duration.
Step-by-step explanation of set
function set(
uint256 _projectId,
uint256 _domain,
JBGroupedSplits[] calldata _groupedSplits
)
external
override
requirePermissionAllowingOverride(
projects.ownerOf(_projectId),
_projectId,
JBOperations.SET_SPLITS,
address(directory.controllerOf(_projectId)) == msg.sender
) { ... }
And this is the struct used JBSplit
/**
@member preferClaimed A flag that only has effect if a projectId is also specified, and the project has a token contract attached. If so, this flag indicates if the tokens that result from making a payment to the project should be delivered claimed into the beneficiary's wallet, or unclaimed to save gas.
@member preferAddToBalance A flag indicating if a distribution to a project should prefer triggering it's addToBalance function instead of its pay function.
@member percent The percent of the whole group that this split occupies. This number is out of `JBConstants.SPLITS_TOTAL_PERCENT`.
@member projectId The ID of a project. If an allocator is not set but a projectId is set, funds will be sent to the protocol treasury belonging to the project who's ID is specified. Resulting tokens will be routed to the beneficiary with the claimed token preference respected.
@member beneficiary An address. The role the of the beneficary depends on whether or not projectId is specified, and whether or not an allocator is specified. If allocator is set, the beneficiary will be forwarded to the allocator for it to use. If allocator is not set but projectId is set, the beneficiary is the address to which the project's tokens will be sent that result from a payment to it. If neither allocator or projectId are set, the beneficiary is where the funds from the split will be sent.
@member lockedUntil Specifies if the split should be unchangeable until the specified time, with the exception of extending the locked period.
@member allocator If an allocator is specified, funds will be sent to the allocator contract along with all properties of this split.
*/
struct JBSplit {
bool preferClaimed;
bool preferAddToBalance;
uint256 percent;
uint256 projectId;
address payable beneficiary;
uint256 lockedUntil;
IJBSplitAllocator allocator;
}
JBPrices. — Manages and normalizes price feeds of various currencies.
The protocol uses this to allow projects to do their accounting in any number of currencies, but manage all funds in ETH or other assets regardless of accounting denomination.
feedFor
property:
// The available price feeds.
// The feed returns the number of `_currency` units that can be converted to 1 `_base` unit.
mapping(uint256 => mapping(uint256 => IJBPriceFeed)) public override feedFor;
Step-by-step explanation of addFeedFor
function addFeedFor(
uint256 _currency,
uint256 _base,
IJBPriceFeed _feed
) external override onlyOwner { ... }
Surface contracts
There are three main surface contracts that manage how projects manage funds and define how all core contracts should be used together.
JBController3_1
, stitches together funding cycles and project tokens, allowing for restricted control, accounting, and token management.JBPayoutRedemptionPaymentTerminal3_1
, manages all inflows and outflows of funds.JBSingleTokenPaymentTerminalStore3_1
, manages accounting data on behalf of payment terminals that manage balances of only one token type.
Juice Buyback Delegate
Juice buyback provides a data source and delegate which maximizes the project token received by the contributor when they call pay
on the terminal.
What is a Buyback contract?
It allows a retailer to return unsold inventory up to a specified amount at an agreed-upon price.
It increases the optimal order quantity for the retailer, resulting in higher product availability and higher profits for both the retailer and the supplier.
What is a Data source contract?
A data source contract is a way of providing extensions to a treasury that either overrides or augments the default payout redemption payment functionality.
A data source contract can be used to provide custom data to the
pay(...)
transaction and/or theredeemTokensOf(...)
transaction.A data source is responsible for specifying any delegate hooks that should be triggered after the core functionality of a
pay(...)
orredeemTokensOf(...)
transaction executes successfully.
What is a Delegate contract?
A delegate contract is a way of providing extensions to a treasury that augments the default payout redemption payment behavior.
Pay delegates include a custom
didPay(...)
hook that will execute after all of the default protocol pay logic has been successfully executed in the terminal contract.Redemption delegates include a custom
didRedeem(...)
hook that will execute after all of the default protocol redeem logic has been successfully executed in the terminal contract.
JBXBuybackDelegate.sol
Datasource and delegate allow pay beneficiaries to get the highest amount of project tokens between minting using the project weigh and swapping in a given Uniswap V3 pool.
Main external functions of the contract:
payParams()
is handling the data source implementation. And returns the weight to use, the original memo passed, and The amount to send to delegates instead of adding to the local balance.
function payParams(JBPayParamsData calldata _data) external override
returns (
uint256 weight,
string memory memo,
JBPayDelegateAllocation[] memory delegateAllocations
)
didPay()
, delegates to either swap to the beneficiary or mint to the beneficiary.
This delegate is called only if the quote for the swap is bigger than the lowest received when minting.
function didPay(JBDidPayData calldata _data) external payable override
uniswapV3SwapCallback
is where the token transfer should happen.
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external override
Hopefully, you can make use of this during the audit or even practice with the audit afterward.
Also, use this as an example of how you should prepare your audits.
Make sure you follow me on Twitter https://twitter.com/TheBlockChainer for my latest updates.