NicolaBernini / BlockchainAnalysis

Analysis of Blockchain related Stuff
3 stars 2 forks source link

Transaction Analysis - Creation of an Opyn Option #12

Open NicolaBernini opened 3 years ago

NicolaBernini commented 3 years ago

1. Overview

Let's consider this token

WETHUSDC 30-April-2021 1280Put USDC Collateral

https://etherscan.io/token/0x18ebe982fd5a6f724837549c03f186438e90eee3

It represents an Opyn Option with

The confirmation of this can be found analysing the source code in the related Smart Contract OToken.sol

        tokenSymbol = string(
            abi.encodePacked(
                "o",
                underlying,
                strike,
                "/",
                collateral,
                "-",
                _uintTo2Chars(day),
                monthSymbol,
                _uintTo2Chars(year),
                "-",
                displayStrikePrice,
                typeSymbol
            )
        );

It was created 60 days before its expiration so on 03 Mar 2021when ETHUSD was about 1560 so this put was essentially betting on ETH loosing >18% in 2 months

2. TX Analysis

This is its minting Tx

https://etherscan.io/tx/0x052a2a5cc5c43d2247ae238beb9bc0445d0850dd56cd53f4198cdc9d31e4aa66

            if (actionType == Actions.ActionType.OpenVault) {
                _openVault(Actions._parseOpenVaultArgs(action));
            } else if (actionType == Actions.ActionType.DepositLongOption) {
                _depositLong(Actions._parseDepositArgs(action));
            } else if (actionType == Actions.ActionType.WithdrawLongOption) {
                _withdrawLong(Actions._parseWithdrawArgs(action));
            } else if (actionType == Actions.ActionType.DepositCollateral) {
                _depositCollateral(Actions._parseDepositArgs(action));
            } else if (actionType == Actions.ActionType.WithdrawCollateral) {
                _withdrawCollateral(Actions._parseWithdrawArgs(action));
            } else if (actionType == Actions.ActionType.MintShortOption) {
                _mintOtoken(Actions._parseMintArgs(action));
            } else if (actionType == Actions.ActionType.BurnShortOption) {
                _burnOtoken(Actions._parseBurnArgs(action));
            } else if (actionType == Actions.ActionType.Redeem) {
                _redeem(Actions._parseRedeemArgs(action));
            } else if (actionType == Actions.ActionType.SettleVault) {
                _settleVault(Actions._parseSettleVaultArgs(action));
            } else if (actionType == Actions.ActionType.Call) {
                _call(Actions._parseCallArgs(action));
            }

2.1 Deposit Collateral

The related action is Actions.ActionType.DepositCollateral which calls here the _depositCollateral

onlyAuthorized(msg.sender, _args.owner)

so only a set of authorized users can call this method

2.1.1 Checks

2.1.1.1 VaultID Validity and Accessibility for _args.owner

        require(_checkVaultId(_args.owner, _args.vaultId), "Controller: invalid vault id");

2.1.1.2 Being allowed to deposit collateral

        require(
            (_args.from == msg.sender) || (_args.from == _args.owner),
            "Controller: cannot deposit collateral from this address"
        );

2.1.1.3 Whitelisted Collateral

        require(
            whitelist.isWhitelistedCollateral(_args.asset),
            "Controller: asset is not whitelisted to be used as collateral"
        );

2.1.2 Changes

2.1.2.1 Increasing the collateral balance in a Vault

        vaults[_args.owner][_args.vaultId].addCollateral(_args.asset, _args.amount, _args.index);

The addCollateral() function interface is defined as follows

    function addCollateral(
        Vault storage _vault,
        address _collateralAsset,
        uint256 _amount,
        uint256 _index
    )

NOTES

            require(
                (_vault.collateralAssets[_index] == _collateralAsset) ||
                    (_vault.collateralAssets[_index] == address(0)),
                "MarginVault: collateral token address mismatch"
            );

So ultimately the changes made here by this function are

            _vault.collateralAmounts[_index] = _vault.collateralAmounts[_index].add(_amount);
            _vault.collateralAssets[_index] = _collateralAsset;

2.1.2.2 Transferring the collateral

        pool.transferToPool(_args.asset, _args.from, _args.amount);

The transferToPool() function interface is defined as follows

function transferToPool(
        address _asset,
        address _user,
        uint256 _amount
    )

After a basic sanity check on _amount > 0 shown here

        require(_amount > 0, "MarginPool: transferToPool amount is equal to 0");

what happens is

        assetBalance[_asset] = assetBalance[_asset].add(_amount);
        ERC20Interface(_asset).safeTransferFrom(_user, address(this), _amount);

2.1.3 Summary

At this point the situation is

pool = MarginPoolInterface(addressbook.getMarginPool());

so the address of the related smart contract is taken from the addressbook which is set at deployment time

2.2 Minting the OToken

The args _mintOtken() requires are defined in the mintArgs struct

    struct MintArgs {
        // address of the account owner
        address owner;
        // index of the vault from which the asset will be minted
        uint256 vaultId;
        // address to which we transfer the minted oTokens
        address to;
        // oToken that is to be minted
        address otoken;
        // each vault can hold multiple short / long / collateral assets but we are restricting the scope to only 1 of each in this version
        // in future versions this would be the index of the short / long / collateral asset that needs to be modified
        uint256 index;
        // amount of oTokens that is to be minted
        uint256 amount;
    }

NOTE

2.2.1 Checks

2.2.1.1 Authorization

onlyAuthorized(msg.sender, _args.owner)

only a set of authorized users can call this method

2.2.1.2 Sanity check on the Vault

require(_checkVaultId(_args.owner, _args.vaultId), "Controller: invalid vault id");

2.2.1.3 Otoken Whitelist Check

require(whitelist.isWhitelistedOtoken(_args.otoken), "Controller: otoken is not whitelisted to be minted");

2.2.1.4 OToken Expiry Sanity Check

        OtokenInterface otoken = OtokenInterface(_args.otoken);

        require(now < otoken.expiryTimestamp(), "Controller: can not mint expired otoken");

2.2.2 Changes

2.2.2.1 Tracking the number of Short OTokens

        vaults[_args.owner][_args.vaultId].addShort(_args.otoken, _args.amount, _args.index);
    function addShort(
        Vault storage _vault,
        address _shortOtoken,
        uint256 _amount,
        uint256 _index
    )

2.2.2.2 Actual OToken Minting

        otoken.mintOtoken(_args.to, _args.amount);
OtokenInterface otoken = OtokenInterface(_args.otoken);

Appunto