Address Contract Partially Verified
Address
0x45ffC7FA69E69210c8045684dfC2ee4824377876
Balance
0 ETH
Nonce
3
Code Size
11200 bytes
Creator
0xe87eD2b3...fBbC at tx 0x907e9e87...7ea7e1
Indexed Transactions
0 (3 on-chain, 0.9% indexed)
Contract Bytecode
11200 bytes
0x6080604052600436106102135760003560e01c80637b9072ea11610118578063b71482cb116100a0578063dd62ed3e1161006f578063dd62ed3e1461060f578063e437c40f14610655578063f2fde38b14610675578063f8b45b0514610695578063fb12637f146106aa57600080fd5b8063b71482cb14610585578063c0246668146105ba578063d0db5083146105da578063d15f5893146105ef57600080fd5b8063a8aa1b31116100e7578063a8aa1b31146104e7578063a9059cbb14610505578063aacebbe314610525578063adc9772e14610545578063afa4f3b21461056557600080fd5b80637b9072ea146104765780638da5cb5b146104945780638e690186146104b257806395d89b41146104d257600080fd5b806331e79db01161019b5780634fbee1931161016a5780634fbee193146103dc578063668038e01461040c57806370a0823114610421578063715018a6146104415780637935510b1461045657600080fd5b806331e79db01461034c57806332cb6b0c1461036c5780633a4dc2fb1461038a5780634cf088d9146103aa57600080fd5b806323b872dd116101e257806323b872dd146102bb578063269a86b2146102db5780632d0f5b34146102fb5780632e17de7814610310578063313ce5671461033057600080fd5b806306fdde031461021f578063095ea7b31461024a5780630e20b02a1461027a57806318160ddd1461029c57600080fd5b3661021a57005b600080fd5b34801561022b57600080fd5b506102346106ca565b6040516102419190612676565b60405180910390f35b34801561025657600080fd5b5061026a6102653660046126d9565b61075c565b6040519015158152602001610241565b34801561028657600080fd5b5061029a610295366004612705565b610776565b005b3480156102a857600080fd5b506002545b604051908152602001610241565b3480156102c757600080fd5b5061026a6102d636600461271e565b610799565b3480156102e757600080fd5b5061029a6102f636600461275f565b6107bd565b34801561030757600080fd5b5061029a61089d565b34801561031c57600080fd5b5061029a61032b366004612705565b610929565b34801561033c57600080fd5b5060405160128152602001610241565b34801561035857600080fd5b5061029a610367366004612781565b610a22565b34801561037857600080fd5b506102ad69d3c21bcecceda100000081565b34801561039657600080fd5b5061029a6103a5366004612705565b610a8d565b3480156103b657600080fd5b506016546001600160a01b03165b6040516001600160a01b039091168152602001610241565b3480156103e857600080fd5b5061026a6103f7366004612781565b601e6020526000908152604090205460ff1681565b34801561041857600080fd5b5061029a610b68565b34801561042d57600080fd5b506102ad61043c366004612781565b610bc2565b34801561044d57600080fd5b5061029a610bdd565b34801561046257600080fd5b5061029a610471366004612705565b610bf1565b34801561048257600080fd5b506015546001600160a01b03166103c4565b3480156104a057600080fd5b506005546001600160a01b03166103c4565b3480156104be57600080fd5b5061029a6104cd366004612705565b610c28565b3480156104de57600080fd5b50610234610c96565b3480156104f357600080fd5b506014546001600160a01b03166103c4565b34801561051157600080fd5b5061026a6105203660046126d9565b610ca5565b34801561053157600080fd5b5061029a610540366004612781565b610cb3565b34801561055157600080fd5b5061029a6105603660046126d9565b610d30565b34801561057157600080fd5b5061029a610580366004612705565b610e90565b34801561059157600080fd5b506105a56105a03660046127a5565b610eb2565b60405161024199989796959493929190612815565b3480156105c657600080fd5b5061029a6105d5366004612887565b61128b565b3480156105e657600080fd5b5061029a6112eb565b3480156105fb57600080fd5b5061029a61060a3660046128c0565b611700565b34801561061b57600080fd5b506102ad61062a3660046128ec565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b34801561066157600080fd5b5061029a610670366004612781565b61178c565b34801561068157600080fd5b5061029a610690366004612781565b611899565b3480156106a157600080fd5b506102ad6118d7565b3480156106b657600080fd5b5061029a6106c5366004612781565b611990565b6060600380546106d99061291a565b80601f01602080910402602001604051908101604052809291908181526020018280546107059061291a565b80156107525780601f1061072757610100808354040283529160200191610752565b820191906000526020600020905b81548152906001019060200180831161073557829003601f168201915b5050505050905090565b60003361076a8185856119f2565b60019150505b92915050565b61077e611a04565b69021e19e0c9bab2400000811061079457600080fd5b601d55565b6000336107a7858285611a31565b6107b2858585611a97565b506001949350505050565b6107c5611a04565b604051636546dab160e11b81526006600482015260248101839052733287846191259ba962047e9c112641d20986b8b29063ca8db5629060440160006040518083038186803b15801561081757600080fd5b505af415801561082b573d6000803e3d6000fd5b50506040516374d7681160e11b81526006600482015260248101849052733287846191259ba962047e9c112641d20986b8b2925063e9aed022915060440160006040518083038186803b15801561088157600080fd5b505af4158015610895573d6000803e3d6000fd5b505050505050565b6108a5611a04565b733287846191259ba962047e9c112641d20986b8b2638efe38796006806108cb30610bc2565b6040516001600160e01b031960e086901b16815260048101939093526024830191909152604482015260640160006040518083038186803b15801561090f57600080fd5b505af4158015610923573d6000803e3d6000fd5b50505050565b600081116109715760405162461bcd60e51b815260206004820152601060248201526f043616e6e6f7420756e7374616b6520360841b60448201526064015b60405180910390fd5b601654604051630615339760e51b8152336004820152602481018390526001600160a01b039091169063c2a672e090604401600060405180830381600087803b1580156109bd57600080fd5b505af11580156109d1573d6000803e3d6000fd5b503392507f7fc4727e062e336010f2c282598ef5f14facb3de68cf8195c2f23e1454b2b74e9150839050610a0660028261296a565b604080519283526020830191909152015b60405180910390a250565b610a2a611a04565b60155460405163031e79db60e41b81526001600160a01b038381166004830152909116906331e79db0906024015b600060405180830381600087803b158015610a7257600080fd5b505af1158015610a86573d6000803e3d6000fd5b5050505050565b60008111610acd5760405162461bcd60e51b815260206004820152600d60248201526c043616e6e6f742076657374203609c1b6044820152606401610968565b60165460405163a6435fff60e01b8152336004820152602481018390526001600160a01b039091169063a6435fff90604401600060405180830381600087803b158015610b1957600080fd5b505af1158015610b2d573d6000803e3d6000fd5b5050604080518481524260208201523393507f8fe19f160f86d04fb1a90dde93e5e1a47df0810685adf4b990153c107d7b3924925001610a17565b60155460405163c7e772ed60e01b81523360048201526001600160a01b039091169063c7e772ed906024015b600060405180830381600087803b158015610bae57600080fd5b505af1158015610923573d6000803e3d6000fd5b6001600160a01b031660009081526020819052604090205490565b610be5611a04565b610bef6000611af6565b565b60165460405163017043b560e51b8152336004820152602481018390526001600160a01b0390911690632e0876a090604401610a58565b610c30611a04565b604051633873159160e11b81526006600482015260248101829052733287846191259ba962047e9c112641d20986b8b2906370e62b229060440160006040518083038186803b158015610c8257600080fd5b505af4158015610a86573d6000803e3d6000fd5b6060600480546106d99061291a565b60003361076a818585611a97565b610cbb611a04565b601780546001600160a01b0319166001600160a01b0383169081179091556040519081527ffb16f0be9e94893ed59dc6e3e6c2799ab51001e2980ead46c1bf9a21a8c397169060200160405180910390a16001600160a01b03166000908152601e60205260409020805460ff19166001179055565b6001600160a01b038216610d42573391505b60008111610d835760405162461bcd60e51b815260206004820152600e60248201526d043616e6e6f74207374616b6520360941b6044820152606401610968565b80610d8d33610bc2565b1015610dd25760405162461bcd60e51b8152602060048201526014602482015273496e73756666696369656e742062616c616e636560601b6044820152606401610968565b601654610dea9033906001600160a01b0316836119f2565b60165460405163bf6eac2f60e01b81526001600160a01b038481166004830152336024830152604482018490529091169063bf6eac2f90606401600060405180830381600087803b158015610e3e57600080fd5b505af1158015610e52573d6000803e3d6000fd5b50506040518381523392507f9e71bc8eea02a63969f509818f2dafb9254532904319f9dbda79b67bd34a5f3d91506020015b60405180910390a25050565b610e98611a04565b683635c9adc5dea000008110610ead57600080fd5b601c55565b601554604051631c13359160e11b81526001600160a01b0385811660048301526060926000928392839283928392839289928492909116906338266b2290602401600060405180830381865afa158015610f10573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f3891908101906129a2565b6040516343fda56160e11b815260066004820152909950733287846191259ba962047e9c112641d20986b8b2906387fb4ac2906024016040805180830381865af4158015610f8a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fae9190612a60565b6016546040516370a0823160e01b8152929a509098504297506001600160a01b0316906370a0823190610ff4908f906004016001600160a01b0391909116815260200190565b602060405180830381865afa158015611011573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110359190612a84565b6016546040516336ca036560e01b81526001600160a01b038f811660048301529297509116906336ca036590602401602060405180830381865afa158015611081573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110a59190612a84565b6016546040516357ff5aed60e01b81526001600160a01b038f811660048301529296509116906357ff5aed90602401602060405180830381865afa1580156110f1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111159190612a84565b925082156111fa57828b1061113a57604080516000815260208101909152915061120c565b89838111156111465750825b808c10156111e257600660100160009054906101000a90046001600160a01b03166001600160a01b031663d510e5b28e8e846040518463ffffffff1660e01b815260040161119693929190612a9d565b600060405180830381865afa1580156111b3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111db91908101906129a2565b92506111f4565b60408051600081526020810190915292505b5061120c565b60408051600081526020810190915291505b60155460405163cbb3a6c160e01b81526001600160a01b038e811660048301529091169063cbb3a6c190602401602060405180830381865afa158015611256573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061127a9190612a84565b905093979b92969a50939750939750565b611293611a04565b6001600160a01b0382166000818152601e6020908152604091829020805460ff191685151590811790915591519182527f9d8f7706ea1113d1a167b526eca956215946dd36cc7df39eb16180222d8b5df79101610e84565b6112f3611a04565b6019541561130057600080fd5b6013546040805163c45a015560e01b815290516001600160a01b039092169163c45a0155916004808201926020929091908290030181865afa15801561134a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061136e9190612abe565b6001600160a01b031663c9c65396306006600d0160009054906101000a90046001600160a01b03166001600160a01b031663ad5c46486040518163ffffffff1660e01b8152600401602060405180830381865afa1580156113d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113f79190612abe565b6040516001600160e01b031960e085901b1681526001600160a01b039283166004820152911660248201526044016020604051808303816000875af1158015611444573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114689190612abe565b601480546001600160a01b039283166001600160a01b0319918216811790925560068054821683179055601280549091168217905560155460405163031e79db60e41b81526004810192909252909116906331e79db090602401600060405180830381600087803b1580156114dc57600080fd5b505af11580156114f0573d6000803e3d6000fd5b505060135461150e92503091506001600160a01b03166000196119f2565b60145460135460405163095ea7b360e01b81526001600160a01b039182166004820152600019602482015291169063095ea7b3906044016020604051808303816000875af1158015611564573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115889190612adb565b506000606461159630610bc2565b6115a1906014612af8565b6115ab919061296a565b6015549091506115c69030906001600160a01b0316836119f2565b6015546040516316f6849d60e31b815260006004820152306024820152604481018390526001600160a01b039091169063b7b424e890606401600060405180830381600087803b15801561161957600080fd5b505af115801561162d573d6000803e3d6000fd5b50506013546001600160a01b0316915063f305d7199050473061164f81610bc2565b6000806116646005546001600160a01b031690565b60405160e088901b6001600160e01b03191681526001600160a01b03958616600482015260248101949094526044840192909252606483015290911660848201524260a482015260c40160606040518083038185885af11580156116cc573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906116f19190612b0f565b50504260198190556008555050565b611708611a04565b604051635dbde74360e11b81526006600482018190526024820152604481018490526064810183905260848101829052733287846191259ba962047e9c112641d20986b8b29063bb7bce869060a40160006040518083038186803b15801561176f57600080fd5b505af4158015611783573d6000803e3d6000fd5b50505050505050565b611794611a04565b601880546001600160a01b0319166001600160a01b0383169081179091556040519081527f5dab461ff3b8ba921b4038f8c6d0260a48defbb76257746d2e1fce16e26e0d309060200160405180910390a16001600160a01b038082166000908152601e6020526040808220805460ff19166001179055601854905163eaa7174d60e01b815292169163eaa7174d916118329181908190600401612a9d565b600060405180830381600087803b15801561184c57600080fd5b505af1158015611860573d6000803e3d6000fd5b5050601854604051636f44b55760e01b81526001600160a01b039091169250636f44b5579150610a589060009081908190600401612a9d565b6118a1611a04565b6001600160a01b0381166118cb57604051631e4fbdf760e01b815260006004820152602401610968565b6118d481611af6565b50565b60405163bd12317960e01b815260066004820152600090733287846191259ba962047e9c112641d20986b8b29063bd12317990602401602060405180830381865af415801561192a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061194e9190612adb565b1561197957606461196a69d3c21bcecceda10000006001612af8565b611974919061296a565b905090565b606461196a69d3c21bcecceda10000006002612af8565b611998611a04565b601580546001600160a01b0319166001600160a01b0383161790556119bb611b48565b6040516001600160a01b038216907f1e7fbad200a59aca8287bffdbe60da36c99480894a2bdc25ac88eed6029936f590600090a250565b6119ff8383836001611d8b565b505050565b6005546001600160a01b03163314610bef5760405163118cdaa760e01b8152336004820152602401610968565b6001600160a01b038381166000908152600160209081526040808320938616835292905220546000198110156109235781811015611a8857828183604051637dc7a0d960e11b815260040161096893929190612a9d565b61092384848484036000611d8b565b6001600160a01b038316611ac157604051634b637e8f60e11b815260006004820152602401610968565b6001600160a01b038216611aeb5760405163ec442f0560e01b815260006004820152602401610968565b6119ff838383611e60565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60155460405163031e79db60e41b81523060048201526001600160a01b03909116906331e79db090602401600060405180830381600087803b158015611b8d57600080fd5b505af1158015611ba1573d6000803e3d6000fd5b50506015546001600160a01b031691506331e79db09050611bca6005546001600160a01b031690565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401600060405180830381600087803b158015611c0b57600080fd5b505af1158015611c1f573d6000803e3d6000fd5b505060155460405163031e79db60e41b81526001600160a01b039091166004820181905292506331e79db09150602401600060405180830381600087803b158015611c6957600080fd5b505af1158015611c7d573d6000803e3d6000fd5b505060155460135460405163031e79db60e41b81526001600160a01b039182166004820152911692506331e79db09150602401600060405180830381600087803b158015611cca57600080fd5b505af1158015611cde573d6000803e3d6000fd5b50506014546001600160a01b0316159150611d5790505760155460145460405163031e79db60e41b81526001600160a01b0391821660048201529116906331e79db090602401600060405180830381600087803b158015611d3e57600080fd5b505af1158015611d52573d6000803e3d6000fd5b505050505b60155460165460405163031e79db60e41b81526001600160a01b0391821660048201529116906331e79db090602401610b94565b6001600160a01b038416611db55760405163e602df0560e01b815260006004820152602401610968565b6001600160a01b038316611ddf57604051634a1406b160e11b815260006004820152602401610968565b6001600160a01b038085166000908152600160209081526040808320938716835292905220829055801561092357826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051611e5291815260200190565b60405180910390a350505050565b6019541580611e7657506001600160a01b038316155b80611e8857506001600160a01b038216155b80611e91575080155b80611ea35750601b54610100900460ff165b15611f9357611eb3838383612228565b80156119ff576001600160a01b03831615611f225760155460405163993ae7e960e01b81526001600160a01b0385811660048301529091169063993ae7e990602401600060405180830381600087803b158015611f0f57600080fd5b505af1925050508015611f20575060015b505b6001600160a01b038216156119ff5760155460405163993ae7e960e01b81526001600160a01b0384811660048301529091169063993ae7e990602401600060405180830381600087803b158015611f7857600080fd5b505af1925050508015611f89575060015b156119ff57505050565b611f9e83838361233f565b601b5460009060ff16158015611fcd57506001600160a01b0384166000908152601e602052604090205460ff16155b8015611ff257506001600160a01b0383166000908152601e602052604090205460ff16155b905081600082156120db57604051635043568760e01b815260116004820152600660248201526001600160a01b03808816604483015286166064820152608481018590526000908190735adcaddf3687eabd16ced06c44f186ba088fa1a09063504356879060a4016040805180830381865af4158015612076573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061209a9190612a60565b90925090506120a98183612b3d565b925081156120c8576120bb8287612b64565b95506120c8883084611a97565b80156120d8576120d8878261255a565b50505b6120e6868686612228565b60155460405163993ae7e960e01b81526001600160a01b0388811660048301529091169063993ae7e990602401600060405180830381600087803b15801561212d57600080fd5b505af192505050801561213e575060015b5060155460405163993ae7e960e01b81526001600160a01b0387811660048301529091169063993ae7e990602401600060405180830381600087803b15801561218657600080fd5b505af1925050508015612197575060015b506040516338366b7360e21b8152600660048201526001600160a01b038088166024830152861660448201526064810183905260848101829052735b4744d543204e27423e82f55ea7c9422f170dfc9063e0d9adcc9060a40160006040518083038186803b15801561220857600080fd5b505af415801561221c573d6000803e3d6000fd5b50505050505050505050565b6001600160a01b0383166122535780600260008282546122489190612b77565b909155506122b29050565b6001600160a01b038316600090815260208190526040902054818110156122935783818360405163391434e360e21b815260040161096893929190612a9d565b6001600160a01b03841660009081526020819052604090209082900390555b6001600160a01b0382166122ce576002805482900390556122ed565b6001600160a01b03821660009081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161233291815260200190565b60405180910390a3505050565b600061234a30610bc2565b601c549091508110156123656005546001600160a01b031690565b6001600160a01b0316856001600160a01b03161415801561239457506005546001600160a01b03858116911614155b15610a86576001600160a01b03841630148015906123c057506014546001600160a01b03858116911614155b80156123da57506013546001600160a01b03858116911614155b80156123f457506015546001600160a01b03858116911614155b801561240e57506016546001600160a01b03858116911614155b801561242857506016546001600160a01b03868116911614155b15612454576124356118d7565b8361243f86610bc2565b6124499190612b77565b111561245457600080fd5b8080156124625750601a5443115b801561247c57506014546001600160a01b03868116911614155b801561249557506014546001600160a01b038581169116145b80156124a357506000601954115b80156124b0575060195442115b15610a865743601a55601b805460ff19166001179055601d5482908111156124d75750601d545b604051638efe387960e01b8152600660048201819052602482015260448101829052733287846191259ba962047e9c112641d20986b8b290638efe38799060640160006040518083038186803b15801561253057600080fd5b505af4158015612544573d6000803e3d6000fd5b5050601b805460ff191690555050505050505050565b601554600090612572906001600160a01b0316610bc2565b905080821115612580578091505b80156119ff5760155460405163282728a960e11b8152600481018490526001600160a01b039091169063504e5152906024016020604051808303816000875af11580156125d1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125f59190612a84565b9150811561262f57601b805461ff001916610100179055601554612623906001600160a01b03168484611a97565b601b805461ff00191690555b604080516001600160a01b0385168152602081018490527f98dcaeced95369821fc42e6b1e87d724bad86c549e4d6f1b69cc88eeb1154387910160405180910390a1505050565b600060208083528351808285015260005b818110156126a357858101830151858201604001528201612687565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b03811681146118d457600080fd5b600080604083850312156126ec57600080fd5b82356126f7816126c4565b946020939093013593505050565b60006020828403121561271757600080fd5b5035919050565b60008060006060848603121561273357600080fd5b833561273e816126c4565b9250602084013561274e816126c4565b929592945050506040919091013590565b6000806040838503121561277257600080fd5b50508035926020909101359150565b60006020828403121561279357600080fd5b813561279e816126c4565b9392505050565b6000806000606084860312156127ba57600080fd5b83356127c5816126c4565b95602085013595506040909401359392505050565b600081518084526020808501945080840160005b8381101561280a578151875295820195908201906001016127ee565b509495945050505050565b60006101208083526128298184018d6127da565b90508a60208401528960408401528860608401528760808401528660a08401528560c084015282810360e084015261286181866127da565b915050826101008301529a9950505050505050505050565b80151581146118d457600080fd5b6000806040838503121561289a57600080fd5b82356128a5816126c4565b915060208301356128b581612879565b809150509250929050565b6000806000606084860312156128d557600080fd5b505081359360208301359350604090920135919050565b600080604083850312156128ff57600080fd5b823561290a816126c4565b915060208301356128b5816126c4565b600181811c9082168061292e57607f821691505b60208210810361294e57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b60008261298757634e487b7160e01b600052601260045260246000fd5b500490565b634e487b7160e01b600052604160045260246000fd5b600060208083850312156129b557600080fd5b825167ffffffffffffffff808211156129cd57600080fd5b818501915085601f8301126129e157600080fd5b8151818111156129f3576129f361298c565b8060051b604051601f19603f83011681018181108582111715612a1857612a1861298c565b604052918252848201925083810185019188831115612a3657600080fd5b938501935b82851015612a5457845184529385019392850192612a3b565b98975050505050505050565b60008060408385031215612a7357600080fd5b505080516020909101519092909150565b600060208284031215612a9657600080fd5b5051919050565b6001600160a01b039390931683526020830191909152604082015260600190565b600060208284031215612ad057600080fd5b815161279e816126c4565b600060208284031215612aed57600080fd5b815161279e81612879565b808202811582820484141761077057610770612954565b600080600060608486031215612b2457600080fd5b8351925060208401519150604084015190509250925092565b8181036000831280158383131683831282161715612b5d57612b5d612954565b5092915050565b8181038181111561077057610770612954565b808201808211156107705761077061295456fea2646970667358221220b62f84ccbbc931b79c4c25435b71b1e5c09ccb78f70843516a3f066f59f6c3c864736f6c63430008140033
Verified Source Code Partial Match
Compiler: v0.8.20+commit.a1b79de6
EVM: paris
Optimization: Yes (200 runs)
UniswapV2PriceImpactCalculator.sol 36 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./IUniswapV2Pair.sol";
import "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
library UniswapV2PriceImpactCalculator {
function calculateSellPriceImpact(address tokenAddress, address pairAddress, uint256 value) public view returns (uint256) {
value = value * 998 / 1000;
IUniswapV2Pair pair = IUniswapV2Pair(pairAddress);
(uint256 r0, uint256 r1,) = pair.getReserves();
IERC20Metadata token0 = IERC20Metadata(pair.token0());
IERC20Metadata token1 = IERC20Metadata(pair.token1());
if(address(token1) == tokenAddress) {
IERC20Metadata tokenTemp = token0;
token0 = token1;
token1 = tokenTemp;
uint256 rTemp = r0;
r0 = r1;
r1 = rTemp;
}
uint256 product = r0 * r1;
uint256 r0After = r0 + value;
uint256 r1After = product / r0After;
return (10000 - (r1After * 10000 / r1)) * 998 / 1000;
}
}
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
draft-IERC6093.sol 161 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC-20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC-721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC-1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
ERC20.sol 312 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
DividendPayingToken.sol 398 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import "./SafeMath.sol";
import "./SafeMathUint.sol";
import "./SafeMathInt.sol";
import "./DividendPayingTokenInterface.sol";
import "./DividendPayingTokenOptionalInterface.sol";
import "./IUniswapV2Pair.sol";
import "./IDexToken.sol";
/// @title Dividend-Paying Token
/// @author Roger Wu (https://github.com/roger-wu)
/// @dev A mintable ERC20 token that allows anyone to pay and distribute ether
/// to token holders as dividends and allows token holders to withdraw their dividends.
/// Reference: the source code of PoWH3D: https://etherscan.io/address/0xB3775fB83F7D12A36E0475aBdD1FCA35c091efBe#code
contract DividendPayingToken is ERC20, DividendPayingTokenInterface, DividendPayingTokenOptionalInterface, Ownable {
using SafeMath for uint256;
using SafeMathUint for uint256;
using SafeMathInt for int256;
// With `magnitude`, we can properly distribute dividends even if the amount of received ether is small.
// For more discussion about choosing the value of `magnitude`,
// see https://github.com/ethereum/EIPs/issues/1726#issuecomment-472352728
uint256 constant internal magnitude = 2**128;
IERC20[] public supportedTokens;
IDexToken internal dexToken;
mapping (IERC20 => uint256) internal magnifiedDividendPerShare;
// Not all the ether sent to the contract are distributed immediately
// Only a certain factor, immediateDistributionFactor, is immediately, and then the rest goes to a
// pool, and is periodically distributed, based on delayedDistributionFactorPerDay
uint256 public constant FACTOR_MAX = 10000;
uint256 public immediateDistributionFactor;
uint256 public delayedDistributionFactorPerDay;
event ImmediateDistributionFactorUpdated(uint256 value);
event DelayedDistributionFactorPerDayUpdated(uint256 value);
// Track how many dividends are currently delayed
mapping (IERC20 => uint256) public delayedDividends;
// Track last time delayed dividends were distributed
mapping (IERC20 => uint256) public lastDelayedDividendsDistribution;
// About dividendCorrection:
// If the token balance of a `_user` is never changed, the dividend of `_user` can be computed with:
// `dividendOf(_user) = dividendPerShare * balanceOf(_user)`.
// When `balanceOf(_user)` is changed (via minting/burning/transferring tokens),
// `dividendOf(_user)` should not be changed,
// but the computed value of `dividendPerShare * balanceOf(_user)` is changed.
// To keep the `dividendOf(_user)` unchanged, we add a correction term:
// `dividendOf(_user) = dividendPerShare * balanceOf(_user) + dividendCorrectionOf(_user)`,
// where `dividendCorrectionOf(_user)` is updated whenever `balanceOf(_user)` is changed:
// `dividendCorrectionOf(_user) = dividendPerShare * (old balanceOf(_user)) - (new balanceOf(_user))`.
// So now `dividendOf(_user)` returns the same value before and after `balanceOf(_user)` is changed.
mapping(IERC20 => mapping(address => int256)) internal magnifiedDividendCorrections;
mapping(IERC20 => mapping(address => uint256)) internal withdrawnDividends;
mapping(IERC20 => uint256) public totalDividendsDistributed;
struct Distribution {
uint256 amount;
uint256 timestamp;
}
mapping(IERC20 => Distribution[]) public distributionHistory;
mapping(IERC20 => uint256) public firstValidDistributionIndex;
mapping(IERC20 => uint256) public distributionsLast24h;
constructor(
string memory _name,
string memory _symbol,
uint256 _immediateDistributionFactor,
uint256 _delayedDistributionFactorPerDay,
IERC20 _token) ERC20(_name, _symbol) Ownable(msg.sender) {
updateImmediateDistributionFactor(_immediateDistributionFactor);
updateDelayedDistributionFactorPerDay(_delayedDistributionFactorPerDay);
dexToken = IDexToken(address(_token));
supportedTokens.push(IERC20(address(0))); //ether
supportedTokens.push(_token); //main token
}
function updateImmediateDistributionFactor(uint256 value) public onlyOwner {
require(value <= FACTOR_MAX);
immediateDistributionFactor = value;
emit ImmediateDistributionFactorUpdated(value);
}
function updateDelayedDistributionFactorPerDay(uint256 value) public onlyOwner {
require(value <= FACTOR_MAX && value >= 10); // Minimum 0.1% per day
delayedDistributionFactorPerDay = value;
emit DelayedDistributionFactorPerDayUpdated(value);
}
/// @dev Distributes dividends whenever ether is paid to this contract.
receive() external payable {
distributeDividends(immediateDistributionFactor, IERC20(address(0)), msg.value);
}
/// @dev Distributes dividends whenever a token is paid to this contract. Caller must approve
/// this contract taking the tokens
function receiveTokens(IERC20 _token, uint256 value) external override {
distributeDividends(immediateDistributionFactor, _token, value);
}
function isTokenSupported(IERC20 _token) private view returns (bool) {
for(uint256 i = 0; i < supportedTokens.length; i++) {
if(_token == supportedTokens[i]) {
return true;
}
}
return false;
}
/// @notice Distributes ether to token holders as dividends.
/// @dev It reverts if the total supply of tokens is 0.
/// It emits the `DividendsDistributed` event if the amount of received ether is greater than 0.
/// About undistributed ether:
/// In each distribution, there is a small amount of ether not distributed,
/// the magnified amount of which is
/// `(msg.value * magnitude) % totalSupply()`.
/// With a well-chosen `magnitude`, the amount of undistributed ether
/// (de-magnified) in a distribution can be less than 1 wei.
/// We can actually keep track of the undistributed ether in a distribution
/// and try to distribute it in the next distribution,
/// but keeping track of such data on-chain costs much more than
/// the saved ether, so we don't do that.
function distributeDividends(uint256 _immediateDistributionFactor, IERC20 _token, uint256 value) public override payable {
if(lastDelayedDividendsDistribution[_token] == 0) {
lastDelayedDividendsDistribution[_token] = block.timestamp;
}
if(address(_token) == address(0)) {
require(value == msg.value, "Invalid value");
}
else {
require(isTokenSupported(_token), "Unsupported");
require(msg.value == 0, "msg.value is not zero");
uint256 before = _token.balanceOf(address(this));
_token.transferFrom(msg.sender, address(this), value);
value = _token.balanceOf(address(this)) - before;
}
uint256 immediate = value * _immediateDistributionFactor / FACTOR_MAX;
uint256 delayed = value - immediate;
distributeDividends(immediate, _token, false);
distributeAllDelayedDividends();
delayedDividends[_token] += delayed;
}
function distributeAllDelayedDividends() public {
distributeDelayedDividends(supportedTokens[0]);
distributeDelayedDividends(supportedTokens[1]);
}
function distributeDelayedDividends(IERC20 _token) private {
if(totalSupply() == 0) {
return;
}
// Distribute delayed if needed
if(delayedDividends[_token] > 0) {
uint256 timeSinceLastDelayedDistribution = block.timestamp - lastDelayedDividendsDistribution[_token];
if(timeSinceLastDelayedDistribution > 0) {
uint256 delayedToDistribute = delayedDividends[_token] * timeSinceLastDelayedDistribution / (1 days) * delayedDistributionFactorPerDay / FACTOR_MAX;
if(delayedToDistribute > delayedDividends[_token]) {
delayedToDistribute = delayedDividends[_token];
}
uint256 balance;
if(address(_token) == address(0)) {
balance = address(this).balance;
}
else {
balance = _token.balanceOf(address(this));
}
if(delayedToDistribute > balance) {
delayedToDistribute = balance;
}
if(delayedToDistribute > 0) {
delayedDividends[_token] -= delayedToDistribute;
distributeDividends(delayedToDistribute, _token, true);
}
}
}
lastDelayedDividendsDistribution[_token] = block.timestamp;
}
/// @notice Distributes ether to token holders as dividends.
/// @dev It reverts if the total supply of tokens is 0.
/// It emits the `DividendsDistributed` event if the amount of received ether/tokens is greater than 0.
/// About undistributed ether:
/// In each distribution, there is a small amount of ether/tokens not distributed,
/// the magnified amount of which is
/// `(amount.value * magnitude) % totalSupply()`.
/// With a well-chosen `magnitude`, the amount of undistributed ether/tokens
/// (de-magnified) in a distribution can be less than 1 wei.
/// We can actually keep track of the undistributed ether/tokens in a distribution
/// and try to distribute it in the next distribution,
/// but keeping track of such data on-chain costs much more than
/// the saved ether/tokens, so we don't do that.
function distributeDividends(uint256 amount, IERC20 token, bool delayed) private {
if (amount > 0) {
require(totalSupply() > 0);
updateDistributionTracking(token, amount); // Add this line at start of if(amount > 0) block
magnifiedDividendPerShare[token] = magnifiedDividendPerShare[token].add(
(amount).mul(magnitude) / totalSupply()
);
emit DividendsDistributed(msg.sender, amount, address(token), delayed);
totalDividendsDistributed[token] = totalDividendsDistributed[token].add(amount);
}
}
/// @notice View the amount of dividend in wei that an address can withdraw.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` can withdraw.
function dividendOf(address _owner, IERC20 _token) public view override returns(uint256) {
return withdrawableDividendOf(_owner, _token);
}
/// @notice View the amount of dividend in wei that an address can withdraw.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` can withdraw.
function withdrawableDividendOf(address _owner, IERC20 _token) public view override returns(uint256) {
return accumulativeDividendOf(_owner, _token).sub(withdrawnDividends[IERC20(_token)][_owner]);
}
/// @notice View the amount of dividend in wei that an address has withdrawn.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` has withdrawn.
function withdrawnDividendOf(address _owner, IERC20 _token) public view override returns(uint256) {
return withdrawnDividends[IERC20(_token)][_owner];
}
/// @notice View the amount of dividend in wei that an address has earned in total.
/// @dev accumulativeDividendOf(_owner) = withdrawableDividendOf(_owner) + withdrawnDividendOf(_owner)
/// = (magnifiedDividendPerShare * balanceOf(_owner) + magnifiedDividendCorrections[_owner]) / magnitude
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` has earned in total.
function accumulativeDividendOf(address _owner, IERC20 _token) public view override returns(uint256) {
return magnifiedDividendPerShare[_token].mul(balanceOf(_owner)).toInt256Safe()
.add(magnifiedDividendCorrections[_token][_owner]).toUint256Safe() / magnitude;
}
function _update(address from, address to, uint256 value) internal virtual override {
if(from == address(0)) {
super._update(from, to, value);
for(uint256 i = 0; i < supportedTokens.length; i++) {
magnifiedDividendCorrections[supportedTokens[i]][to] = magnifiedDividendCorrections[supportedTokens[i]][to]
.sub( (magnifiedDividendPerShare[supportedTokens[i]].mul(value)).toInt256Safe() );
}
}
else if(to == address(0)) {
super._update(from, to, value);
for(uint256 i = 0; i < supportedTokens.length; i++) {
magnifiedDividendCorrections[supportedTokens[i]][from] = magnifiedDividendCorrections[supportedTokens[i]][from]
.add( (magnifiedDividendPerShare[supportedTokens[i]].mul(value)).toInt256Safe() );
}
}
else {
require(false, "Non-transferrable");
}
}
function _setBalance(address account, uint256 newBalance) internal {
uint256 currentBalance = balanceOf(account);
if(newBalance > currentBalance) {
uint256 mintAmount = newBalance.sub(currentBalance);
_mint(account, mintAmount);
} else if(newBalance < currentBalance) {
uint256 burnAmount = currentBalance.sub(newBalance);
_burn(account, burnAmount);
}
}
function updateDistributionTracking(IERC20 token, uint256 amount) private {
// Add new distribution to end of array
distributionHistory[token].push(Distribution({
amount: amount,
timestamp: block.timestamp
}));
// Add to 24h total
distributionsLast24h[token] += amount;
// Remove expired distributions from total
uint256 cutoffTime = block.timestamp - 24 hours;
uint256 currentFirstValid = firstValidDistributionIndex[token];
// Move index forward past expired distributions
while (currentFirstValid < distributionHistory[token].length &&
distributionHistory[token][currentFirstValid].timestamp < cutoffTime) {
distributionsLast24h[token] -= distributionHistory[token][currentFirstValid].amount;
currentFirstValid++;
}
firstValidDistributionIndex[token] = currentFirstValid;
}
function getDistributionAmounts(IERC20 token) public view returns (uint256, uint256) {
// If no distributions yet, return 0 for both
if (distributionHistory[token].length == 0) {
return (0, 0);
}
// Get timestamp of first valid distribution
uint256 firstTimestamp = distributionHistory[token][firstValidDistributionIndex[token]].timestamp;
uint256 secondsElapsed = block.timestamp - firstTimestamp;
// Return total distributions and seconds elapsed
return (distributionsLast24h[token], secondsElapsed);
}
function getDistributionsAmounts() public view returns (uint256[] memory, uint256[] memory) {
uint256[] memory amounts = new uint256[](supportedTokens.length);
uint256[] memory secondsElapsed = new uint256[](supportedTokens.length);
for (uint256 i = 0; i < supportedTokens.length; i++) {
(amounts[i], secondsElapsed[i]) = getDistributionAmounts(supportedTokens[i]);
}
return (amounts, secondsElapsed);
}
function getStakingAPR(uint256 inputAmount) public view returns (uint256) {
if(totalSupply() == 0) {
return 0;
}
// Calculate how much XPeriscope they would get (5x)
uint256 xPeriscopeAmount = inputAmount * 5;
// Calculate their share of the new total supply
uint256 share = (xPeriscopeAmount * 1e18) / (totalSupply() + xPeriscopeAmount); // Use 1e18 for precision
(uint256[] memory amounts, uint256[] memory secondsElapsed) = getDistributionsAmounts();
// Calculate annualized amounts for both tokens
uint256 elapsed = secondsElapsed[0] > 0 ? secondsElapsed[0] : 1;
uint256 annualETH = (amounts[0] * 365 days) / elapsed;
uint256 annualPeriscope = (amounts[1] * 365 days) / elapsed;
// Calculate their share of annual distributions
uint256 theirAnnualETH = (annualETH * share) / 1e18;
uint256 theirAnnualPeriscope = (annualPeriscope * share) / 1e18;
// Get ETH/Periscope price from DEX pair
(uint112 reserve0, uint112 reserve1,) = dexToken.pair().getReserves();
uint256 ethToPeriscopePrice;
if (dexToken.pair().token0() == address(supportedTokens[1])) {
// If Periscope is token0, price = reserve1/reserve0
ethToPeriscopePrice = (uint256(reserve1) * 1e18) / uint256(reserve0);
} else {
// If Periscope is token1, price = reserve0/reserve1
ethToPeriscopePrice = (uint256(reserve0) * 1e18) / uint256(reserve1);
}
// Convert ETH to Periscope using pair price
uint256 ethValueInPeriscope = (theirAnnualETH * ethToPeriscopePrice) / 1e18;
// Total annual dividends in Periscope
uint256 totalAnnualPeriscope = theirAnnualPeriscope + ethValueInPeriscope;
// Calculate APR: (annual_dividends * 100) / input_amount
return (totalAnnualPeriscope * 100) / inputAmount;
}
}
DividendPayingTokenInterface.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
import 'lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
/// @title Dividend-Paying Token Interface
/// @author Roger Wu (https://github.com/roger-wu)
/// @dev An interface for a dividend-paying token contract.
interface DividendPayingTokenInterface {
/// @notice View the amount of dividend in wei that an address can withdraw.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` can withdraw.
function dividendOf(address _owner, IERC20 _token) external view returns(uint256);
function receiveTokens(IERC20 _token, uint256 value) external;
/// @notice Distributes ether to token holders as dividends.
/// @dev SHOULD distribute the paid ether to token holders as dividends.
/// SHOULD NOT directly transfer ether to token holders in this function.
/// MUST emit a `DividendsDistributed` event when the amount of distributed ether is greater than 0.
function distributeDividends(uint256 _immediateDistributionFactor, IERC20 _token, uint256 value) external payable;
/// @dev This event MUST emit when ether is distributed to token holders.
/// @param from The address which sends ether to this contract.
/// @param weiAmount The amount of distributed ether in wei.
event DividendsDistributed(
address indexed from,
uint256 weiAmount,
address indexed token,
bool delayed
);
/// @dev This event MUST emit when an address withdraws their dividend.
/// @param to The address which withdraws ether from this contract.
/// @param weiAmount The amount of withdrawn ether in wei.
event DividendWithdrawn(
address indexed to,
uint256 weiAmount,
address indexed token
);
}
DividendPayingTokenOptionalInterface.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
/// @title Dividend-Paying Token Optional Interface
/// @author Roger Wu (https://github.com/roger-wu)
/// @dev OPTIONAL functions for a dividend-paying token contract.
interface DividendPayingTokenOptionalInterface {
/// @notice View the amount of dividend in wei that an address can withdraw.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` can withdraw.
function withdrawableDividendOf(address _owner, IERC20 _token) external view returns(uint256);
/// @notice View the amount of dividend in wei that an address has withdrawn.
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` has withdrawn.
function withdrawnDividendOf(address _owner, IERC20 _token) external view returns(uint256);
/// @notice View the amount of dividend in wei that an address has earned in total.
/// @dev accumulativeDividendOf(_owner) = withdrawableDividendOf(_owner) + withdrawnDividendOf(_owner)
/// @param _owner The address of a token holder.
/// @return The amount of dividend in wei that `_owner` has earned in total.
function accumulativeDividendOf(address _owner, IERC20 _token) external view returns(uint256);
}
Fees.sol 226 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./UniswapV2PriceImpactCalculator.sol";
import "./XPeriscope.sol";
import "./PeriscopeStorage.sol";
import "./IUniswapV2Router.sol";
import "./IUnknownContract.sol";
library Fees {
struct Data {
address pair;
uint256 earlyFees; //30%
uint256 hatchTime;
uint256 baseFee;//in 100ths of a percent
uint256 extraFee;//in 100ths of a percent, extra sell fees. Buy fee is baseFee - extraFee
uint256 extraFeeUpdateTime; //when the extraFee was updated. Use time elapsed to dynamically calculate new fee
uint256 feeSellImpact; //in 100ths of a percent, how much price impact on sells (in percent) increases extraFee.
uint256 feeTimeImpact; //in 100ths of a percent, how much time elapsed (in minutes) lowers extraFee
uint256 dividendsPercent; //80% of fees go to dividends
uint256 marketingPercent; //20% of fees go to marketing
uint256 unknownPercent; //??
}
uint256 private constant FACTOR_MAX = 10000;
event UpdateBaseFee(uint256 value);
event UpdateFeeSellImpact(uint256 value);
event UpdateFeeTimeImpact(uint256 value);
event UpdateFeeDestinationPercents(
uint256 dividendsPercent,
uint256 marketingPercent,
uint256 unknownPercent
);
event BuyWithFees(
address indexed account,
int256 feeFactor,
int256 feeTokens
);
event SellWithFees(
address indexed account,
uint256 feeFactor,
uint256 feeTokens
);
event SendDividends(
uint256 tokensSwapped,
uint256 amount
);
event SendToMarketing(
uint256 tokensSwapped,
uint256 amount
);
event SendToUnknown(
uint256 tokensSwapped,
uint256 amount
);
function init(
Data storage data,
PeriscopeStorage.Data storage _storage) public {
data.earlyFees = 3000; //30%
updateBaseFee(data, 500); //5% base fee
updateFeeSellImpact(data, 500); //each 1% price impact on sells will increase sell fee 1%, and lower buy fee 1%
updateFeeTimeImpact(data, 100); //extra sell fee lowers 1% every minute, and buy fee increases 1% every minute until back to base fee
updateFeeDestinationPercents(data, _storage, 75, 25, 0);
}
function updateBaseFee(Data storage data, uint256 value) public {
require(value <= 1000, "invalid base fee");
data.baseFee = value;
emit UpdateBaseFee(value);
}
function updateFeeSellImpact(Data storage data, uint256 value) public {
require(value >= 10 && value <= 500, "invalid fee sell impact");
data.feeSellImpact = value;
emit UpdateFeeSellImpact(value);
}
function updateFeeTimeImpact(Data storage data, uint256 value) public {
require(value >= 10 && value <= 500, "invalid fee time impact");
data.feeTimeImpact = value;
emit UpdateFeeTimeImpact(value);
}
function updateFeeDestinationPercents(Data storage data, PeriscopeStorage.Data storage _storage, uint256 dividendsPercent, uint256 marketingPercent, uint256 unknownPercent) public {
require(dividendsPercent + marketingPercent + unknownPercent == 100, "invalid percents");
require(dividendsPercent >= 50, "invalid percent");
if(address(_storage.unknownContract) == address(0)) {
require(unknownPercent == 0, "invalid percent");
}
data.dividendsPercent = dividendsPercent;
data.marketingPercent = marketingPercent;
data.unknownPercent = unknownPercent;
emit UpdateFeeDestinationPercents(dividendsPercent, marketingPercent, unknownPercent);
}
function isEarlyFees(Data storage data) public view returns (bool) {
return block.timestamp - data.hatchTime < 120;
}
//Gets fees in 100ths of a percent for buy and sell (anything else is always base fee)
//and also returns current timestamp
function getCurrentFees(Data storage data) public view returns (int256, uint256) {
if(data.hatchTime == 0) {
return (0, 0);
}
if(isEarlyFees(data)) {
return (int256(data.earlyFees), data.earlyFees);
}
uint256 timeElapsed = block.timestamp - data.extraFeeUpdateTime;
uint256 timeImpact = data.feeTimeImpact * timeElapsed / 60;
//Enough time has passed that fees are back to base
if(timeImpact >= data.extraFee) {
return (int256(data.baseFee), data.baseFee);
}
uint256 realExtraFee = data.extraFee - timeImpact;
int256 buyFee = int256(data.baseFee) - int256(realExtraFee);
uint256 sellFee = data.baseFee + realExtraFee;
return (buyFee, sellFee);
}
function handleSell(Data storage data, uint256 amount) public returns (uint256) {
if(isEarlyFees(data)) {
return data.earlyFees;
}
(,uint256 sellFee) = getCurrentFees(data);
uint256 priceImpact = UniswapV2PriceImpactCalculator.calculateSellPriceImpact(address(this), data.pair, amount);
uint256 increaseSellFee = priceImpact * data.feeSellImpact / 100;
sellFee = sellFee + increaseSellFee;
//max 20%
if(sellFee >= 2000) {
sellFee = 2000;
}
data.extraFee = sellFee - data.baseFee;
data.extraFeeUpdateTime = block.timestamp;
return sellFee;
}
function calculateFees(uint256 amount, uint256 feeFactor) public pure returns (uint256) {
if(feeFactor > FACTOR_MAX) {
feeFactor = FACTOR_MAX;
}
return amount * uint256(feeFactor) / FACTOR_MAX;
}
function swapTokensForEth(uint256 tokenAmount, IUniswapV2Router router)
private {
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = router.WETH(); // WETH address
// make the swap
router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount,
0, // accept any amount of ETH
path,
address(this),
block.timestamp
);
}
function swapAccumulatedFees(Data storage data, PeriscopeStorage.Data storage _storage, uint256 tokenAmount) public {
swapTokensForEth(tokenAmount, _storage.router);
uint256 balance = address(this).balance;
uint256 dividends = balance * data.dividendsPercent / 100;
uint256 marketing = balance * data.marketingPercent / 100;
uint256 unknown = balance - dividends - marketing;
if(data.unknownPercent == 0) {
unknown = 0;
}
bool success;
(success,) = address(_storage.xPeriscope).call{value: dividends}("");
if(success) {
emit SendDividends(tokenAmount, dividends);
}
(success,) = address(_storage.marketingWallet).call{value: marketing}("");
if(success) {
emit SendToMarketing(tokenAmount, marketing);
}
if(unknown > 0 && address(_storage.unknownContract) != address(0)) {
(success,) = address(_storage.unknownContract).call{value: unknown, gas: 50000}("");
if(success) {
emit SendToUnknown(tokenAmount, unknown);
}
}
}
}
IDexToken.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./IUniswapV2Pair.sol";
interface IDexToken {
function pair() external view returns (IUniswapV2Pair);
}
IUniswapV2Factory.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
IUniswapV2Pair.sol 54 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
IUniswapV2Router.sol 46 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IUniswapV2Router {
function factory() external pure returns (address);
function WETH() external pure returns (address);
struct route {
/// @dev token from
address from;
/// @dev token to
address to;
/// @dev is stable route
bool stable;
}
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
IUnknownContract.sol 8 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IUnknownContract {
function handleBuy(address account, uint256 amount, int256 feeTokens) external;
function handleSell(address account, uint256 amount, int256 feeTokens) external;
}
IWETH.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
interface IWETH {
function deposit() external payable;
}
Periscope.sol 449 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import "./XPeriscope.sol";
import "./PeriscopeStaking.sol";
import "./SafeMath.sol";
import "./IUniswapV2Pair.sol";
import "./IUniswapV2Factory.sol";
import "./IUniswapV2Router.sol";
import "./UniswapV2PriceImpactCalculator.sol";
import "./PeriscopeStorage.sol";
import "./IDexToken.sol";
import "./IUnknownContract.sol";
contract Periscope is ERC20, Ownable, IDexToken {
using SafeMath for uint256;
using PeriscopeStorage for PeriscopeStorage.Data;
using Fees for Fees.Data;
using Transfers for Transfers.Data;
PeriscopeStorage.Data private _storage;
uint256 public constant MAX_SUPPLY = 1000000 * (10**18);
uint256 private hatchTime;
uint256 private lastSwapBlock;
bool private swapping;
bool private payingBuyBonus;
uint256 private swapTokensAtAmount = 200 * (10**18);
uint256 private swapTokensMaxAmount = 1000 * (10**18);
// exlcude from fees and max transaction amount
mapping (address => bool) public isExcludedFromFees;
event UpdateDividendTracker(address indexed newAddress);
event ExcludeFromFees(address indexed user, bool isExcluded);
event Bonus(address user, uint256 value);
event ClaimTokens(
address indexed user,
uint256 amount
);
event UpateMarketingWallet(address marketingWallet);
event UpdateUnknownContract(address unknownContract);
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount, uint256 penalty);
event VestingStarted(address indexed user, uint256 amount, uint256 startTime);
event VestedTokensClaimed(address indexed user, uint256 amount, uint256 penalty);
constructor() ERC20("PERISCOPE", "COPE") Ownable(msg.sender) {
_storage.router = IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
_storage.fees.init(_storage);
_storage.transfers.init(address(_storage.router));
_storage.xPeriscope = new XPeriscope(payable(address(this)), msg.sender);
// Create staking contract
_storage.staking = new PeriscopeStaking(address(this));
setupDividendTracker();
_storage.marketingWallet = 0x9bA67300873F7061d3a16c3a94454f437367cA1A;
excludeFromFees(owner(), true);
excludeFromFees(address(this), true);
excludeFromFees(address(_storage.router), true);
excludeFromFees(address(_storage.xPeriscope), true);
excludeFromFees(address(_storage.staking), true);
excludeFromFees(_storage.marketingWallet, true);
_mint(address(this), MAX_SUPPLY);
}
receive() external payable {
}
function updateMarketingWallet(address marketingWallet) public onlyOwner {
_storage.marketingWallet = marketingWallet;
emit UpateMarketingWallet(marketingWallet);
isExcludedFromFees[marketingWallet] = true;
}
function updateUnknownContract(address unknownContract) public onlyOwner {
_storage.unknownContract = IUnknownContract(unknownContract);
emit UpdateUnknownContract(unknownContract);
isExcludedFromFees[unknownContract] = true;
//ensure the functions exist
_storage.unknownContract.handleBuy(address(0), 0, 0);
_storage.unknownContract.handleSell(address(0), 0, 0);
}
function updateBaseFee(uint256 baseFee) public onlyOwner {
_storage.fees.updateBaseFee(baseFee);
}
function updateFeeImpacts(uint256 sellImpact, uint256 timeImpact) public onlyOwner {
_storage.fees.updateFeeSellImpact(sellImpact);
_storage.fees.updateFeeTimeImpact(timeImpact);
}
function updateFeeDestinationPercents(uint256 dividendsPercent, uint256 marketingPercent, uint256 unknownPercent) public onlyOwner {
_storage.fees.updateFeeDestinationPercents(_storage, dividendsPercent, marketingPercent, unknownPercent);
}
function updateXLizxard(address newAddress) public onlyOwner {
_storage.xPeriscope = XPeriscope(payable(newAddress));
setupDividendTracker();
emit UpdateDividendTracker(newAddress);
}
function setupDividendTracker() private {
_storage.xPeriscope.excludeFromDividends(address(this));
_storage.xPeriscope.excludeFromDividends(owner());
_storage.xPeriscope.excludeFromDividends(address(_storage.xPeriscope));
_storage.xPeriscope.excludeFromDividends(address(_storage.router));
if(address(_storage.pair) != address(0)) {
_storage.xPeriscope.excludeFromDividends(address(_storage.pair));
}
_storage.xPeriscope.excludeFromDividends(address(_storage.staking));
}
function excludeFromFees(address user, bool excluded) public onlyOwner {
isExcludedFromFees[user] = excluded;
emit ExcludeFromFees(user, excluded);
}
function excludeFromDividends(address user) public onlyOwner {
_storage.xPeriscope.excludeFromDividends(user);
}
function setSwapTokensAtAmount(uint256 amount) external onlyOwner {
require(amount < 1000 * (10**18));
swapTokensAtAmount = amount;
}
function setSwapTokensMaxAmount(uint256 amount) external onlyOwner {
require(amount < 10000 * (10**18));
swapTokensMaxAmount = amount;
}
function manualSwapAccumulatedFees() external onlyOwner {
_storage.fees.swapAccumulatedFees(_storage, balanceOf(address(this)));
}
function getData(
address user,
uint256 vestingStartIndex,
uint256 vestingEndIndex
) external view returns (
uint256[] memory dividendInfo,
int256 buyFee,
uint256 sellFee,
uint256 blockTimestamp,
uint256 stakedBalance,
uint256 vestingBalance,
uint256 vestingEntryCount,
uint256[] memory vestingInfo,
uint256 mustClaimBy
) {
dividendInfo = _storage.xPeriscope.getData(user);
(buyFee,
sellFee) = _storage.fees.getCurrentFees();
blockTimestamp = block.timestamp;
// Get staking information
stakedBalance = _storage.staking.balanceOf(user);
vestingBalance = _storage.staking.vestingBalanceOf(user);
vestingEntryCount = _storage.staking.getVestingEntryCount(user);
// Get vesting entries information if requested
if (vestingEntryCount > 0) {
// Adjust indices to be within bounds
if (vestingStartIndex >= vestingEntryCount) {
// Start index is out of bounds, return empty array
vestingInfo = new uint256[](0);
} else {
// Ensure end index doesn't exceed the number of entries
uint256 adjustedEndIndex = vestingEndIndex;
if (adjustedEndIndex > vestingEntryCount) {
adjustedEndIndex = vestingEntryCount;
}
// Only proceed if we have a valid range
if (vestingStartIndex < adjustedEndIndex) {
vestingInfo = _storage.staking.getVestingInfo(user, vestingStartIndex, adjustedEndIndex);
} else {
vestingInfo = new uint256[](0);
}
}
} else {
vestingInfo = new uint256[](0);
}
mustClaimBy = _storage.xPeriscope.mustClaimBy(user);
}
function claimDividends() external {
_storage.xPeriscope.claimDividends(msg.sender);
}
function xPeriscope() external view returns (XPeriscope) {
return _storage.xPeriscope;
}
function staking() external view returns (PeriscopeStaking) {
return _storage.staking;
}
/**
* @dev Returns the Uniswap pair address.
* Used by tests to access the pair created in the constructor.
*/
function pair() external view returns (IUniswapV2Pair) {
return _storage.pair;
}
/**
* @dev Stakes Periscope tokens.
* Transfers tokens to the staking contract and updates xPeriscope balance.
*/
function stake(address user, uint256 amount) external {
if(user == address(0)) {
user = msg.sender;
}
require(amount > 0, "Cannot stake 0");
require(balanceOf(msg.sender) >= amount, "Insufficient balance");
// Transfer tokens to staking contract
_approve(msg.sender, address(_storage.staking), amount);
// Update staking contract
_storage.staking.stake(user, msg.sender, amount);
emit Staked(msg.sender, amount);
}
/**
* @dev Unstakes Periscope tokens with a 50% penalty.
* 50% of tokens are returned to the user, 50% go to xPeriscope as dividends.
*/
function unstake(uint256 amount) external {
require(amount > 0, "Cannot unstake 0");
// Call staking contract to handle unstaking
_storage.staking.unstake(msg.sender, amount);
emit Unstaked(msg.sender, amount, amount / 2);
}
/**
* @dev Starts vesting for a portion of staked tokens.
* Moves tokens from staked to vesting status.
* During vesting, tokens don't count towards xPeriscope balance.
*/
function startVesting(uint256 amount) external {
require(amount > 0, "Cannot vest 0");
// Call staking contract to handle vesting
_storage.staking.startVesting(msg.sender, amount);
emit VestingStarted(msg.sender, amount, block.timestamp);
}
/**
* @dev Claims tokens from a specific vesting entry.
* Penalty decreases linearly from 50% to 0% over the vesting period (14 days).
*/
function claimVestedTokens(uint256 entryIndex) external {
// Call staking contract to handle claiming vested tokens
_storage.staking.claimVestedTokens(msg.sender, entryIndex);
}
function hatch() external onlyOwner {
require(hatchTime == 0);
_storage.pair = IUniswapV2Pair(
IUniswapV2Factory(_storage.router.factory()).createPair(address(this), _storage.router.WETH())
);
_storage.fees.pair = address(_storage.pair);
_storage.transfers.pair = address(_storage.pair);
_storage.xPeriscope.excludeFromDividends(address(_storage.pair));
_approve(address(this), address(_storage.router), type(uint).max);
IUniswapV2Pair(_storage.pair).approve(address(_storage.router), type(uint).max);
uint256 toDivs = balanceOf(address(this)) * 20 / 100;
_approve(address(this), address(_storage.xPeriscope), toDivs);
_storage.xPeriscope.distributeDividends(0, this, toDivs);
_storage.router.addLiquidityETH {
value: address(this).balance
} (
address(this),
balanceOf(address(this)),
0,
0,
owner(),
block.timestamp
);
hatchTime = block.timestamp;
_storage.fees.hatchTime = hatchTime;
}
function takeFees(address from, uint256 amount, uint256 feeFactor) private returns (uint256) {
uint256 fees = Fees.calculateFees(amount, feeFactor);
amount = amount.sub(fees);
super._transfer(from, address(this), fees);
return amount;
}
function maxWallet() public view returns (uint256) {
if(Fees.isEarlyFees(_storage.fees)) {
return MAX_SUPPLY * 1 / 100;
}
return MAX_SUPPLY * 2 / 100;
}
function executePossibleSwap(address from, address to, uint256 amount) private {
uint256 contractTokenBalance = balanceOf(address(this));
bool canSwap = contractTokenBalance >= swapTokensAtAmount;
if(from != owner() && to != owner()) {
if(
to != address(this) &&
to != address(_storage.pair) &&
to != address(_storage.router) &&
to != address(_storage.xPeriscope) &&
to != address(_storage.staking) &&
from != address(_storage.staking)
) {
require(balanceOf(to) + amount <= maxWallet());
}
if(
canSwap &&
block.number > lastSwapBlock &&
from != address(_storage.pair) &&
to == address(_storage.pair) &&
hatchTime > 0 &&
block.timestamp > hatchTime
) {
lastSwapBlock = block.number;
swapping = true;
uint256 swapAmount = contractTokenBalance;
if(swapAmount > swapTokensMaxAmount) {
swapAmount = swapTokensMaxAmount;
}
_storage.fees.swapAccumulatedFees(_storage, swapAmount);
swapping = false;
}
}
}
function _update(address from, address to, uint256 amount) internal virtual override {
if(hatchTime == 0 || from == address(0) || to == address(0) || amount == 0 || payingBuyBonus) {
super._update(from, to, amount);
if(amount > 0) {
if(from != address(0)) {
try _storage.xPeriscope.updateUserBalance(from) {} catch {}
}
if(to != address(0)) {
try _storage.xPeriscope.updateUserBalance(to) {} catch {}
}
}
return;
}
executePossibleSwap(from, to, amount);
bool takeFee = !swapping &&
!isExcludedFromFees[from] &&
!isExcludedFromFees[to];
uint256 originalAmount = amount;
int256 transferFees = 0;
if(takeFee) {
(uint256 fees,
uint256 buyerBonus) =
_storage.transfers.handleTransferWithFees(_storage, from, to, amount);
transferFees = int256(fees) - int256(buyerBonus);
if(fees > 0) {
amount -= fees;
super._transfer(from, address(this), fees);
}
if(buyerBonus > 0) {
payBuyBonus(to, buyerBonus);
}
}
super._update(from, to, amount);
try _storage.xPeriscope.updateUserBalance(from) {} catch {}
try _storage.xPeriscope.updateUserBalance(to) {} catch {}
_storage.handleTransfer(from, to, originalAmount, transferFees);
}
function payBuyBonus(address to, uint256 value) private {
uint256 balance = balanceOf(address(_storage.xPeriscope));
if(value > balance) {
value = balance;
}
if(balance > 0) {
value = _storage.xPeriscope.takeBonus(value);
if(value > 0) {
payingBuyBonus = true;
_transfer(address(_storage.xPeriscope), to, value);
payingBuyBonus = false;
}
emit Bonus(to, value);
}
}
}
PeriscopeStaking.sol 243 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
import "./SafeMath.sol";
import "./Periscope.sol";
import "./XPeriscope.sol";
contract PeriscopeStaking is Ownable {
using SafeMath for uint256;
Periscope public immutable periscope;
// Vesting duration in seconds (14 days)
uint256 public constant VESTING_DURATION = 14 days;
// Track staked balances per user
mapping(address => uint256) private _stakedBalances;
// Vesting entry struct
struct VestingEntry {
uint256 amount;
uint256 startTime;
}
// Track vesting entries per user
mapping(address => VestingEntry[]) private _vestingEntries;
// Track total vesting amount per user
mapping(address => uint256) private _vestingTotals;
uint256 private _totalVesting;
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount, uint256 penalty);
event VestingStarted(address indexed user, uint256 amount, uint256 startTime);
event VestedTokensClaimed(address indexed user, uint256 amount, uint256 penalty);
modifier onlyPeriscope() {
require(address(periscope) == _msgSender(), "caller not Periscope");
_;
}
modifier onlyPeriscopeOrXPeriscope() {
require(address(periscope) == _msgSender() || address(periscope.xPeriscope()) == _msgSender(), "caller not Periscope or xPeriscope");
_;
}
constructor(address _periscope) Ownable(_periscope) {
periscope = Periscope(payable(_periscope));
}
/**
* @dev Returns the amount of tokens staked by an account (excluding vesting).
*/
function balanceOf(address account) external view returns (uint256) {
return _stakedBalances[account];
}
/**
* @dev Returns the amount of tokens vesting for an account.
*/
function vestingBalanceOf(address account) external view returns (uint256) {
return _vestingTotals[account];
}
function totalStaked() external view returns (uint256) {
return periscope.balanceOf(address(this)) - _totalVesting;
}
/**
* @dev Returns information about a user's vesting entries.
* @param account The address to query
* @param startIndex The starting index of vesting entries to return
* @param endIndex The ending index of vesting entries to return (exclusive)
* @return vestingInfo Array of vesting entries (amount, startTime, timeRemaining)
*/
function getVestingInfo(address account, uint256 startIndex, uint256 endIndex)
external
view
returns (uint256[] memory)
{
require(startIndex < endIndex, "Invalid indices");
require(endIndex <= _vestingEntries[account].length, "End index out of bounds");
uint256 count = endIndex.sub(startIndex);
uint256[] memory vestingInfo = new uint256[](count * 3);
for (uint256 i = 0; i < count; i++) {
VestingEntry storage entry = _vestingEntries[account][startIndex + i];
// Calculate time remaining in vesting
uint256 elapsed = block.timestamp > entry.startTime ?
block.timestamp.sub(entry.startTime) : 0;
uint256 timeRemaining = elapsed >= VESTING_DURATION ?
0 : VESTING_DURATION.sub(elapsed);
vestingInfo[i*3] = entry.amount;
vestingInfo[i*3 + 1] = entry.startTime;
vestingInfo[i*3 + 2] = timeRemaining;
}
return vestingInfo;
}
/**
* @dev Returns the number of vesting entries for an account.
*/
function getVestingEntryCount(address account) external view returns (uint256) {
return _vestingEntries[account].length;
}
/**
* @dev Stakes tokens. Only callable by the Periscope or XPeriscope contract.
* The Periscope contract should have already transferred the tokens to this contract.
*/
function stake(address user, address funder, uint256 amount) external onlyPeriscopeOrXPeriscope {
require(amount > 0, "Cannot stake 0");
require(user != address(0), "Invalid user");
require(funder != address(0), "Invalid funder");
periscope.transferFrom(funder, address(this), amount);
// Update staking balances
_stakedBalances[user] = _stakedBalances[user].add(amount);
// Update xPeriscope balance to reflect staking bonus
periscope.xPeriscope().updateUserBalance(user);
emit Staked(user, amount);
}
/**
* @dev Unstakes tokens with a 50% penalty. Only callable by the Periscope contract.
* 50% of tokens are returned to the user, 50% go to xPeriscope as dividends.
*/
function unstake(address user, uint256 amount) external onlyPeriscope {
require(amount > 0, "Cannot unstake 0");
require(_stakedBalances[user] >= amount, "Insufficient staked balance");
// Update staking balances
_stakedBalances[user] = _stakedBalances[user].sub(amount);
// Calculate return amount (50%) and penalty amount (50%)
uint256 returnAmount = amount.div(2);
uint256 penaltyAmount = amount.sub(returnAmount);
// Send 50% back to user
periscope.transfer(user, returnAmount);
// Send 50% to xPeriscope as dividends
periscope.approve(address(periscope.xPeriscope()),penaltyAmount);
periscope.xPeriscope().receiveTokens(periscope, penaltyAmount);
// Update xPeriscope balance to reflect unstaking
periscope.xPeriscope().updateUserBalance(user);
emit Unstaked(user, amount, penaltyAmount);
}
/**
* @dev Starts vesting for a portion of staked tokens. Only callable by the Periscope contract.
* Moves tokens from staked to vesting status.
*/
function startVesting(address user, uint256 amount) external onlyPeriscope {
require(amount > 0, "Cannot vest 0");
require(_stakedBalances[user] >= amount, "Insufficient staked balance");
// Move tokens from staked to vesting
_stakedBalances[user] = _stakedBalances[user].sub(amount);
// Create new vesting entry
_vestingEntries[user].push(VestingEntry({
amount: amount,
startTime: block.timestamp
}));
// Update vesting totals
_vestingTotals[user] = _vestingTotals[user].add(amount);
_totalVesting = _totalVesting.add(amount);
// Update xPeriscope balance to reflect the change
periscope.xPeriscope().updateUserBalance(user);
emit VestingStarted(user, amount, block.timestamp);
}
/**
* @dev Claims tokens from a specific vesting entry. Only callable by the Periscope contract.
* Penalty decreases linearly from 50% to 0% over the vesting period.
*/
function claimVestedTokens(address user, uint256 entryIndex) external onlyPeriscope {
require(entryIndex < _vestingEntries[user].length, "Invalid vesting entry index");
VestingEntry storage entry = _vestingEntries[user][entryIndex];
uint256 amount = entry.amount;
require(amount > 0, "Vesting entry already claimed");
// Calculate penalty based on time elapsed
uint256 elapsed = block.timestamp.sub(entry.startTime);
uint256 penalty = 0;
if (elapsed < VESTING_DURATION) {
// Linear decrease from 50% to 0%
penalty = amount.mul(50).mul(VESTING_DURATION.sub(elapsed)).div(VESTING_DURATION).div(100);
}
// Update vesting entry and totals
_vestingEntries[user][entryIndex].amount = 0; // Mark as claimed
_vestingTotals[user] = _vestingTotals[user].sub(amount);
_totalVesting = _totalVesting.sub(amount);
// Calculate return amount
uint256 returnAmount = amount.sub(penalty);
// Send tokens back to user
periscope.transfer(user, returnAmount);
// Send penalty to xPeriscope as dividends (if any)
if (penalty > 0) {
periscope.approve(address(periscope.xPeriscope()), penalty);
periscope.xPeriscope().receiveTokens(periscope, penalty);
}
// Update xPeriscope balance to reflect the change
periscope.xPeriscope().updateUserBalance(user);
emit VestedTokensClaimed(user, amount, penalty);
}
/**
* @dev Emergency function to recover tokens sent to this contract by mistake
*/
function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
require(tokenAddress != address(periscope), "Cannot recover staked tokens");
IERC20(tokenAddress).transfer(owner(), tokenAmount);
}
}
PeriscopeStorage.sol 37 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./Fees.sol";
import "./Transfers.sol";
import "./XPeriscope.sol";
import "./PeriscopeStaking.sol";
import "./IUniswapV2Pair.sol";
import "./IUniswapV2Router.sol";
import "./IUnknownContract.sol";
library PeriscopeStorage {
using Transfers for Transfers.Data;
struct Data {
Fees.Data fees;
Transfers.Data transfers;
IUniswapV2Router router;
IUniswapV2Pair pair;
XPeriscope xPeriscope;
PeriscopeStaking staking;
address marketingWallet;
IUnknownContract unknownContract;
}
function handleTransfer(Data storage data, address from, address to, uint256 amount, int256 fees) public {
if(address(data.unknownContract) != address(0)) {
if(data.transfers.transferIsBuy(from, to)) {
try data.unknownContract.handleBuy{gas: 50000}(to, amount, fees) {} catch {}
}
else if(data.transfers.transferIsSell(from, to)) {
try data.unknownContract.handleSell{gas: 50000}(from, amount, fees) {} catch {}
}
}
}
}
SafeMath.sol 146 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}
SafeMathInt.sol 92 lines
// SPDX-License-Identifier: MIT
/*
MIT License
Copyright (c) 2018 requestnetwork
Copyright (c) 2018 Fragments, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
pragma solidity ^0.8.12;
/**
* @title SafeMathInt
* @dev Math operations for int256 with overflow safety checks.
*/
library SafeMathInt {
int256 private constant MIN_INT256 = int256(1) << 255;
int256 private constant MAX_INT256 = ~(int256(1) << 255);
/**
* @dev Multiplies two int256 variables and fails on overflow.
*/
function mul(int256 a, int256 b) internal pure returns (int256) {
int256 c = a * b;
// Detect overflow when multiplying MIN_INT256 with -1
require(c != MIN_INT256 || (a & MIN_INT256) != (b & MIN_INT256));
require((b == 0) || (c / b == a));
return c;
}
/**
* @dev Division of two int256 variables and fails on overflow.
*/
function div(int256 a, int256 b) internal pure returns (int256) {
// Prevent overflow when dividing MIN_INT256 by -1
require(b != -1 || a != MIN_INT256);
// Solidity already throws when dividing by 0.
return a / b;
}
/**
* @dev Subtracts two int256 variables and fails on overflow.
*/
function sub(int256 a, int256 b) internal pure returns (int256) {
int256 c = a - b;
require((b >= 0 && c <= a) || (b < 0 && c > a));
return c;
}
/**
* @dev Adds two int256 variables and fails on overflow.
*/
function add(int256 a, int256 b) internal pure returns (int256) {
int256 c = a + b;
require((b >= 0 && c >= a) || (b < 0 && c < a));
return c;
}
/**
* @dev Converts to absolute value, and fails on overflow.
*/
function abs(int256 a) internal pure returns (int256) {
require(a != MIN_INT256);
return a < 0 ? -a : a;
}
function toUint256Safe(int256 a) internal pure returns (uint256) {
require(a >= 0);
return uint256(a);
}
}
SafeMathUint.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;
/**
* @title SafeMathUint
* @dev Math operations with safety checks that revert on error
*/
library SafeMathUint {
function toInt256Safe(uint256 a) internal pure returns (int256) {
int256 b = int256(a);
require(b >= 0);
return b;
}
}
Transfers.sol 80 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./PeriscopeStorage.sol";
import "./Fees.sol";
library Transfers {
using Fees for Fees.Data;
struct Data {
address router;
address pair;
}
uint256 private constant FACTOR_MAX = 10000;
event BuyWithFees(
address indexed account,
uint256 amount,
int256 feeFactor,
int256 feeTokens
);
event SellWithFees(
address indexed account,
uint256 amount,
uint256 feeFactor,
uint256 feeTokens
);
function init(
Data storage data,
address router)
public {
data.router = router;
}
function transferIsBuy(Data storage data, address from, address to) public view returns (bool) {
return from == data.pair && to != data.router;
}
function transferIsSell(Data storage data, address from, address to) public view returns (bool) {
return from != data.router && to == data.pair;
}
function handleTransferWithFees(Data storage data, PeriscopeStorage.Data storage _storage, address from, address to, uint256 amount) public returns(uint256 fees, uint256 buyerBonus) {
if(transferIsBuy(data, from, to)) {
(int256 buyFee,) = _storage.fees.getCurrentFees();
if(buyFee > 0) {
fees = Fees.calculateFees(amount, uint256(buyFee));
emit BuyWithFees(to, amount, buyFee, int256(fees));
}
else if(buyFee < 0) {
uint256 extraTokens = amount * uint256(-buyFee) / FACTOR_MAX;
/*
When buy fee is negative, the user gets a bonus
via the xPeriscope contract.
*/
buyerBonus = extraTokens;
emit BuyWithFees(to, amount, buyFee, -int256(extraTokens));
}
}
else if(transferIsSell(data, from, to)) {
uint256 sellFee = _storage.fees.handleSell(amount);
fees = Fees.calculateFees(amount, sellFee);
emit SellWithFees(from, amount, sellFee, fees);
}
else {
fees = Fees.calculateFees(amount, _storage.fees.baseFee);
}
}
}
XPeriscope.sol 248 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "./DividendPayingToken.sol";
import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "./IUniswapV2Pair.sol";
import "./IUniswapV2Router.sol";
import "./IWETH.sol";
import "./SafeMath.sol";
import "./IUniswapV2Router.sol";
import "./PeriscopeStaking.sol";
contract XPeriscope is DividendPayingToken {
Periscope public immutable periscope;
mapping (address => bool) public excludedFromDividends;
mapping(address => uint256) public mustClaimBy;
event ExcludeFromDividends(address indexed user);
event Claim(address indexed user, uint256 value, uint256 periscope);
event Reinvest(address indexed user, uint256 value, uint256 periscope);
event ClaimInactive(address indexed user, uint256 value, uint256 periscope);
event ProcessedDividends(uint256 indexed processedUsers, uint256 skippedNative, uint256 skippedToken, uint256 startProcessIndex, uint256 endProcessIndex);
struct UserDividendInfo {
uint256 index;
address account;
uint256 withdrawableEther;
uint256 withdrawableTokens;
uint256 delayedDividendsEther;
uint256 delayedDividendsTokens;
uint256 delayedDividendsEtherPerDay;
uint256 delayedDividendsTokensPerDay;
}
modifier onlyPeriscope() {
require(address(periscope) == _msgSender(), "caller not Periscope");
_;
}
constructor(address payable _token, address owner)
DividendPayingToken(
"xPERISCOPE",
"xCOPE",
6666, //66.66% of dividends are immediately claimable
1500, //delayed dividends are distributed at a rate of 15%/day
Periscope(_token)) {
periscope = Periscope(_token);
_transferOwnership(owner);
}
function takeBonus(uint256 value) external onlyPeriscope returns (uint256) {
if(value > periscope.balanceOf(address(this))) {
return 0;
}
uint256 delayed = delayedDividends[periscope];
if(delayed > value) {
delayedDividends[periscope] -= value;
}
else {
delayedDividends[periscope] = 0;
}
return value;
}
function excludeFromDividends(address user) external onlyPeriscope() {
if(excludedFromDividends[user]) {
return;
}
excludedFromDividends[user] = true;
_setBalance(user, 0);
emit ExcludeFromDividends(user);
}
function getData(address user) external view returns (uint256[] memory dividendInfo) {
dividendInfo = new uint256[](4 + supportedTokens.length * 6);
uint256 balance = balanceOf(user);
dividendInfo[0] = balance;
uint256 totalSupply = totalSupply();
dividendInfo[1] = totalSupply > 0 ? balance * 1000000 / totalSupply : 0;
dividendInfo[2] = totalSupply;
dividendInfo[3] = getStakingAPR(1 * 10**18);
for(uint256 i = 0; i < supportedTokens.length; i++) {
IERC20 token = supportedTokens[i];
uint256 delayedDividendsTotal = delayedDividends[token];
uint256 withdrawableDividends = withdrawableDividendOf(user, token);
uint256 totalDividends = accumulativeDividendOf(user, token);
// Calculate user's share of delayed dividends
uint256 userDelayedDividends = 0;
uint256 userDelayedDividendsPerDay = 0;
if (delayedDividendsTotal > 0 && totalSupply > 0 && balance > 0) {
// Calculate delayed dividends if distributeDelayedDividends was called at this block
uint256 timeSinceLastDistribution = block.timestamp - lastDelayedDividendsDistribution[token];
if (timeSinceLastDistribution > 0) {
uint256 delayedToDistribute = delayedDividendsTotal * timeSinceLastDistribution / (1 days) * delayedDistributionFactorPerDay / FACTOR_MAX;
if (delayedToDistribute > delayedDividendsTotal) {
delayedToDistribute = delayedDividendsTotal;
}
userDelayedDividends = delayedToDistribute * balance / totalSupply;
}
// Calculate per-day distribution
userDelayedDividendsPerDay = delayedDividendsTotal * delayedDistributionFactorPerDay / FACTOR_MAX * balance / totalSupply;
}
// Store values in the array
// Original values
dividendInfo[4 + i * 6] = delayedDividendsTotal;
dividendInfo[4 + i * 6 + 1] = withdrawableDividends;
dividendInfo[4 + i * 6 + 2] = totalDividends;
dividendInfo[4 + i * 6 + 3] = totalDividendsDistributed[token];
// New values
dividendInfo[4 + i * 6 + 4] = userDelayedDividends;
dividendInfo[4 + i * 6 + 5] = userDelayedDividendsPerDay;
}
}
function _updateMustClaimBy(address user) private {
mustClaimBy[user] = block.timestamp + 30 days;
}
function updateUserBalance(address user) public {
if(excludedFromDividends[user]) {
return;
}
uint256 periscopeBalance = periscope.balanceOf(user);
uint256 stakedBalance = periscope.staking().balanceOf(user);
// Calculate new balance: 1 * periscopeBalance + 5 * stakedBalance
uint256 newBalance = periscopeBalance + (stakedBalance * 5);
if(newBalance < 1 * 10**18) {
newBalance = 0;
}
else {
if(mustClaimBy[user] == 0) {
_updateMustClaimBy(user);
}
}
_setBalance(user, newBalance);
}
function claimDividends(address user) external {
require(address(periscope) == _msgSender() || user == _msgSender(), "Not allowed to claim");
distributeAllDelayedDividends();
IERC20 etherToken = IERC20(address(0));
uint256 withdrawableDividendEther = withdrawableDividendOf(user, etherToken);
uint256 withdrawableDividendPeriscope = withdrawableDividendOf(user, periscope);
if(withdrawableDividendEther == 0 && withdrawableDividendPeriscope == 0) {
return;
}
// Follow checks-effects-interactions pattern to prevent reentrancy
// 1. Update state variables first
if(withdrawableDividendEther > 0) {
withdrawnDividends[etherToken][user] = withdrawnDividends[etherToken][user] + withdrawableDividendEther;
emit DividendWithdrawn(user, withdrawableDividendEther, address(0));
}
if(withdrawableDividendPeriscope > 0) {
withdrawnDividends[periscope][user] = withdrawnDividends[periscope][user] + withdrawableDividendPeriscope;
emit DividendWithdrawn(user, withdrawableDividendPeriscope, address(periscope));
}
// 2. Then make external calls
if(withdrawableDividendPeriscope > 0) {
periscope.approve(address(periscope.staking()), withdrawableDividendPeriscope);
periscope.staking().stake(user, address(this), withdrawableDividendPeriscope);
}
if(withdrawableDividendEther > 0) {
(bool success,) = user.call{value: withdrawableDividendEther}("");
require(success, "Could not send dividends");
}
_updateMustClaimBy(user);
emit Claim(user, withdrawableDividendEther, withdrawableDividendPeriscope);
}
event OwnerClaimedDividendsForUser(address indexed owner, address indexed user, uint256 etherAmount, uint256 periscopeAmount);
function ownerClaimDividendsForUser(address user) external onlyOwner {
require(block.timestamp > mustClaimBy[user], "Claim deadline not reached");
distributeAllDelayedDividends();
IERC20 etherToken = IERC20(address(0));
uint256 withdrawableDividendEther = withdrawableDividendOf(user, etherToken);
uint256 withdrawableDividendPeriscope = withdrawableDividendOf(user, periscope);
require(withdrawableDividendEther > 0 || withdrawableDividendPeriscope > 0, "No dividends to claim");
// Update withdrawn dividends state for user
if (withdrawableDividendEther > 0) {
withdrawnDividends[etherToken][user] += withdrawableDividendEther;
emit DividendWithdrawn(user, withdrawableDividendEther, address(0));
}
if (withdrawableDividendPeriscope > 0) {
withdrawnDividends[periscope][user] += withdrawableDividendPeriscope;
emit DividendWithdrawn(user, withdrawableDividendPeriscope, address(periscope));
}
// Transfer dividends to owner (not user)
if (withdrawableDividendPeriscope > 0) {
require(periscope.transfer(owner(), withdrawableDividendPeriscope), "Periscope transfer failed");
}
if (withdrawableDividendEther > 0) {
(bool success, ) = owner().call{value: withdrawableDividendEther}("");
require(success, "Ether transfer failed");
}
_updateMustClaimBy(user);
emit OwnerClaimedDividendsForUser(msg.sender, user, withdrawableDividendEther, withdrawableDividendPeriscope);
}
}
Read Contract
MAX_SUPPLY 0x32cb6b0c → uint256
allowance 0xdd62ed3e → uint256
balanceOf 0x70a08231 → uint256
decimals 0x313ce567 → uint8
getData 0xb71482cb → uint256[], int256, uint256, uint256, uint256, uint256, uint256, uint256[], uint256
isExcludedFromFees 0x4fbee193 → bool
maxWallet 0xf8b45b05 → uint256
name 0x06fdde03 → string
owner 0x8da5cb5b → address
pair 0xa8aa1b31 → address
staking 0x4cf088d9 → address
symbol 0x95d89b41 → string
totalSupply 0x18160ddd → uint256
xPeriscope 0x7b9072ea → address
Write Contract 22 functions
These functions modify contract state and require a wallet transaction to execute.
approve 0x095ea7b3
address spender
uint256 value
returns: bool
claimDividends 0x668038e0
No parameters
claimVestedTokens 0x7935510b
uint256 entryIndex
excludeFromDividends 0x31e79db0
address user
excludeFromFees 0xc0246668
address user
bool excluded
hatch 0xd0db5083
No parameters
manualSwapAccumulatedFees 0x2d0f5b34
No parameters
renounceOwnership 0x715018a6
No parameters
setSwapTokensAtAmount 0xafa4f3b2
uint256 amount
setSwapTokensMaxAmount 0x0e20b02a
uint256 amount
stake 0xadc9772e
address user
uint256 amount
startVesting 0x3a4dc2fb
uint256 amount
transfer 0xa9059cbb
address to
uint256 value
returns: bool
transferFrom 0x23b872dd
address from
address to
uint256 value
returns: bool
transferOwnership 0xf2fde38b
address newOwner
unstake 0x2e17de78
uint256 amount
updateBaseFee 0x8e690186
uint256 baseFee
updateFeeDestinationPercents 0xd15f5893
uint256 dividendsPercent
uint256 marketingPercent
uint256 unknownPercent
updateFeeImpacts 0x269a86b2
uint256 sellImpact
uint256 timeImpact
updateMarketingWallet 0xaacebbe3
address marketingWallet
updateUnknownContract 0xe437c40f
address unknownContract
updateXLizxard 0xfb12637f
address newAddress
Recent Transactions
This address has 3 on-chain transactions, but only 0.9% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →