Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x5124fcC2...3F38f1C34
Miner
| (Faith Builder) | 4.071507858824841596 Eth | 4.071518522024841596 Eth | 0.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 )
Tranche.redeemOrder( usr=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, newRedeemAmount=3734408101716650788863 )
-
0x22a1caca2ee82e9ce7ef900fd961891b66deb7ca.STATICCALL( )
RestrictedToken.transferFrom( from=0x80Acb3b55132C4fe408f8BCDa583AAF98824e206, to=0x53CF3CCd97CA914F9e441B8cd9A901E69B170f27, wad=3734408101716650788863 ) => ( True )
-
Memberlist.member( usr=0x53CF3CCd97CA914F9e441B8cd9A901E69B170f27 )
-
-
redeemOrder[Operator (ln:141)]
hasMember[Operator (ln:142)]
hasMember[Operator (ln:143)]
redeemOrder[Operator (ln:144)]
File 1 of 4: Operator
File 2 of 4: Tranche
File 3 of 4: RestrictedToken
File 4 of 4: Memberlist
// 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; } }