Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x6d1eff1aFF1dc9978d851D09d9d15f2938Da7BD7
Balance 0 ETH
Nonce 1
Code Size 7747 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

7747 bytes
0x608060405234801561000f575f80fd5b50600436106101c6575f3560e01c80637c33c06b116100fe578063b26f9a091161009e578063dd1040451161006e578063dd104045146104c4578063de0e1435146104d7578063e4fc6b6d146104df578063eb19f621146104e7575f80fd5b8063b26f9a0914610473578063c4d7a3a114610483578063cfc6dee81461049a578063d94a42d3146104b1575f80fd5b80639c22b9a6116100d95780639c22b9a61461037a578063a8602fea14610436578063ab4d602314610449578063ac21d47a14610460575f80fd5b80637c33c06b1461032f57806386e0c633146103455780639ac2a01114610358575f80fd5b80631e1bff3f11610169578063516c731c11610144578063516c731c146102df57806353ba352c146102f25780635d84969e14610305578063790201941461031c575f80fd5b80631e1bff3f146102a25780632b66d78a146102b55780634626402b146102cc575f80fd5b806308a199da116101a457806308a199da1461023b5780630ee56f781461025057806318ff318d146102635780631a75386614610276575f80fd5b80630134aeba146101ca578063022914a7146101e8578063062287491461021a575b5f80fd5b6101d26104fa565b6040516101df9190611ae1565b60405180910390f35b61020a6101f6366004611b9d565b5f6020819052908152604090205460ff1681565b60405190151581526020016101df565b61022361dead81565b6040516001600160a01b0390911681526020016101df565b61024e610249366004611b9d565b6105fd565b005b600754610223906001600160a01b031681565b600454610223906001600160a01b031681565b60095461028d90600160201b900463ffffffff1681565b60405163ffffffff90911681526020016101df565b61024e6102b0366004611bcd565b610737565b60085461028d90600160c01b900463ffffffff1681565b600854610223906001600160a01b031681565b61024e6102ed366004611bcd565b61076f565b600354610223906001600160a01b031681565b60085461028d90600160a01b900463ffffffff1681565b61024e61032a366004611c02565b6107a7565b6103376108ae565b6040516101df929190611c2a565b600654610223906001600160a01b031681565b61020a610366366004611b9d565b60016020525f908152604090205460ff1681565b600a54600b54600c54600d54600e54600f546010546011546012546103cc9860ff16979695949392919063ffffffff80821691600160201b8104821691600160401b8204811691600160601b9004168c565b604080519c15158d5260208d019b909b52998b019890985260608a0196909652608089019490945260a088019290925260c087015260e086015263ffffffff90811661010086015290811661012085015290811661014084015216610160820152610180016101df565b61024e610444366004611b9d565b6109c7565b60095461028d90600160401b900463ffffffff1681565b600254610223906001600160a01b031681565b60095461028d9063ffffffff1681565b60085461028d90600160e01b900463ffffffff1681565b60095461028d90600160601b900463ffffffff1681565b600554610223906001600160a01b031681565b61024e6104d2366004611c93565b610a6d565b61024e610b5d565b61024e610c48565b61024e6104f5366004611b9d565b611145565b61056c6040518061018001604052805f151581526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f81526020015f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681525090565b506040805161018081018252600a5460ff1615158152600b546020820152600c5491810191909152600d546060820152600e546080820152600f5460a082015260105460c082015260115460e082015260125463ffffffff808216610100840152600160201b82048116610120840152600160401b82048116610140840152600160601b9091041661016082015290565b335f9081526020819052604090205460ff166106345760405162461bcd60e51b815260040161062b90611cd3565b60405180910390fd5b600280546001600160a01b0319166001600160a01b0383811691821790925560035460405163095ea7b360e01b815260048101929092525f60248301529091169063095ea7b3906044016020604051808303815f875af115801561069a573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106be9190611cf9565b5060035460405163095ea7b360e01b81526001600160a01b0383811660048301525f1960248301529091169063095ea7b3906044016020604051808303815f875af115801561070f573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107339190611cf9565b5050565b335f9081526020819052604090205460ff166107655760405162461bcd60e51b815260040161062b90611cd3565b6107338282611671565b335f9081526020819052604090205460ff1661079d5760405162461bcd60e51b815260040161062b90611cd3565b61073382826116d0565b335f9081526020819052604090205460ff166107d55760405162461bcd60e51b815260040161062b90611cd3565b600a5460ff161561083b5760405162461bcd60e51b815260206004820152602a60248201527f43616e6e6f74207265636f76657220647572696e672070656e64696e6720646960448201526939ba3934b13aba34b7b760b11b606482015260840161062b565b60405163a9059cbb60e01b8152336004820152602481018290526001600160a01b0383169063a9059cbb906044016020604051808303815f875af1158015610885573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108a99190611cf9565b505050565b600a545f9060609060ff166108f15750506040805180820190915260178152762737903832b73234b733903234b9ba3934b13aba34b7b760491b60208201525f91565b600480546040516370a0823160e01b815230928101929092525f916001600160a01b03909116906370a0823190602401602060405180830381865afa15801561093c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109609190611d14565b600f54909150811015610990575f6040518060600160405280602b8152602001611de3602b913992509250509091565b50506040805180820190915260168152752932b0b23c903337b9103234b9ba3934b13aba34b7b760511b6020820152600192909150565b335f9081526020819052604090205460ff166109f55760405162461bcd60e51b815260040161062b90611cd3565b6001600160a01b038116610a4b5760405162461bcd60e51b815260206004820152601760248201527f496e76616c69642074726561737572792077616c6c6574000000000000000000604482015260640161062b565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b335f9081526020819052604090205460ff16610a9b5760405162461bcd60e51b815260040161062b90611cd3565b80610aa68385611d3f565b610ab09190611d3f565b63ffffffff166298968014610b075760405162461bcd60e51b815260206004820181905260248201527f536861726573206d7573742061646420757020746f2031305f3030305f303030604482015260640161062b565b6008805467ffffffffffffffff60a01b1916600160a01b63ffffffff9586160263ffffffff60c01b191617600160c01b93851693909302929092176001600160e01b0316600160e01b9190931602919091179055565b335f9081526020819052604090205460ff16610b8b5760405162461bcd60e51b815260040161062b90611cd3565b600a5460ff16610bd75760405162461bcd60e51b81526020600482015260176024820152762737903832b73234b733903234b9ba3934b13aba34b7b760491b604482015260640161062b565b6040514281527f18f688726e6ca19a212dbb115f67c79ad15f8ffefcd4eac589e69d81686966759060200160405180910390a1600a805460ff191690555f600b819055600c819055600d819055600e819055600f8190556010819055601155601280546001600160801b0319169055565b335f9081526001602052604090205460ff16610c765760405162461bcd60e51b815260040161062b90611cd3565b600a5460ff16610cc25760405162461bcd60e51b81526020600482015260176024820152762737903832b73234b733903234b9ba3934b13aba34b7b760491b604482015260640161062b565b6040805161018081018252600a5460ff1615158152600b546020820152600c5481830152600d546060820152600e546080820152600f5460a082015260105460c082015260115460e082015260125463ffffffff808216610100840152600160201b82048116610120840152600160401b82048116610140840152600160601b909104166101608201526004805492516370a0823160e01b8152309181019190915290915f916001600160a01b03909116906370a0823190602401602060405180830381865afa158015610d98573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610dbc9190611d14565b604083015190915015610e4757600354600854604084810151905163a9059cbb60e01b81526001600160a01b039283166004820152602481019190915291169063a9059cbb906044016020604051808303815f875af1158015610e21573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e459190611cf9565b505b606082015115610eb65760055460608301516040516345efb3f960e11b81526001600160a01b0390921691638bdf67f291610e889160040190815260200190565b5f604051808303815f87803b158015610e9f575f80fd5b505af1158015610eb1573d5f803e3d5ffd5b505050505b608082015115610f255760065460808301516040516345efb3f960e11b81526001600160a01b0390921691638bdf67f291610ef79160040190815260200190565b5f604051808303815f87803b158015610f0e575f80fd5b505af1158015610f20573d5f803e3d5ffd5b505050505b5f80821561108e575f8460c001518560e001518660a00151610f479190611d63565b610f519190611d63565b90508015610f7757808560c0015185610f6a9190611d7c565b610f749190611d93565b92505b610f818385611db2565b91505f83118015610f90575060015b1561100c576004805460405163a9059cbb60e01b815261dead92810192909252602482018590526001600160a01b03169063a9059cbb906044016020604051808303815f875af1158015610fe6573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061100a9190611cf9565b505b811561108c576004805460075460405163a9059cbb60e01b81526001600160a01b039182169381019390935260248301859052169063a9059cbb906044016020604051808303815f875af1158015611066573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061108a9190611cf9565b505b505b600a805460ff191690555f600b819055600c819055600d819055600e819055600f8190556010819055601155601280546001600160801b03191690556020848101516040808701516060808901516080808b015160e0808d01518751988952988801959095528686019290925291850152830185905260a083019390935260c08201859052517ff12270c3c7c203ebeb07ec60da15451b9b044fe6e4c01d153ad66c087c389c02929181900390910190a150505050565b335f9081526001602052604090205460ff166111735760405162461bcd60e51b815260040161062b90611cd3565b600a5460ff16156111c65760405162461bcd60e51b815260206004820152601c60248201527f446973747269627574696f6e20616c72656164792070656e64696e6700000000604482015260640161062b565b6003546040516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa15801561120c573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906112309190611d14565b905061123a611725565b60085462989680905f90829061125d90600160a01b900463ffffffff1685611d7c565b6112679190611d93565b6009549091505f9063ffffffff80851691611283911686611d7c565b61128d9190611d93565b6009549091505f9063ffffffff808616916112b191600160201b9091041687611d7c565b6112bb9190611d93565b6009549091505f9063ffffffff808716916112df91600160401b9091041688611d7c565b6112e99190611d93565b6009549091505f9063ffffffff8088169161130d91600160601b9091041689611d7c565b6113179190611d93565b6008549091505f9063ffffffff8089169161133b91600160e01b909104168a611d7c565b6113459190611d93565b905060405180610180016040528060011515815260200189815260200187815260200186815260200185815260200184815260200182815260200183815260200160095f9054906101000a900463ffffffff1663ffffffff168152602001600960049054906101000a900463ffffffff1663ffffffff168152602001600960089054906101000a900463ffffffff1663ffffffff1681526020016009600c9054906101000a900463ffffffff1663ffffffff16815250600a5f820151815f015f6101000a81548160ff0219169083151502179055506020820151816001015560408201518160020155606082015181600301556080820151816004015560a0820151816005015560c0820151816006015560e08201518160070155610100820151816008015f6101000a81548163ffffffff021916908363ffffffff1602179055506101208201518160080160046101000a81548163ffffffff021916908363ffffffff1602179055506101408201518160080160086101000a81548163ffffffff021916908363ffffffff16021790555061016082015181600801600c6101000a81548163ffffffff021916908363ffffffff1602179055509050505f8183856115109190611d63565b61151a9190611d63565b11156115e3576002546003546001600160a01b03918216916344bc937b915f918d911685611548888a611d63565b6115529190611d63565b6040516001600160e01b031960e087901b1681526001600160a01b039384166004820152929091166024830152604482015260a06064820152600f60a48201526e03d3a723a743a302f312f303a743a3608c1b60c48201525f19608482015260e4015f604051808303818588803b1580156115cb575f80fd5b505af11580156115dd573d5f803e3d5ffd5b50505050505b600954604080518a8152602081018590528082018690526060810184905263ffffffff8084166080830152600160201b8404811660a0830152600160401b8404811660c0830152600160601b90930490921660e0830152517f325679693ae06d37f48da0ac587bef07fd528790bb5df75437270a11802a1cc4918190036101000190a1505050505050505050565b6001600160a01b0382165f81815260016020908152604091829020805460ff191685151590811790915591519182527f278b09622564dd3991fe7744514513d64ea2c8ed2b2b9ec1150ad964fde80a9991015b60405180910390a25050565b6001600160a01b0382165f8181526020818152604091829020805460ff191685151590811790915591519182527ff74826f11048fa8ecf33e91132bf280f6582ed97548a84e426b56e98526b931691016116c4565b6004546001600160a01b03166117725760405162461bcd60e51b8152602060048201526012602482015271151213d4881d1bdad95b881b9bdd081cd95d60721b604482015260640161062b565b600480546005546040516370a0823160e01b81526001600160a01b03918216938101939093525f929116906370a0823190602401602060405180830381865afa1580156117c1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117e59190611d14565b600480546007546040516370a0823160e01b81526001600160a01b03918216938101939093529293505f9216906370a0823190602401602060405180830381865afa158015611836573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061185a9190611d14565b600480546006546040516370a0823160e01b81526001600160a01b03918216938101939093529293505f9216906370a0823190602401602060405180830381865afa1580156118ab573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118cf9190611d14565b600480546002546040516370a0823160e01b81526001600160a01b03918216938101939093529293505f9216906370a0823190602401602060405180830381865afa158015611920573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906119449190611d14565b90505f81846119538588611d63565b61195d9190611d63565b6119679190611d63565b90505f81116119b15760405162461bcd60e51b8152602060048201526016602482015275139bc8151213d48818985b185b98d95cc8199bdd5b9960521b604482015260640161062b565b60085481906119cd90600160c01b900463ffffffff1687611d7c565b6119d79190611d93565b6009805463ffffffff191663ffffffff9283161790556008548291611a0491600160c01b90041685611d7c565b611a0e9190611d93565b6009805467ffffffff000000001916600160201b63ffffffff938416021790556008548291611a4591600160c01b90041686611d7c565b611a4f9190611d93565b600980546bffffffff0000000000000000198116600160401b63ffffffff9485168102918217938490558304841693611a9693600160201b90048116928116911617611d3f565b611aa09190611d3f565b600854611aba9190600160c01b900463ffffffff16611dc5565b6009600c6101000a81548163ffffffff021916908363ffffffff1602179055505050505050565b81511515815261018081016020830151602083015260408301516040830152606083015160608301526080830151608083015260a083015160a083015260c083015160c083015260e083015160e083015261010080840151611b4a8285018263ffffffff169052565b50506101208381015163ffffffff90811691840191909152610140808501518216908401526101609384015116929091019190915290565b80356001600160a01b0381168114611b98575f80fd5b919050565b5f60208284031215611bad575f80fd5b611bb682611b82565b9392505050565b8015158114611bca575f80fd5b50565b5f8060408385031215611bde575f80fd5b611be783611b82565b91506020830135611bf781611bbd565b809150509250929050565b5f8060408385031215611c13575f80fd5b611c1c83611b82565b946020939093013593505050565b82151581525f60206040602084015283518060408501525f5b81811015611c5f57858101830151858201606001528201611c43565b505f606082860101526060601f19601f830116850101925050509392505050565b803563ffffffff81168114611b98575f80fd5b5f805f60608486031215611ca5575f80fd5b611cae84611c80565b9250611cbc60208501611c80565b9150611cca60408501611c80565b90509250925092565b6020808252600c908201526b155b985d5d1a1bdc9a5e995960a21b604082015260600190565b5f60208284031215611d09575f80fd5b8151611bb681611bbd565b5f60208284031215611d24575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b63ffffffff818116838216019080821115611d5c57611d5c611d2b565b5092915050565b80820180821115611d7657611d76611d2b565b92915050565b8082028115828204841417611d7657611d76611d2b565b5f82611dad57634e487b7160e01b5f52601260045260245ffd5b500490565b81810381811115611d7657611d76611d2b565b63ffffffff828116828216039080821115611d5c57611d5c611d2b56fe496e73756666696369656e742054484f522062616c616e636520666f72207654686f722072657761726473a2646970667358221220af38a7cf2c2c6e43aed12dbf2f38864bc1a8fc12109bb58183428ecbca8c0f1764736f6c63430008160033

Verified Source Code Full Match

Compiler: v0.8.22+commit.4fc1097e EVM: shanghai Optimization: Yes (200 runs)
TSFeeDistributor_V5.sol 384 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Owners} from "../../lib/Owners.sol";
import {Executors} from "../../lib/Executors.sol";
import {SafeTransferLib} from "../../lib/SafeTransferLib.sol";
import {IERC20} from "../../interfaces/IERC20.sol";
import {IThorchainRouterV4} from "../../interfaces/IThorchainRouterV4.sol";

// Minimal interface to call depositRewards on uThor/yThor:
interface IRewardsReceiver {
    function depositRewards(uint256 amount) external;
}

/**
 * @title TSFeeDistributor_V5
 * @notice Two-phase distribution system to prevent double-dipping vulnerability
 * @dev Phase 1: swapToRune() prepares cross-chain swaps and stores distribution amounts
 *      Phase 2: distribute() executes atomic distribution using stored amounts
 */
contract TSFeeDistributor_V5 is Owners, Executors {
    using SafeTransferLib for address;

    // ------------------------------------------------------
    // External Contracts
    // ------------------------------------------------------
    IThorchainRouterV4 public tcRouter;
    IERC20 public feeAsset; // e.g., USDC
    IERC20 public thorToken; // THOR token, if needed for BPS calc
    IERC20 public uThorToken;
    IERC20 public yThorToken;
    IERC20 public vThorToken;
    address public treasuryWallet;
    address public constant burnWallet = 0x000000000000000000000000000000000000dEaD;

    // ------------------------------------------------------
    // Config
    // ------------------------------------------------------
    // "treasuryPreciseBps + communityPreciseBps + burnPreciseBps = 10000000" (100%).
    uint32 public treasuryPreciseBps; // e.g. 2500bps = 2_500_000[1000bps] = 25%
    uint32 public communityPreciseBps; // e.g. 5500bps = 5_500_000[1000bps] = 55%
    uint32 public burnPreciseBps; // e.g. 2000bps = 2_000_000[1000bps] = 20%
    // Dynamic community splits (calculated per distribution)
    uint32 public uThorPreciseBps;
    uint32 public yThorPreciseBps;
    uint32 public vThorPreciseBps;
    uint32 public thorPoolPreciseBps;

    // ------------------------------------------------------
    // Memory Management for Two-Phase Distribution
    // ------------------------------------------------------
    struct PendingDistribution {
        bool isActive;              // Whether a distribution is pending
        uint256 totalAmount;        // Total USDC amount being distributed
        uint256 treasuryAmount;     // USDC for treasury
        uint256 uThorAmount;        // USDC for uThor
        uint256 yThorAmount;        // USDC for yThor
        uint256 vThorAmount;        // Expected THOR for vThor (converted from USDC)
        uint256 burnAmount;         // Expected THOR for burn (converted from USDC)
        uint256 thorPoolAmount;     // Expected RUNE for thorPool (converted from USDC)
        uint32 snapshotUThorBps;    // BPS at time of snapshot
        uint32 snapshotYThorBps;    // BPS at time of snapshot
        uint32 snapshotVThorBps;    // BPS at time of snapshot
        uint32 snapshotThorPoolBps; // BPS at time of snapshot
    }

    PendingDistribution public pendingDistribution;

    // ------------------------------------------------------
    // Events
    // ------------------------------------------------------
    event SwapToRuneInitiated(
        uint256 totalAmount,
        uint256 runeSwapAmount,
        uint256 thorSwapAmount,
        uint256 burnPortion,
        uint32 uThorBps,
        uint32 yThorBps,
        uint32 vThorBps,
        uint32 thorPoolBps
    );

    event Distribution(
        uint256 totalAmount,
        uint256 treasuryAmount,
        uint256 uThorAmount,
        uint256 yThorAmount,
        uint256 vThorAmount,
        uint256 thorPoolAmount,
        uint256 burnAmount
    );

    event PendingDistributionCancelled(uint256 timestamp);

    // ------------------------------------------------------
    // Constructor
    // ------------------------------------------------------
    constructor(
        address _tcRouterAddress,
        address _feeAsset,
        address _treasuryWallet,
        address _thorToken,
        address _uThorToken,
        address _vThorToken,
        address _yThorToken
    ) {
        // Initialize external references
        tcRouter = IThorchainRouterV4(_tcRouterAddress);
        feeAsset = IERC20(_feeAsset);
        thorToken = IERC20(_thorToken);
        uThorToken = IERC20(_uThorToken);
        vThorToken = IERC20(_vThorToken);
        yThorToken = IERC20(_yThorToken);

        // Default BPS (25% treasury / 55% community / 20% burn)
        treasuryPreciseBps = 2_500_000;
        communityPreciseBps = 5_500_000;
        burnPreciseBps = 2_000_000;

        // Approve Thorchain router to spend feeAsset
        feeAsset.approve(_tcRouterAddress, type(uint256).max);
        feeAsset.approve(_uThorToken, type(uint256).max);
        feeAsset.approve(_yThorToken, type(uint256).max);

        // Basic config
        treasuryWallet = _treasuryWallet;

        // Setup owners/executors
        _setOwner(msg.sender, true);
    }

    // ------------------------------------------------------
    // Owner Setters
    // ------------------------------------------------------
    function setTCRouter(address _tcRouterAddress) public isOwner {
        tcRouter = IThorchainRouterV4(_tcRouterAddress);
        feeAsset.approve(_tcRouterAddress, 0);
        feeAsset.approve(_tcRouterAddress, type(uint256).max);
    }

    /**
     * @notice Set the top-level treasury/community/burn BPS split. Must total 10_000_000
     */
    function setShares(
        uint32 _treasuryPreciseBps,
        uint32 _communityPreciseBps,
        uint32 _burnPreciseBps
    ) external isOwner {
        require(
            _treasuryPreciseBps + _communityPreciseBps + _burnPreciseBps  == 10_000_000,
            "Shares must add up to 10_000_000"
        );
        treasuryPreciseBps = _treasuryPreciseBps;
        communityPreciseBps = _communityPreciseBps;
        burnPreciseBps = _burnPreciseBps;
    }

    function setTreasuryWallet(address _treasuryWallet) external isOwner {
        require(_treasuryWallet != address(0), "Invalid treasury wallet");
        treasuryWallet = _treasuryWallet;
    }

    /**
     * @notice Cancel a pending distribution if it's expired or stuck
     * @dev Can only be called by owner, resets pending state
     */
    function cancelPendingDistribution() external isOwner {
        require(pendingDistribution.isActive, "No pending distribution");
        emit PendingDistributionCancelled(block.timestamp);
        delete pendingDistribution;
    }

    // ------------------------------------------------------
    // Internal Helper Functions
    // ------------------------------------------------------
    
    /**
     * @notice Calculate community splits based on current THOR token balances
     * @dev Updates the BPS allocation for each community component
     */
    function updateCommunitySplitsByTHORBalance() private {
        require(address(thorToken) != address(0), "THOR token not set");

        uint256 uBal = thorToken.balanceOf(address(uThorToken));
        uint256 vBal = thorToken.balanceOf(address(vThorToken));
        uint256 yBal = thorToken.balanceOf(address(yThorToken));
        uint256 pBal = thorToken.balanceOf(address(tcRouter));

        uint256 totalBalAssetAmount = (uBal + yBal + vBal + pBal);
        require(totalBalAssetAmount > 0, "No THOR balances found");

        uThorPreciseBps = uint32(
            ((uBal * communityPreciseBps) / totalBalAssetAmount)
        );
        yThorPreciseBps = uint32(
            ((yBal * communityPreciseBps) / totalBalAssetAmount)
        );
        vThorPreciseBps = uint32(
            ((vBal * communityPreciseBps) / totalBalAssetAmount)
        );
        thorPoolPreciseBps =
            communityPreciseBps -
            (uThorPreciseBps + yThorPreciseBps + vThorPreciseBps);
    }

    // ------------------------------------------------------
    // Phase 1: Swap feeAsset to RUNE
    // ------------------------------------------------------
    
    /**
     * @notice Phase 1: Initiate cross-chain swaps and store distribution parameters
     * @dev This function:
     *      1. Takes snapshot of current USDC balance and THOR allocations
     *      2. Calculates all distribution amounts
     *      3. Swaps required USDC to RUNE for LP donation
     *      4. Swaps required USDC to THOR for vThor rewards
     *      5. Stores all parameters for later atomic distribution
     * @param inboundAddress The Thorchain inbound address for cross-chain operations
     */
    function swapToRune(
        address inboundAddress
    ) external isExecutor {
        require(!pendingDistribution.isActive, "Distribution already pending");
        
        // 1. Check USDC balance
        uint256 balance = feeAsset.balanceOf(address(this));

        // 2. Update community splits based on current THOR balances
        updateCommunitySplitsByTHORBalance();

        uint32 preciseBps = 10_000_000;

        // 3. Calculate all distribution amounts in usdc
        uint256 treasuryAmount = (balance * treasuryPreciseBps) / preciseBps;
        uint256 uPortion = (balance * uThorPreciseBps) / preciseBps;
        uint256 yPortion = (balance * yThorPreciseBps) / preciseBps;
        uint256 vPortion = (balance * vThorPreciseBps) / preciseBps;
        uint256 poolPortion = (balance * thorPoolPreciseBps) / preciseBps;
        uint256 burnPortion = (balance * burnPreciseBps) / preciseBps;

        // 4. Store distribution parameters in memory
        pendingDistribution = PendingDistribution({
            isActive: true,
            totalAmount: balance,
            treasuryAmount: treasuryAmount,
            uThorAmount: uPortion,
            yThorAmount: yPortion,
            vThorAmount: vPortion,
            thorPoolAmount: poolPortion,
            burnAmount: burnPortion,
            snapshotUThorBps: uThorPreciseBps,
            snapshotYThorBps: yThorPreciseBps,
            snapshotVThorBps: vThorPreciseBps,
            snapshotThorPoolBps: thorPoolPreciseBps
        });

        // 5. Execute cross-chain swaps
        // Swap for RUNE, prepare for THOR distribution
        if (vPortion + poolPortion + burnPortion > 0) {
            tcRouter.depositWithExpiry{value: 0}(
                payable(inboundAddress),
                address(feeAsset),
                vPortion + poolPortion + burnPortion,
                "=:r:t:0/1/0:t:0",
                type(uint256).max
            );
        }

        emit SwapToRuneInitiated(
            balance,
            poolPortion,
            vPortion,
            burnPortion,
            uThorPreciseBps,
            yThorPreciseBps,
            vThorPreciseBps,
            thorPoolPreciseBps
        );
    }

    // ------------------------------------------------------
    // Phase 2: Atomic Distribution
    // ------------------------------------------------------
    
    /**
     * @notice Phase 2: Execute atomic distribution using stored parameters
     * @dev This function distributes all rewards simultaneously:
     *      - USDC to treasury
     *      - USDC to uThor and yThor via depositRewards()
     *      - THOR to vThor via depositRewards() (from cross-chain swap)
     */
    function distribute() external isExecutor {
        require(pendingDistribution.isActive, "No pending distribution");
        PendingDistribution memory dist = pendingDistribution;
        uint256 thorBalance = thorToken.balanceOf(address(this));

        // 1. Send treasury portion in USDC
        if (dist.treasuryAmount > 0) {
            feeAsset.transfer(treasuryWallet, dist.treasuryAmount);
        }

        // 2. Send USDC rewards to uThor and yThor simultaneously
        if (dist.uThorAmount > 0) {
            IRewardsReceiver(address(uThorToken)).depositRewards(dist.uThorAmount);
        }
        if (dist.yThorAmount > 0) {
            IRewardsReceiver(address(yThorToken)).depositRewards(dist.yThorAmount);
        }

        // 3. Split $thor balance for burn and vThor
        uint256 thorForBurn = 0;
        uint256 thorForVThor = 0;

        if (thorBalance > 0) {
            // Get total USDC to calc split (usdc 6 decimals)
            uint256 totalUsdc = dist.vThorAmount + dist.thorPoolAmount + dist.burnAmount;
            // Get $thor amount(18decimals) for burn based on usdc share split.
            if (totalUsdc > 0) {
                thorForBurn = (thorBalance * dist.burnAmount) / totalUsdc;
            }
            // Send rest to vThor (+dust)
            thorForVThor = thorBalance - thorForBurn;

            if(thorForBurn > 0 && burnWallet != address(0)){
                thorToken.transfer(address(burnWallet), thorForBurn);
            }
            if (thorForVThor > 0) {
            thorToken.transfer(address(vThorToken), thorForVThor);
            }

        }
        
        // 4. Clear pending distribution state
        delete pendingDistribution;

        emit Distribution(
            dist.totalAmount,
            dist.treasuryAmount,
            dist.uThorAmount,
            dist.yThorAmount,
            thorForVThor,
            dist.thorPoolAmount,
            thorForBurn
        );
    }

    // ------------------------------------------------------
    // View Functions
    // ------------------------------------------------------
    
    /**
     * @notice Get current pending distribution details
     */
    function getPendingDistribution() external view returns (PendingDistribution memory) {
        return pendingDistribution;
    }

    /**
     * @notice Check if contract is ready for Phase 2 distribution
     * @dev Verifies THOR balance is sufficient for vThor rewards
     */
    function isReadyForDistribution() external view returns (bool ready, string memory reason) {
        if (!pendingDistribution.isActive) {
            return (false, "No pending distribution");
        }


        uint256 thorBalance = thorToken.balanceOf(address(this));
        if (thorBalance < pendingDistribution.vThorAmount) {
            return (false, "Insufficient THOR balance for vThor rewards");
        }

        return (true, "Ready for distribution");
    }

    /**
     * @notice Emergency function to recover stuck tokens
     * @dev Only callable by owner, only when no pending distribution
     */
    function emergencyRecoverToken(address token, uint256 amount) external isOwner {
        require(!pendingDistribution.isActive, "Cannot recover during pending distribution");
        IERC20(token).transfer(msg.sender, amount);
    }
}
IERC20.sol 28 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IERC20 {
    function decimals() external view returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(
        address recipient,
        uint256 amount
    ) external returns (bool);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
}
IThorchainRouterV4.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IThorchainRouterV4 {
    function depositWithExpiry(
        address payable vault,
        address asset,
        uint amount,
        string memory memo,
        uint expiration
    ) external payable;
}
Executors.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Owners} from "./Owners.sol";

abstract contract Executors is Owners {
    event ExecutorSet(address indexed executor, bool active);

    mapping(address => bool) public executors;

    modifier isExecutor() {
        require(executors[msg.sender], "Unauthorized");
        _;
    }

    function _setExecutor(address executor, bool active) internal virtual {
        executors[executor] = active;
        emit ExecutorSet(executor, active);
    }

    function setExecutor(address owner, bool active) external virtual isOwner {
        _setExecutor(owner, active);
    }
}
Owners.sol 22 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

abstract contract Owners {
    event OwnerSet(address indexed owner, bool active);

    mapping(address => bool) public owners;

    modifier isOwner() {
        require(owners[msg.sender], "Unauthorized");
        _;
    }

    function _setOwner(address owner, bool active) internal virtual {
        owners[owner] = active;
        emit OwnerSet(owner, active);
    }

    function setOwner(address owner, bool active) external virtual isOwner {
        _setOwner(owner, active);
    }
}
SafeTransferLib.sol 159 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
library SafeTransferLib {
    /*///////////////////////////////////////////////////////////////
                            ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            callStatus := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(callStatus, "ETH_TRANSFER_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                           ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0x23b872dd00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(from, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "from" argument.
            mstore(
                add(freeMemoryPointer, 36),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 100 because the calldata length is 4 + 32 * 3.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)
        }

        require(
            didLastOptionalReturnCallSucceed(callStatus),
            "TRANSFER_FROM_FAILED"
        );
    }

    function safeTransfer(address token, address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0xa9059cbb00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(
            didLastOptionalReturnCallSucceed(callStatus),
            "TRANSFER_FAILED"
        );
    }

    function safeApprove(address token, address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0x095ea7b300000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), "APPROVE_FAILED");
    }

    /*///////////////////////////////////////////////////////////////
                         INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function didLastOptionalReturnCallSucceed(
        bool callStatus
    ) private pure returns (bool success) {
        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }
}

Read Contract

burnPreciseBps 0xc4d7a3a1 → uint32
burnWallet 0x06228749 → address
communityPreciseBps 0x2b66d78a → uint32
executors 0x9ac2a011 → bool
feeAsset 0x53ba352c → address
getPendingDistribution 0x0134aeba → tuple
isReadyForDistribution 0x7c33c06b → bool, string
owners 0x022914a7 → bool
pendingDistribution 0x9c22b9a6 → bool, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint32, uint32, uint32, uint32
tcRouter 0xac21d47a → address
thorPoolPreciseBps 0xcfc6dee8 → uint32
thorToken 0x18ff318d → address
treasuryPreciseBps 0x5d84969e → uint32
treasuryWallet 0x4626402b → address
uThorPreciseBps 0xb26f9a09 → uint32
uThorToken 0xd94a42d3 → address
vThorPreciseBps 0xab4d6023 → uint32
vThorToken 0x0ee56f78 → address
yThorPreciseBps 0x1a753866 → uint32
yThorToken 0x86e0c633 → address

Write Contract 9 functions

These functions modify contract state and require a wallet transaction to execute.

cancelPendingDistribution 0xde0e1435
No parameters
distribute 0xe4fc6b6d
No parameters
emergencyRecoverToken 0x79020194
address token
uint256 amount
setExecutor 0x1e1bff3f
address owner
bool active
setOwner 0x516c731c
address owner
bool active
setShares 0xdd104045
uint32 _treasuryPreciseBps
uint32 _communityPreciseBps
uint32 _burnPreciseBps
setTCRouter 0x08a199da
address _tcRouterAddress
setTreasuryWallet 0xa8602fea
address _treasuryWallet
swapToRune 0xeb19f621
address inboundAddress

Token Balances (1)

View Transfers →
USDC 0

Recent Transactions

No transactions found for this address