ETH Price: $2,241.46 (-0.83%)

Transaction Decoder

Block:
21122973 at Nov-05-2024 05:34:59 PM +UTC
Transaction Fee:
0.000664273848435256 ETH $1.49
Gas Used:
54,232 Gas / 12.248743333 Gwei

Emitted Events:

Account State Difference:

  Address   Before After State Difference Code
0x3c5Aac01...D67557fe2
0.062749290017384857 Eth
Nonce: 2165
0.062085016168949601 Eth
Nonce: 2166
0.000664273848435256
(beaverbuild)
7.567916290470491995 Eth7.567920074185936987 Eth0.000003783715444992
0xAD038Eb6...35dC5D710

Execution Trace

DolaBorrowingRights.transfer( to=0xB9Ed955fc3A389229A1873F631669a40c345F5F4, amount=6250000000000000000000 ) => ( True )
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

/**
@title Dola Borrow Rights
@notice The DolaBorrowRights contract is a non-standard ERC20 token, that gives the right of holders to borrow DOLA at 0% interest.
 As a borrower takes on DOLA debt, their DBR balance will be exhausted at 1 DBR per 1 DOLA borrowed per year.
*/
contract DolaBorrowingRights {

    string public name;
    string public symbol;
    uint8 public constant decimals = 18;
    uint256 public _totalSupply;
    address public operator;
    address public pendingOperator;
    uint public totalDueTokensAccrued;
    uint public replenishmentPriceBps;
    mapping(address => uint256) public balances;
    mapping(address => mapping(address => uint256)) public allowance;
    uint256 internal immutable INITIAL_CHAIN_ID;
    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
    mapping(address => uint256) public nonces;
    mapping (address => bool) public minters;
    mapping (address => bool) public markets;
    mapping (address => uint) public debts; // user => debt across all tracked markets
    mapping (address => uint) public dueTokensAccrued; // user => amount of due tokens accrued
    mapping (address => uint) public lastUpdated; // user => last update timestamp

    constructor(
        uint _replenishmentPriceBps,
        string memory _name,
        string memory _symbol,
        address _operator
    ) {
        replenishmentPriceBps = _replenishmentPriceBps;
        name = _name;
        symbol = _symbol;
        operator = _operator;
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    modifier onlyOperator {
        require(msg.sender == operator, "ONLY OPERATOR");
        _;
    }
    
    /**
    @notice Sets pending operator of the contract. Operator role must be claimed by the new oprator. Only callable by Operator.
    @param newOperator_ The address of the newOperator
    */
    function setPendingOperator(address newOperator_) public onlyOperator {
        pendingOperator = newOperator_;
    }

    /**
    @notice Sets the replenishment price in basis points. Replenishment price denotes the increase in DOLA debt upon forced replenishments.
     At 10000, the cost of replenishing 1 DBR is 1 DOLA in debt. Only callable by Operator.
    @param newReplenishmentPriceBps_ The new replen
    */
    function setReplenishmentPriceBps(uint newReplenishmentPriceBps_) public onlyOperator {
        require(newReplenishmentPriceBps_ > 0, "replenishment price must be over 0");
        require(newReplenishmentPriceBps_ <= 1_000_000, "Replenishment price cannot exceed 100 DOLA per DBR");
        replenishmentPriceBps = newReplenishmentPriceBps_;
    }
    
    /**
    @notice claims the Operator role if set as pending operator.
    */
    function claimOperator() public {
        require(msg.sender == pendingOperator, "ONLY PENDING OPERATOR");
        operator = pendingOperator;
        pendingOperator = address(0);
        emit ChangeOperator(operator);
    }

    /**
    @notice Add a minter to the set of addresses allowed to mint DBR tokens. Only callable by Operator.
    @param minter_ The address of the new minter.
    */
    function addMinter(address minter_) public onlyOperator {
        minters[minter_] = true;
        emit AddMinter(minter_);
    }

    /**
    @notice Removes a minter from the set of addresses allowe to mint DBR tokens. Only callable by Operator.
    @param minter_ The address to be removed from the minter set.
    */
    function removeMinter(address minter_) public onlyOperator {
        minters[minter_] = false;
        emit RemoveMinter(minter_);
    }
    /**
    @notice Adds a market to the set of active markets. Only callable by Operator.
    @dev markets can be added but cannot be removed. A removed market would result in unrepayable debt for some users.
    @param market_ The address of the new market contract to be added.
    */
    function addMarket(address market_) public onlyOperator {
        markets[market_] = true;
        emit AddMarket(market_);
    }

    /**
    @notice Get the total supply of DBR tokens.
    @dev The total supply is calculated as the difference between total DBR minted and total DBR accrued.
    @return uint representing the total supply of DBR.
    */
    function totalSupply() public view returns (uint) {
        if(totalDueTokensAccrued > _totalSupply) return 0;
        return _totalSupply - totalDueTokensAccrued;
    }

    /**
    @notice Get the DBR balance of an address. Will return 0 if the user has zero DBR or a deficit.
    @dev The balance of a user is calculated as the difference between the user's balance and the user's accrued DBR debt + due DBR debt.
    @param user Address of the user.
    @return uint representing the balance of the user.
    */
    function balanceOf(address user) public view returns (uint) {
        uint debt = debts[user];
        uint accrued = (block.timestamp - lastUpdated[user]) * debt / 365 days;
        if(dueTokensAccrued[user] + accrued > balances[user]) return 0;
        return balances[user] - dueTokensAccrued[user] - accrued;
    }

    /**
    @notice Get the DBR deficit of an address. Will return 0 if th user has zero DBR or more.
    @dev The deficit of a user is calculated as the difference between the user's accrued DBR deb + due DBR debt and their balance.
    @param user Address of the user.
    @return uint representing the deficit of the user.
    */
    function deficitOf(address user) public view returns (uint) {
        uint debt = debts[user];
        uint accrued = (block.timestamp - lastUpdated[user]) * debt / 365 days;
        if(dueTokensAccrued[user] + accrued < balances[user]) return 0;
        return dueTokensAccrued[user] + accrued - balances[user];
    }
    
    /**
    @notice Get the signed DBR balance of an address.
    @dev This function will revert if a user has a balance of more than 2^255-1 DBR
    @param user Address of the user.
    @return Returns a signed int of the user's balance
    */
    function signedBalanceOf(address user) public view returns (int) {
        uint debt = debts[user];
        uint accrued = (block.timestamp - lastUpdated[user]) * debt / 365 days;
        return int(balances[user]) - int(dueTokensAccrued[user]) - int(accrued);
    }

    /**
    @notice Approves spender to spend amount of DBR on behalf of the message sender.
    @param spender Address of the spender to be approved
    @param amount Amount to be approved to spend
    @return Always returns true, will revert if not successful.
    */
    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    /**
    @notice Transfers amount to address to from message sender.
    @param to The address to transfer to
    @param amount The amount of DBR to transfer
    @return Always returns true, will revert if not successful.
    */
    function transfer(address to, uint256 amount) public virtual returns (bool) {
        require(balanceOf(msg.sender) >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        unchecked {
            balances[to] += amount;
        }
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    /**
    @notice Transfer amount of DBR  on behalf of address from to address to. Message sender must have a sufficient allowance from the from address.
    @dev Allowance is reduced by the amount transferred.
    @param from Address to transfer from.
    @param to Address to transfer to.
    @param amount Amount of DBR to transfer.
    @return Always returns true, will revert if not successful.
    */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender];
        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
        require(balanceOf(from) >= amount, "Insufficient balance");
        balances[from] -= amount;
        unchecked {
            balances[to] += amount;
        }
        emit Transfer(from, to, amount);
        return true;
    }

    /**
    @notice Permits an address to spend on behalf of another address via a signed message.
    @dev Can be bundled with a transferFrom call, to reduce transaction load on users.
    @param owner Address of the owner permitting the spending
    @param spender Address allowed to spend on behalf of owner.
    @param value Amount to be allowed to spend.
    @param deadline Timestamp after which the signed message is no longer valid.
    @param v The v param of the ECDSA signature
    @param r The r param of the ECDSA signature
    @param s The s param of the ECDSA signature
    */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );
            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
            allowance[recoveredAddress][spender] = value;
        }
        emit Approval(owner, spender, value);
    }

    /**
    @notice Function for invalidating the nonce of a signed message.
    */
    function invalidateNonce() public {
        nonces[msg.sender]++;
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /**
    @notice Accrue due DBR debt of user
    @dev DBR debt is accrued at a rate of 1 DBR per 1 DOLA of debt per year.
    @param user The address of the user to accrue DBR debt to.
    */
    function accrueDueTokens(address user) public {
        uint debt = debts[user];
        if(lastUpdated[user] == block.timestamp) return;
        uint accrued = (block.timestamp - lastUpdated[user]) * debt / 365 days;
        if(accrued > 0 || lastUpdated[user] == 0){
            dueTokensAccrued[user] += accrued;
            totalDueTokensAccrued += accrued;
            lastUpdated[user] = block.timestamp;
            emit Transfer(user, address(0), accrued);
        }
    }

    /**
    @notice Function to be called by markets when a borrow occurs.
    @dev Accrues due tokens on behalf of the user, before increasing their debt.
    @param user The address of the borrower
    @param additionalDebt The additional amount of DOLA the user is borrowing
    */
    function onBorrow(address user, uint additionalDebt) public {
        require(markets[msg.sender], "Only markets can call onBorrow");
        accrueDueTokens(user);
        require(deficitOf(user) == 0, "DBR Deficit");
        debts[user] += additionalDebt;
    }

    /**
    @notice Function to be called by markets when a repayment occurs.
    @dev Accrues due tokens on behalf of the user, before reducing their debt.
    @param user The address of the borrower having their debt repaid
    @param repaidDebt The amount of DOLA repaid
    */
    function onRepay(address user, uint repaidDebt) public {
        require(markets[msg.sender], "Only markets can call onRepay");
        accrueDueTokens(user);
        debts[user] -= repaidDebt;
    }

    /**
    @notice Function to be called by markets when a force replenish occurs. This function can only be called if the user has a DBR deficit.
    @dev Accrues due tokens on behalf of the user, before increasing their debt by the replenishment price and minting them new DBR.
    @param user The user to be force replenished.
    @param amount The amount of DBR the user will be force replenished.
    */
    function onForceReplenish(address user, address replenisher, uint amount, uint replenisherReward) public {
        require(markets[msg.sender], "Only markets can call onForceReplenish");
        uint deficit = deficitOf(user);
        require(deficit > 0, "No deficit");
        require(deficit >= amount, "Amount > deficit");
        uint replenishmentCost = amount * replenishmentPriceBps / 10000;
        accrueDueTokens(user);
        debts[user] += replenishmentCost;
        _mint(user, amount);
        emit ForceReplenish(user, replenisher, msg.sender, amount, replenishmentCost, replenisherReward);
    }

    /**
    @notice Function for burning DBR from message sender, reducing supply.
    @param amount Amount to be burned
    */
    function burn(uint amount) public {
        _burn(msg.sender, amount);
    }

    /**
    @notice Function for minting new DBR, increasing supply. Only callable by minters and the operator.
    @param to Address to mint DBR to.
    @param amount Amount of DBR to mint.
    */
    function mint(address to, uint amount) public {
        require(minters[msg.sender] == true || msg.sender == operator, "ONLY MINTERS OR OPERATOR");
        _mint(to, amount);
    }

    /**
    @notice Internal function for minting DBR.
    @param to Address to mint DBR to.
    @param amount Amount of DBR to mint.
    */
    function _mint(address to, uint256 amount) internal virtual {
        _totalSupply += amount;
        unchecked {
            balances[to] += amount;
        }
        emit Transfer(address(0), to, amount);
    }

    /**
    @notice Internal function for burning DBR.
    @param from Address to burn DBR from.
    @param amount Amount of DBR to be burned.
    */
    function _burn(address from, uint256 amount) internal virtual {
        require(balanceOf(from) >= amount, "Insufficient balance");
        balances[from] -= amount;
        unchecked {
            _totalSupply -= amount;
        }
        emit Transfer(from, address(0), amount);
    }

    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 amount);
    event AddMinter(address indexed minter);
    event RemoveMinter(address indexed minter);
    event AddMarket(address indexed market);
    event ChangeOperator(address indexed newOperator);
    event ForceReplenish(address indexed account, address indexed replenisher, address indexed market, uint deficit, uint replenishmentCost, uint replenisherReward);

}