ETH Price: $2,568.74 (+1.44%)

Transaction Decoder

Block:
17891848 at Aug-11-2023 01:09:35 PM +UTC
Transaction Fee:
0.002670806742199776 ETH $6.86
Gas Used:
106,632 Gas / 25.046953468 Gwei

Emitted Events:

250 RestrictedToken.Transfer( src=[Sender] 0x80acb3b55132c4fe408f8bcda583aaf98824e206, dst=Tranche, wad=3734408101716650788863 )
251 Operator.0x4a58ca0e00000000000000000000000000000000000000000000000000000000( 0x4a58ca0e00000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000080acb3b55132c4fe408f8bcda583aaf98824e206, 0x0000000000000000000000000000000000000000000000ca7152bec4e9ca87ff, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000024, 4a58ca0e0000000000000000000000000000000000000000000000ca7152bec4, e9ca87ff00000000000000000000000000000000000000000000000000000000 )

Account State Difference:

  Address   Before After State Difference Code
(Faith Builder)
4.071507858824841596 Eth4.071518522024841596 Eth0.0000106632
0x53CF3CCd...69B170f27
0x80Acb3b5...98824e206
0.594968989479996501 Eth
Nonce: 214
0.592298182737796725 Eth
Nonce: 215
0.002670806742199776
0x961e1d4c...66b62dBb1

Execution Trace

Operator.redeemOrder( amount=3734408101716650788863 )
  • RestrictedToken.hasMember( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206 ) => ( True )
    • Memberlist.hasMember( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206 ) => ( True )
    • RestrictedToken.hasMember( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206 ) => ( True )
      • Memberlist.hasMember( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206 ) => ( True )
      • Tranche.redeemOrder( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, newRedeemAmount=3734408101716650788863 )
        • 0x22a1caca2ee82e9ce7ef900fd961891b66deb7ca.STATICCALL( )
        • RestrictedToken.transferFrom( from=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, to=0x53CF3CCd97CA914F9e441B8cd9A901E69B170f27, wad=3734408101716650788863 ) => ( True )
          • Memberlist.member( usr=0x53CF3CCd97CA914F9e441B8cd9A901E69B170f27 )
            File 1 of 4: Operator
            // Verified using https://dapp.tools
            
            // hevm: flattened sources of src/lender/operator.sol
            pragma solidity >=0.5.15 >=0.5.15 <0.6.0;
            
            ////// lib/tinlake-auth/lib/ds-note/src/note.sol
            /// note.sol -- the `note' modifier, for logging calls as events
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU General Public License for more details.
            
            // You should have received a copy of the GNU General Public License
            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
            /* pragma solidity >=0.5.15; */
            
            contract DSNote {
                event LogNote(
                    bytes4   indexed  sig,
                    address  indexed  guy,
                    bytes32  indexed  foo,
                    bytes32  indexed  bar,
                    uint256           wad,
                    bytes             fax
                ) anonymous;
            
                modifier note {
                    bytes32 foo;
                    bytes32 bar;
                    uint256 wad;
            
                    assembly {
                        foo := calldataload(4)
                        bar := calldataload(36)
                        wad := callvalue()
                    }
            
                    _;
            
                    emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
                }
            }
            
            ////// lib/tinlake-auth/src/auth.sol
            // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            /* import "ds-note/note.sol"; */
            
            contract Auth is DSNote {
                mapping (address => uint) public wards;
                function rely(address usr) public auth note { wards[usr] = 1; }
                function deny(address usr) public auth note { wards[usr] = 0; }
                modifier auth { require(wards[msg.sender] == 1); _; }
            }
            
            ////// src/lender/operator.sol
            // Copyright (C) 2020 Centrifuge
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            /* import "ds-note/note.sol"; */
            /* import "tinlake-auth/auth.sol"; */
            
            contract TrancheLike_2 {
                function supplyOrder(address usr, uint currencyAmount) public;
                function redeemOrder(address usr, uint tokenAmount) public;
                function disburse(address usr) public returns (uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency,  uint remainingRedeemToken);
                function disburse(address usr, uint endEpoch) public returns (uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency,  uint remainingRedeemToken);
                function currency() public view returns (address);
            }
            
            interface RestrictedTokenLike {
                function hasMember(address) external view returns (bool);
            }
            
            interface EIP2612PermitLike {
                function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
            }
            
            interface DaiPermitLike {
                function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external;
            }
            
            contract Operator is DSNote, Auth {
                TrancheLike_2 public tranche;
                RestrictedTokenLike public token;
            
                constructor(address tranche_) public {
                    wards[msg.sender] = 1;
                    tranche = TrancheLike_2(tranche_);
                }
            
                /// sets the dependency to another contract
                function depend(bytes32 contractName, address addr) public auth {
                    if (contractName == "tranche") { tranche = TrancheLike_2(addr); }
                    else if (contractName == "token") { token = RestrictedTokenLike(addr); }
                    else revert();
                }
            
                /// only investors that are on the memberlist can submit supplyOrders
                function supplyOrder(uint amount) public note {
                    require((token.hasMember(msg.sender) == true), "user-not-allowed-to-hold-token");
                    tranche.supplyOrder(msg.sender, amount);
                }
            
                /// only investors that are on the memberlist can submit redeemOrders
                function redeemOrder(uint amount) public note {
                    require((token.hasMember(msg.sender) == true), "user-not-allowed-to-hold-token");
                    token.hasMember(msg.sender);
                    tranche.redeemOrder(msg.sender, amount);
                }
            
                /// only investors that are on the memberlist can disburse
                function disburse() external
                    returns(uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency,  uint remainingRedeemToken)
                {
                    require((token.hasMember(msg.sender) == true), "user-not-allowed-to-hold-token");
                    return tranche.disburse(msg.sender);
                }
            
                function disburse(uint endEpoch) external
                    returns(uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency,  uint remainingRedeemToken)
                {
                    require((token.hasMember(msg.sender) == true), "user-not-allowed-to-hold-token");
                    return tranche.disburse(msg.sender, endEpoch);
                }
            
                // --- Permit Support ---
                function supplyOrderWithDaiPermit(uint amount, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public {
                    DaiPermitLike(tranche.currency()).permit(msg.sender, address(tranche), nonce, expiry, true, v, r, s);
                    supplyOrder(amount);
                }
                function supplyOrderWithPermit(uint amount, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
                    EIP2612PermitLike(tranche.currency()).permit(msg.sender, address(tranche), value, deadline, v, r, s);
                    supplyOrder(amount);
                }
                function redeemOrderWithPermit(uint amount, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) public {
                    EIP2612PermitLike(address(token)).permit(msg.sender, address(tranche), value, deadline, v, r, s);
                    redeemOrder(amount);
                }
            }
            

            File 2 of 4: Tranche
            // Copyright (C) 2020 Centrifuge
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            pragma solidity >=0.5.15 <0.6.0;
            pragma experimental ABIEncoderV2;
            
            // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            pragma solidity >=0.5.15 <0.6.0;
            
            /// note.sol -- the `note' modifier, for logging calls as events
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU General Public License for more details.
            
            // You should have received a copy of the GNU General Public License
            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
            
            pragma solidity >=0.4.23;
            
            contract DSNote {
                event LogNote(
                    bytes4   indexed  sig,
                    address  indexed  guy,
                    bytes32  indexed  foo,
                    bytes32  indexed  bar,
                    uint256           wad,
                    bytes             fax
                ) anonymous;
            
                modifier note {
                    bytes32 foo;
                    bytes32 bar;
                    uint256 wad;
            
                    assembly {
                        foo := calldataload(4)
                        bar := calldataload(36)
                        wad := callvalue()
                    }
            
                    _;
            
                    emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
                }
            }
            
            
            contract Auth is DSNote {
                mapping (address => uint) public wards;
                function rely(address usr) public auth note { wards[usr] = 1; }
                function deny(address usr) public auth note { wards[usr] = 0; }
                modifier auth { require(wards[msg.sender] == 1); _; }
            }
            
            // Copyright (C) 2018 Rain <[email protected]>
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            pragma solidity >=0.5.15 <0.6.0;
            
            contract Math {
                uint256 constant ONE = 10 ** 27;
            
                function safeAdd(uint x, uint y) public pure returns (uint z) {
                    require((z = x + y) >= x, "safe-add-failed");
                }
            
                function safeSub(uint x, uint y) public pure returns (uint z) {
                    require((z = x - y) <= x, "safe-sub-failed");
                }
            
                function safeMul(uint x, uint y) public pure returns (uint z) {
                    require(y == 0 || (z = x * y) / y == x, "safe-mul-failed");
                }
            
                function safeDiv(uint x, uint y) public pure returns (uint z) {
                    z = x / y;
                }
            
                function rmul(uint x, uint y) public pure returns (uint z) {
                    z = safeMul(x, y) / ONE;
                }
            
                function rdiv(uint x, uint y) public pure returns (uint z) {
                    require(y > 0, "division by zero");
                    z = safeAdd(safeMul(x, ONE), y / 2) / y;
                }
            
                function rdivup(uint x, uint y) internal pure returns (uint z) {
                    require(y > 0, "division by zero");
                    // always rounds up
                    z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y;
                }
            
            
            }
            
            // Copyright (C) 2020 Centrifuge
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            pragma solidity >=0.5.15 <0.6.0;
            
            contract FixedPoint {
                struct Fixed27 {
                    uint value;
                }
            }
            
            
            interface ERC20Like {
                function balanceOf(address) external view returns (uint);
                function transferFrom(address, address, uint) external returns (bool);
                function transfer(address to, uint amount) external returns (bool);
                function mint(address, uint) external;
                function burn(address, uint) external;
                function totalSupply() external view returns (uint);
                function approve(address usr, uint amount) external;
            }
            
            interface ReserveLike {
                function deposit(uint amount) external;
                function payout(uint amount) external;
                function totalBalanceAvailable() external returns (uint);
            }
            
            interface EpochTickerLike {
                function currentEpoch() external view returns (uint);
                function lastEpochExecuted() external view returns(uint);
            }
            
            contract Tranche is Math, Auth, FixedPoint {
                mapping(uint => Epoch) public epochs;
            
                struct Epoch {
                    // denominated in 10^27
                    // percentage ONE == 100%
                    Fixed27 redeemFulfillment;
                    // denominated in 10^27
                    // percentage ONE == 100%
                    Fixed27 supplyFulfillment;
                    // tokenPrice after end of epoch
                    Fixed27 tokenPrice;
                }
            
                struct UserOrder {
                    uint orderedInEpoch;
                    uint supplyCurrencyAmount;
                    uint redeemTokenAmount;
                }
            
                mapping(address => UserOrder) public users;
            
                uint public  totalSupply;
                uint public  totalRedeem;
            
                ERC20Like public currency;
                ERC20Like public token;
                ReserveLike public reserve;
                EpochTickerLike public epochTicker;
            
                // additional requested currency if the reserve could not fulfill a tranche request
                uint public requestedCurrency;
                address self;
            
                bool public waitingForUpdate = false;
            
                modifier orderAllowed(address usr) {
                    require((users[usr].supplyCurrencyAmount == 0 && users[usr].redeemTokenAmount == 0)
                    || users[usr].orderedInEpoch == epochTicker.currentEpoch(), "disburse required");
                    _;
                }
            
                constructor(address currency_, address token_) public {
                    wards[msg.sender] = 1;
                    token = ERC20Like(token_);
                    currency = ERC20Like(currency_);
                    self = address(this);
                }
            
                function balance() external view returns (uint) {
                    return currency.balanceOf(self);
                }
            
                function tokenSupply() external view returns (uint) {
                    return token.totalSupply();
                }
            
                function depend(bytes32 contractName, address addr) public auth {
                    if (contractName == "token") {token = ERC20Like(addr);}
                    else if (contractName == "currency") {currency = ERC20Like(addr);}
                    else if (contractName == "reserve") {reserve = ReserveLike(addr);}
                    else if (contractName == "epochTicker") {epochTicker = EpochTickerLike(addr);}
                    else revert();
                }
            
                // supplyOrder function can be used to place or revoke an supply
                function supplyOrder(address usr, uint newSupplyAmount) public auth orderAllowed(usr) {
                    users[usr].orderedInEpoch = epochTicker.currentEpoch();
            
                    uint currentSupplyAmount = users[usr].supplyCurrencyAmount;
            
                    users[usr].supplyCurrencyAmount = newSupplyAmount;
            
                    totalSupply = safeAdd(safeTotalSub(totalSupply, currentSupplyAmount), newSupplyAmount);
            
                    if (newSupplyAmount > currentSupplyAmount) {
                        uint delta = safeSub(newSupplyAmount, currentSupplyAmount);
                        require(currency.transferFrom(usr, self, delta), "currency-transfer-failed");
                        return;
                    }
                    uint delta = safeSub(currentSupplyAmount, newSupplyAmount);
                    if (delta > 0) {
                        _safeTransfer(currency, usr, delta);
                    }
                }
            
                // redeemOrder function can be used to place or revoke a redeem
                function redeemOrder(address usr, uint newRedeemAmount) public auth orderAllowed(usr) {
                    users[usr].orderedInEpoch = epochTicker.currentEpoch();
            
                    uint currentRedeemAmount = users[usr].redeemTokenAmount;
                    users[usr].redeemTokenAmount = newRedeemAmount;
                    totalRedeem = safeAdd(safeTotalSub(totalRedeem, currentRedeemAmount), newRedeemAmount);
            
                    if (newRedeemAmount > currentRedeemAmount) {
                        uint delta = safeSub(newRedeemAmount, currentRedeemAmount);
                        require(token.transferFrom(usr, self, delta), "token-transfer-failed");
                        return;
                    }
            
                    uint delta = safeSub(currentRedeemAmount, newRedeemAmount);
                    if (delta > 0) {
                        _safeTransfer(token, usr, delta);
                    }
                }
            
                function calcDisburse(address usr) public view returns(uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency, uint remainingRedeemToken) {
                    return calcDisburse(usr, epochTicker.lastEpochExecuted());
                }
            
                ///  calculates the current disburse of a user starting from the ordered epoch until endEpoch
                function calcDisburse(address usr, uint endEpoch) public view returns(uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency, uint remainingRedeemToken) {
                    uint epochIdx = users[usr].orderedInEpoch;
                    uint lastEpochExecuted = epochTicker.lastEpochExecuted();
            
                    // no disburse possible in this epoch
                    if (users[usr].orderedInEpoch == epochTicker.currentEpoch()) {
                        return (payoutCurrencyAmount, payoutTokenAmount, users[usr].supplyCurrencyAmount, users[usr].redeemTokenAmount);
                    }
            
                    if (endEpoch > lastEpochExecuted) {
                        // it is only possible to disburse epochs which are already over
                        endEpoch = lastEpochExecuted;
                    }
            
                    remainingSupplyCurrency = users[usr].supplyCurrencyAmount;
                    remainingRedeemToken = users[usr].redeemTokenAmount;
                    uint amount = 0;
            
                    // calculates disburse amounts as long as remaining tokens or currency is left or the end epoch is reached
                    while(epochIdx <= endEpoch && (remainingSupplyCurrency != 0 || remainingRedeemToken != 0 )){
                        if(remainingSupplyCurrency != 0) {
                            amount = rmul(remainingSupplyCurrency, epochs[epochIdx].supplyFulfillment.value);
                            // supply currency payout in token
                            if (amount != 0) {
                                payoutTokenAmount = safeAdd(payoutTokenAmount, safeDiv(safeMul(amount, ONE), epochs[epochIdx].tokenPrice.value));
                                remainingSupplyCurrency = safeSub(remainingSupplyCurrency, amount);
                            }
                        }
            
                        if(remainingRedeemToken != 0) {
                            amount = rmul(remainingRedeemToken, epochs[epochIdx].redeemFulfillment.value);
                            // redeem token payout in currency
                            if (amount != 0) {
                                payoutCurrencyAmount = safeAdd(payoutCurrencyAmount, rmul(amount, epochs[epochIdx].tokenPrice.value));
                                remainingRedeemToken = safeSub(remainingRedeemToken, amount);
                            }
                        }
                        epochIdx = safeAdd(epochIdx, 1);
                    }
            
                    return (payoutCurrencyAmount, payoutTokenAmount, remainingSupplyCurrency, remainingRedeemToken);
                }
            
                // the disburse function can be used after an epoch is over to receive currency and tokens
                function disburse(address usr) public auth returns (uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency, uint remainingRedeemToken) {
                    return disburse(usr, epochTicker.lastEpochExecuted());
                }
            
                function _safeTransfer(ERC20Like erc20, address usr, uint amount) internal returns(uint) {
                    uint max = erc20.balanceOf(self);
                    if(amount > max) {
                        amount = max;
                    }
                    require(erc20.transfer(usr, amount), "token-transfer-failed");
                    return amount;
                }
            
                // the disburse function can be used after an epoch is over to receive currency and tokens
                function disburse(address usr,  uint endEpoch) public auth returns (uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency, uint remainingRedeemToken) {
                    require(users[usr].orderedInEpoch <= epochTicker.lastEpochExecuted(), "epoch-not-executed-yet");
            
                    uint lastEpochExecuted = epochTicker.lastEpochExecuted();
            
                    if (endEpoch > lastEpochExecuted) {
                        // it is only possible to disburse epochs which are already over
                        endEpoch = lastEpochExecuted;
                    }
            
                    (payoutCurrencyAmount, payoutTokenAmount,
                    remainingSupplyCurrency, remainingRedeemToken) = calcDisburse(usr, endEpoch);
                    users[usr].supplyCurrencyAmount = remainingSupplyCurrency;
                    users[usr].redeemTokenAmount = remainingRedeemToken;
                    // if lastEpochExecuted is disbursed, orderInEpoch is at the current epoch again
                    // which allows to change the order. This is only possible if all previous epochs are disbursed
                    users[usr].orderedInEpoch = safeAdd(endEpoch, 1);
            
            
                    if (payoutCurrencyAmount > 0) {
                        payoutCurrencyAmount = _safeTransfer(currency, usr, payoutCurrencyAmount);
                    }
            
                    if (payoutTokenAmount > 0) {
                        payoutTokenAmount = _safeTransfer(token, usr, payoutTokenAmount);
                    }
                    return (payoutCurrencyAmount, payoutTokenAmount, remainingSupplyCurrency, remainingRedeemToken);
                }
            
            
                // called by epoch coordinator in epoch execute method
                function epochUpdate(uint epochID, uint supplyFulfillment_, uint redeemFulfillment_, uint tokenPrice_, uint epochSupplyOrderCurrency, uint epochRedeemOrderCurrency) public auth {
                    require(waitingForUpdate == true);
                    waitingForUpdate = false;
            
                    epochs[epochID].supplyFulfillment.value = supplyFulfillment_;
                    epochs[epochID].redeemFulfillment.value = redeemFulfillment_;
                    epochs[epochID].tokenPrice.value = tokenPrice_;
            
                    // currency needs to be converted to tokenAmount with current token price
                    uint redeemInToken = 0;
                    uint supplyInToken = 0;
                    if(tokenPrice_ > 0) {
                        supplyInToken = rdiv(epochSupplyOrderCurrency, tokenPrice_);
                        redeemInToken = safeDiv(safeMul(epochRedeemOrderCurrency, ONE), tokenPrice_);
                    }
            
                    // calculates the delta between supply and redeem for currency and deposit or get them from the reserve
                    adjustCurrencyBalance(epochID, epochSupplyOrderCurrency, epochRedeemOrderCurrency);
                    // calculates the delta between supply and redeem for tokens and burn or mint them
                    adjustTokenBalance(epochID, supplyInToken, redeemInToken);
            
                    // the unfulfilled orders (1-fulfillment) is automatically ordered
                    totalSupply = safeAdd(safeTotalSub(totalSupply, epochSupplyOrderCurrency), rmul(epochSupplyOrderCurrency, safeSub(ONE, epochs[epochID].supplyFulfillment.value)));
                    totalRedeem = safeAdd(safeTotalSub(totalRedeem, redeemInToken), rmul(redeemInToken, safeSub(ONE, epochs[epochID].redeemFulfillment.value)));
                }
                
                function closeEpoch() public auth returns (uint totalSupplyCurrency_, uint totalRedeemToken_) {
                    require(waitingForUpdate == false);
                    waitingForUpdate = true;
                    return (totalSupply, totalRedeem);
                }
            
                function safeBurn(uint tokenAmount) internal {
                    uint max = token.balanceOf(self);
                    if(tokenAmount > max) {
                        tokenAmount = max;
                    }
                    token.burn(self, tokenAmount);
                }
            
                function safePayout(uint currencyAmount) internal returns(uint payoutAmount) {
                    uint max = reserve.totalBalanceAvailable();
            
                    if(currencyAmount > max) {
                        // currently reserve can't fulfill the entire request
                        currencyAmount = max;
                    }
                    reserve.payout(currencyAmount);
                    return currencyAmount;
                }
            
                function payoutRequestedCurrency() public {
                    if(requestedCurrency > 0) {
                        uint payoutAmount = safePayout(requestedCurrency);
                        requestedCurrency = safeSub(requestedCurrency, payoutAmount);
                    }
                }
                // adjust token balance after epoch execution -> min/burn tokens
                function adjustTokenBalance(uint epochID, uint epochSupplyToken, uint epochRedeemToken) internal {
                    // mint token amount for supply
            
                    uint mintAmount = 0;
                    if (epochs[epochID].tokenPrice.value > 0) {
                        mintAmount = rmul(epochSupplyToken, epochs[epochID].supplyFulfillment.value);
                    }
            
                    // burn token amount for redeem
                    uint burnAmount = rmul(epochRedeemToken, epochs[epochID].redeemFulfillment.value);
                    // burn tokens that are not needed for disbursement
                    if (burnAmount > mintAmount) {
                        uint diff = safeSub(burnAmount, mintAmount);
                        safeBurn(diff);
                        return;
                    }
                    // mint tokens that are required for disbursement
                    uint diff = safeSub(mintAmount, burnAmount);
                    if (diff > 0) {
                        token.mint(self, diff);
                    }
                }
            
                // additional minting of tokens produces a dilution of all token holders
                // interface is required for adapters
                function mint(address usr, uint amount) public auth {
                    token.mint(usr, amount);
                }
            
                // adjust currency balance after epoch execution -> receive/send currency from/to reserve
                function adjustCurrencyBalance(uint epochID, uint epochSupply, uint epochRedeem) internal {
                    // currency that was supplied in this epoch
                    uint currencySupplied = rmul(epochSupply, epochs[epochID].supplyFulfillment.value);
                    // currency required for redemption
                    uint currencyRequired = rmul(epochRedeem, epochs[epochID].redeemFulfillment.value);
            
                    if (currencySupplied > currencyRequired) {
                        // send surplus currency to reserve
                        uint diff = safeSub(currencySupplied, currencyRequired);
                        currency.approve(address(reserve), diff);
                        reserve.deposit(diff);
                        return;
                    }
                    uint diff = safeSub(currencyRequired, currencySupplied);
                    if (diff > 0) {
                        // get missing currency from reserve
                        uint payoutAmount = safePayout(diff);
                        if(payoutAmount < diff) {
                            // reserve couldn't fulfill the entire request
                            requestedCurrency = safeAdd(requestedCurrency, safeSub(diff, payoutAmount));
                        }
                    }
                }
            
                // recovery transfer can be used by governance to recover funds if tokens are stuck
                function authTransfer(address erc20, address usr, uint amount) public auth {
                    ERC20Like(erc20).transfer(usr, amount);
                }
            
                // due to rounding in token & currency conversions currency & token balances might be off by 1 wei with the totalSupply/totalRedeem amounts.
                // in order to prevent an underflow error, 0 is returned when amount to be subtracted is bigger then the total value.
                function safeTotalSub(uint total, uint amount) internal returns (uint) {
                    if (total < amount) {
                        return 0;
                    }
                    return safeSub(total, amount);
                }
            }

            File 3 of 4: RestrictedToken
            // Verified using https://dapp.tools
            
            // hevm: flattened sources of src/lender/token/restricted.sol
            pragma solidity >=0.5.15 >=0.5.15 <0.6.0;
            
            ////// lib/tinlake-erc20/src/erc20.sol
            // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico, lucasvo
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15; */
            
            contract ERC20 {
                // --- Auth ---
                mapping (address => uint) public wards;
                function rely(address usr) public auth { wards[usr] = 1; }
                function deny(address usr) public auth { wards[usr] = 0; }
                modifier auth { require(wards[msg.sender] == 1); _; }
            
                // --- ERC20 Data ---
                uint8   public decimals = 18;
                string  public name;
                string  public symbol;
                string  public version;
                uint256 public totalSupply;
            
                bytes32 public DOMAIN_SEPARATOR;
                // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
                bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
                mapping(address => uint) public nonces;
            
                mapping (address => uint)                      public balanceOf;
                mapping (address => mapping (address => uint)) public allowance;
            
                event Approval(address indexed src, address indexed usr, uint wad);
                event Transfer(address indexed src, address indexed dst, uint wad);
            
                // --- Math ---
                function add(uint x, uint y) internal pure returns (uint z) {
                    require((z = x + y) >= x, "math-add-overflow");
                }
                function sub(uint x, uint y) internal pure returns (uint z) {
                    require((z = x - y) <= x, "math-sub-underflow");
                }
            
                constructor(string memory symbol_, string memory name_) public {
                    wards[msg.sender] = 1;
                    symbol = symbol_;
                    name = name_;
            
                    uint chainId;
                    assembly {
                        chainId := chainid()
                    }
                    DOMAIN_SEPARATOR = keccak256(
                        abi.encode(
                            keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                            keccak256(bytes(name)),
                            keccak256(bytes('1')),
                            chainId,
                            address(this)
                        )
                    );
                }
            
                // --- ERC20 ---
                function transfer(address dst, uint wad) external returns (bool) {
                    return transferFrom(msg.sender, dst, wad);
                }
                function transferFrom(address src, address dst, uint wad)
                    public returns (bool)
                {
                    require(balanceOf[src] >= wad, "cent/insufficient-balance");
                    if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
                        require(allowance[src][msg.sender] >= wad, "cent/insufficient-allowance");
                        allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
                    }
                    balanceOf[src] = sub(balanceOf[src], wad);
                    balanceOf[dst] = add(balanceOf[dst], wad);
                    emit Transfer(src, dst, wad);
                    return true;
                }
                function mint(address usr, uint wad) external auth {
                    balanceOf[usr] = add(balanceOf[usr], wad);
                    totalSupply    = add(totalSupply, wad);
                    emit Transfer(address(0), usr, wad);
                }
                function burn(address usr, uint wad) external {
                    require(balanceOf[usr] >= wad, "cent/insufficient-balance");
                    if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) {
                        require(allowance[usr][msg.sender] >= wad, "cent/insufficient-allowance");
                        allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad);
                    }
                    balanceOf[usr] = sub(balanceOf[usr], wad);
                    totalSupply    = sub(totalSupply, wad);
                    emit Transfer(usr, address(0), wad);
                }
                function approve(address usr, uint wad) external returns (bool) {
                    allowance[msg.sender][usr] = wad;
                    emit Approval(msg.sender, usr, wad);
                    return true;
                }
            
                // --- Alias ---
                function push(address usr, uint wad) external {
                    transferFrom(msg.sender, usr, wad);
                }
                function pull(address usr, uint wad) external {
                    transferFrom(usr, msg.sender, wad);
                }
                function move(address src, address dst, uint wad) external {
                    transferFrom(src, dst, wad);
                }
            
                // --- Approve by signature ---
                function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
                    require(deadline >= block.timestamp, 'cent/past-deadline');
                    bytes32 digest = keccak256(
                        abi.encodePacked(
                            '\x19\x01',
                            DOMAIN_SEPARATOR,
                            keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                        )
                    );
                    address recoveredAddress = ecrecover(digest, v, r, s);
                    require(recoveredAddress != address(0) && recoveredAddress == owner, 'cent-erc20/invalid-sig');
                    allowance[owner][spender] = value;
                    emit Approval(owner, spender, value);
                }
            }
            
            ////// src/lender/token/restricted.sol
            // Copyright (C) 2020 Centrifuge
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            /* import "tinlake-erc20/erc20.sol"; */
            
            contract MemberlistLike_2 {
                function hasMember(address) public view returns (bool);
                function member(address) public;
            }
            
            // Only mebmber with a valid (not expired) membership should be allowed to receive tokens
            contract RestrictedToken is ERC20 {
            
                MemberlistLike_2 public memberlist; 
                modifier checkMember(address usr) { memberlist.member(usr); _; }
                
                function hasMember(address usr) public view returns (bool) {
                    return memberlist.hasMember(usr);
                }
            
                constructor(string memory symbol_, string memory name_) ERC20(symbol_, name_) public {}
            
                function depend(bytes32 contractName, address addr) public auth {
                    if (contractName == "memberlist") { memberlist = MemberlistLike_2(addr); }
                    else revert();
                }
            
                function transferFrom(address from, address to, uint wad) checkMember(to) public returns (bool) {
                    return super.transferFrom(from, to, wad);
                }
            }
            

            File 4 of 4: Memberlist
            // Verified using https://dapp.tools
            
            // hevm: flattened sources of src/lender/token/memberlist.sol
            pragma solidity >=0.5.15 >=0.5.15 <0.6.0;
            
            ////// lib/tinlake-auth/lib/ds-note/src/note.sol
            /// note.sol -- the `note' modifier, for logging calls as events
            
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU General Public License for more details.
            
            // You should have received a copy of the GNU General Public License
            // along with this program.  If not, see <http://www.gnu.org/licenses/>.
            /* pragma solidity >=0.5.15; */
            
            contract DSNote {
                event LogNote(
                    bytes4   indexed  sig,
                    address  indexed  guy,
                    bytes32  indexed  foo,
                    bytes32  indexed  bar,
                    uint256           wad,
                    bytes             fax
                ) anonymous;
            
                modifier note {
                    bytes32 foo;
                    bytes32 bar;
                    uint256 wad;
            
                    assembly {
                        foo := calldataload(4)
                        bar := calldataload(36)
                        wad := callvalue()
                    }
            
                    _;
            
                    emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
                }
            }
            
            ////// lib/tinlake-auth/src/auth.sol
            // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            /* import "ds-note/note.sol"; */
            
            contract Auth is DSNote {
                mapping (address => uint) public wards;
                function rely(address usr) public auth note { wards[usr] = 1; }
                function deny(address usr) public auth note { wards[usr] = 0; }
                modifier auth { require(wards[msg.sender] == 1); _; }
            }
            
            ////// lib/tinlake-math/src/math.sol
            // Copyright (C) 2018 Rain <[email protected]>
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            contract Math {
                uint256 constant ONE = 10 ** 27;
            
                function safeAdd(uint x, uint y) public pure returns (uint z) {
                    require((z = x + y) >= x, "safe-add-failed");
                }
            
                function safeSub(uint x, uint y) public pure returns (uint z) {
                    require((z = x - y) <= x, "safe-sub-failed");
                }
            
                function safeMul(uint x, uint y) public pure returns (uint z) {
                    require(y == 0 || (z = x * y) / y == x, "safe-mul-failed");
                }
            
                function safeDiv(uint x, uint y) public pure returns (uint z) {
                    z = x / y;
                }
            
                function rmul(uint x, uint y) public pure returns (uint z) {
                    z = safeMul(x, y) / ONE;
                }
            
                function rdiv(uint x, uint y) public pure returns (uint z) {
                    require(y > 0, "division by zero");
                    z = safeAdd(safeMul(x, ONE), y / 2) / y;
                }
            
                function rdivup(uint x, uint y) internal pure returns (uint z) {
                    require(y > 0, "division by zero");
                    // always rounds up
                    z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y;
                }
            
            
            }
            
            ////// src/lender/token/memberlist.sol
            // Copyright (C) 2020 Centrifuge
            //
            // This program is free software: you can redistribute it and/or modify
            // it under the terms of the GNU Affero General Public License as published by
            // the Free Software Foundation, either version 3 of the License, or
            // (at your option) any later version.
            //
            // This program is distributed in the hope that it will be useful,
            // but WITHOUT ANY WARRANTY; without even the implied warranty of
            // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
            // GNU Affero General Public License for more details.
            //
            // You should have received a copy of the GNU Affero General Public License
            // along with this program.  If not, see <https://www.gnu.org/licenses/>.
            
            /* pragma solidity >=0.5.15 <0.6.0; */
            
            /* import "tinlake-math/math.sol"; */
            /* import "tinlake-auth/auth.sol"; */
            
            contract Memberlist is Math, Auth {
            
                uint constant minimumDelay = 7 days;
            
                // -- Members--
                mapping (address => uint) public members;
                function updateMember(address usr, uint validUntil) public auth {
                    require((safeAdd(block.timestamp, minimumDelay)) < validUntil);
                    members[usr] = validUntil;
                 }
            
                function updateMembers(address[] memory users, uint validUntil) public auth {
                    for (uint i = 0; i < users.length; i++) {
                        updateMember(users[i], validUntil);
                    }
                }
            
                constructor() public {
                    wards[msg.sender] = 1;
                }
            
                function member(address usr) public view {
                    require((members[usr] >= block.timestamp), "not-allowed-to-hold-token");
                }
            
                function hasMember(address usr) public view returns (bool) {
                    if (members[usr] >= block.timestamp) {
                        return true;
                    } 
                    return false;
                }
            }