Standing on the Shoulders of Giants

I spent the last few weeks traveling and it interrupted my work habits quite a bit. I’ve gotten back into the swing of things now. I’m excited to double down on learning, building, and writing about web3 this winter.

Taking some time to reflect

I had some time to think about how I want to prioritize my time. There are so many opportunities and projects that I could work on at any given time. It’s important to make sure I use my time well. I want to try building some niche products (alone or with a small group) and do some freelance services work for other teams to see which work style and pace I like best. Both avenues allow for creativity in implementing the contract architecture and web apps, but the scope, commitment, overhead, and financials are rather different for each case.

Being a relative noob, it’s unlikely you’re going to build a widely successful project on your own initially without serious luck or past, relevant experience (perhaps with the exception a well-timed fork of something existing on a L2). That being said…

The most open giants ever

While away, I did more reading than building. I read about some of the newer DeFi protocols and have various concepts to explore this winter, including:

This got me thinking about how amazing the web3 ecosystem is. The composability of existing protocols and the fact that most of the code is open source means we can “stand on the shoulders of giants” like few others have been able to in past industries.

Some of the early pioneers on Ethereum constructed primitive building blocks that makes a whole new class of applications possible today. These giants include MakerDAO (DAI), Compound and Aave (Lending), Uniswap (AMM), Punks and Kitties (NFTs and ERC721), OpenZeppelin, and many others. OpenZeppelin has open source implementations of many useful primitives (e.g. access control, proxy contracts, and reentrancy guard) as well as major ERC specifications to quickly build common projects. These resources are invaluable for anyone getting started in the space. The newer protocols have followed in their footsteps and expanded the integrations and code available.

Wonder how Olympus does staking with rebase? Go read it on Github, then fork and build your own version.

Need a lending protocol to power an app? Just plug into Aave and leverage their documentation plus other contracts that have integrated (like Yearn strategies).

Worried about getting hacked? Review past hack vulnerabilities, use a static code analysis tool to check for issues, and post your contract for roasting in a developer discord.

The resources are endless.

Leveraging the work of giants

The small amount of work I did while gone focused on continued testing and optimization of the auto-compounding vaults for Crypto Raiders. While debugging some math challenges based on the way balances were being stored, I started thinking I might be approaching the problem the wrong way.

I began to explore alternatives and found the Yearn v1 contract documentation. I had previously considered an architecture where user balances would be tracked by an ERC20 token (similar to Yearn) instead of the vaultWeight system I had devised to avoid looping over users. After having small rounding errors show up and reading the yearn docs, I decided to give it another go.

Yearn's v1 vault contract had pretty much all the building blocks I needed to take user deposits in the SlpTokens, track the balances, and issue rvTokens back to them. Withdrawing does the opposite. After spending a bit of time tweaking it to use a simpler architecture (just a vault and strategy contract), I had a working implementation of "rVault". Here is the interface (doesn't include all the standard ERC20 functions):

interface IrVault {
    // -------------------- EVENTS --------------------
    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);

    // -------------------- VIEW FUNCTIONS --------------------
    function getDepositToken() external view returns (address);
    function totalBalance() external view returns (uint);
    function getPricePerFullShare() external view returns (uint256);
    function getTokensFromShares() external view returns (uint256);

    // -------------------- ADMIN FUNCTIONS --------------------
    function setStrategy(address _strategyAddr) external;
    function pause() external;
    function unpause() external;
    function emergencyWithdrawFromStrategy() external;

    // -------------------- USER FUNCTIONS --------------------
    function deposit(uint _amount) external payable;
    function withdraw(uint _shares) external;
    function withdrawAll() external;
}

With the vault in place, I decided to build a generalized (abstract) rStrategy contract and interface to provide a consistent API for the rVault to leverage. In this setup, the rVault contract is the rStrategy's end user. It takes receipts from end users and sends them to the vault. The benefit of separating the two is that you can swap out strategies without having to get users to take their funds out of the vault. The general strategy also handles management of strategy fees and access control to three roles: Owner, Manager, and Vault.

interface IrStrategy {
    // ----------- DEPOSIT/WITHDRAW ACTIONS ------------
    function deposit(uint _amount) external;
    function withdraw(uint _amount) external;
    function withdrawAll() external;

    // ---------- OWNER FUNCTIONS ---------------------
    function setManager(address _managerAddr) external;
    function setFeePercent(uint _percent) external;
    function withdrawFees() external;
    function pause() external;
    function unpause() external;

    // ------------ VIEW FUNCTIONS ---------------------
    function totalBalance() external view returns (uint);
    function getDepositToken() external view returns (address);
}

After the rStrategy contract was in place, I just needed to create a version of it for the Crypto Raiders SLP staking strategy by porting my staking, getRewards, and compound logic over. In the process, I was able to update it to handle both MATIC and ERC20 SLP pairs for the tokens.

Overall, leveraging the Yearn contracts, some pieces directly others as a concept, greatly accelerated my development of the new architecture. I also added a price oracle using UniswapV2 Time Weighted Average Price (TWAP). I couldn't have done it as quickly (or wouldn't have thought of the idea?) if the giants hadn't provided their shoulders to stand on.