Address Contract Partially Verified
Address
0xEe710f79aA85099e200be4d40Cdf1Bfb2B467a01
Balance
0 ETH
Nonce
1
Code Size
17099 bytes
Creator
0xfdeedE3a...E060 at tx 0xa96975bc...ac2231
Indexed Transactions
0
Contract Bytecode
17099 bytes
0x608060405234801561001057600080fd5b50600436106103205760003560e01c80637a766460116101a7578063a694fc3a116100ee578063e8376b8a11610097578063ed63e80711610071578063ed63e80714610700578063f2fde38b1461070e578063fa4934e71461072157600080fd5b8063e8376b8a146106e8578063e937fdaa146106f0578063e9f37cdf146106f857600080fd5b8063cfcd8fd8116100c8578063cfcd8fd8146106a7578063d174e658146106ba578063e0974ea5146106e057600080fd5b8063a694fc3a14610684578063a7a2f5aa14610697578063b88a802f1461069f57600080fd5b80638da5cb5b1161015057806392eefe9b1161012a57806392eefe9b1461064b5780639a109bc21461065e578063a07aea1c1461067157600080fd5b80638da5cb5b146105f25780638fb4b57314610603578063929ec5371461061657600080fd5b806387e900b11161018157806387e900b1146105b95780638899fdeb146105cc5780638932a90d146105df57600080fd5b80637a7664601461055b5780637e1a3786146105925780637eee288d146105a657600080fd5b80633ff089851161026b5780635c975abb116102145780636d70f7ae116101ee5780636d70f7ae1461052d578063715018a61461054057806375c93bb91461054857600080fd5b80635c975abb146104e25780635e8b40d7146104f457806363b2c85a1461051a57600080fd5b80634e71d92d116102455780634e71d92d146104b357806351858e27146104bb57806359f01879146104c357600080fd5b80633ff08985146104855780634a4e3bd5146104985780634aba2ca9146104a057600080fd5b80632624c83c116102cd5780633018205f116102a75780633018205f1461046157806332e288501461047257806338adb6f01461047d57600080fd5b80632624c83c14610413578063282d3fdf146104395780632e17de781461044e57600080fd5b8063181f5a77116102fe578063181f5a77146103a75780631ddb5552146103d657806322f3e2d4146103fb57600080fd5b8063049b2ca0146103255780630641bdd81461034b5780630fbc8f5b14610396575b600080fd5b6002546201000090046001600160601b03165b6040519081526020015b60405180910390f35b6003547f000000000000000000000000000000000000000000000000000000e8d4a5100090600160601b90046001600160601b03165b60408051928352602083019190915201610342565b6003546001600160601b0316610338565b604080518082018252600d81526c05374616b696e6720302e312e3609c1b602082015290516103429190613df1565b600c546001600160a01b03165b6040516001600160a01b039091168152602001610342565b610403610742565b6040519015158152602001610342565b7f0000000000000000000000000000000000000000000069e10de76676d0800000610338565b61044c610447366004613e20565b61076d565b005b61044c61045c366004613e4a565b6108b1565b6009546001600160a01b03166103e3565b60065460ff16610338565b610338610980565b610338610493366004613e63565b61098c565b61044c610a45565b61044c6104ae366004613e7e565b610a57565b61044c610a77565b61044c610aa8565b60085463ffffffff600160701b8204811691600160501b900416610381565b600054600160a01b900460ff16610403565b7f0000000000000000000000000000000000000000000000000000000000000014610338565b61044c610528366004613e63565b610ab8565b61040361053b366004613e63565b610c06565b61044c610c29565b61044c610556366004613e7e565b610c3b565b610338610569366004613e63565b6001600160a01b031660009081526001602052604090205461010090046001600160601b031690565b60085469ffffffffffffffffffff16610338565b61044c6105b4366004613e20565b610d3a565b6103386105c7366004613e63565b610e78565b61044c6105da366004613e20565b610f9f565b61044c6105ed366004613ea0565b6111cc565b6000546001600160a01b03166103e3565b61044c610611366004613e7e565b611332565b610338610624366004613e63565b6001600160a01b03166000908152600160205260409020600201546001600160601b031690565b61044c610659366004613e63565b6113f4565b61033861066c366004613e63565b611471565b61044c61067f366004613f12565b611547565b61044c610692366004613e4a565b6115a6565b610338611667565b61044c6116a6565b61044c6106b5366004613e7e565b611891565b7f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a6103e3565b6103386118ef565b61044c611999565b61044c611c09565b600454610338565b60075463ffffffff16610338565b61044c61071c366004613e63565b611cc9565b61073461072f366004613e63565b611d42565b604051610342929190613f75565b60025460009060ff168015610768575060085442600160501b90910463ffffffff161115155b905090565b6009546001600160a01b0316331461079857604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff166107e25760405163eac13dcd60e01b81526001600160a01b03841660048201526024015b60405180910390fd5b805461010090046001600160601b031682111561081557604051631d820b1760e01b8152600481018390526024016107d9565b61081e82612083565b60028201805460009061083b9084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f9f1ec8c880f76798e7b793325d625e9b60e4082a553c98f42b6cda368dd6000883836040516108a49291906001600160a01b03929092168252602082015260400190565b60405180910390a1505050565b6108b96120b1565b60006108ca64e8d4a510008361404c565b905080156108df576108dc8183614060565b91505b6000806108ee3385600061210b565b9092509050610932336109018385614073565b6001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169190612817565b6040805133815260208101869052908101839052606081018290527f204fccf0d92ed8d48f204adb39b2e81e92bad0dedb93f5716ca9478cfb57de009060800160405180910390a150505050565b600061076860016128a7565b336000908152600160208190526040822001805482036109af5750600092915050565b60005b8154811015610a3e5760008282815481106109cf576109cf614086565b90600052602060002090600202019050428383815481106109f2576109f2614086565b90600052602060002090600202016001015411610a25578054610a1e906001600160601b031685614073565b9350610a2b565b50610a3e565b5080610a368161409c565b9150506109b2565b5050919050565b610a4d6128fd565b610a55612957565b565b610a5f6128fd565b610a676129a7565b610a7360018383611f1c565b5050565b610a7f6120b1565b610a876116a6565b336000908152600160208190526040909120015415610a5557610a55611999565b610ab06128fd565b610a556129d6565b610ac06128fd565b6001600160a01b0381163b1580610adf57506001600160a01b03811630145b80610af75750600a546001600160a01b038281169116145b80610b0f5750600c546001600160a01b038281169116145b80610b8657506040516301ffc9a760e01b81526331f5be1560e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa158015610b60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b8491906140b5565b155b15610ba4576040516306cf420760e31b815260040160405180910390fd5b600c80546001600160a01b0319908116909155600a80546001600160a01b03841692168217905542600b556040519081527f5c74c441be501340b2713817a6c6975e6f3d4a4ae39fa1ac0bf75d3c54a0cad3906020015b60405180910390a150565b6001600160a01b03811660009081526001602052604081205460ff165b92915050565b610c316128fd565b610a556000612a19565b610c436128fd565b610c4b6129a7565b600254610c6a906201000090046001600160601b03165b600590612a69565b610c90610c75611667565b6002546201000090046001600160601b031660059190612ae7565b610cc56001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b610cf2600583837f0000000000000000000000000000000000000000000000000000000000015180612b9a565b7f6c07ee05dcf262f13abf9d87b846ee789d2f90fe991d495acd7d7fc109ee1f5582610d1e8342614073565b6040805192835260208301919091520160405180910390a15050565b6009546001600160a01b03163314610d6557604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b0382166000908152600160205260409020805460ff16610daa5760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60028101546001600160601b0316821115610de957600281015460405163c9dcab8760e01b81526001600160601b0390911660048201526024016107d9565b610df282612083565b600282018054600090610e0f9084906001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055507f0f0bc5b519ddefdd8e5f9e6423433aa2b869738de2ae34d58ebc796fc749fa0d83836040516108a49291906001600160a01b03929092168252602082015260400190565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879695939486019390929190879084015b82821015610f23576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101610edb565b50505090825250600291909101546001600160601b03166020909101528051909150610f525750600092915050565b80602001516001600160601b0316600003610f705750600092915050565b610f9883610f7c611667565b6002546201000090046001600160601b03166005929190612ca2565b9392505050565b6009546001600160a01b03163314610fca57604051630f5caa3360e41b815260040160405180910390fd5b6001600160a01b03821660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561106f576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611027565b50505090825250600291909101546001600160601b031660209091015280519091506110b95760405163eac13dcd60e01b81526001600160a01b03841660048201526024016107d9565b60006110c784610f7c611667565b905060006110d58483612ceb565b90506110e081612083565b6001600160a01b03861660009081526005602052604090208054600c90611118908490600160601b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506111826111516000546001600160a01b031690565b6001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169083612817565b604080516001600160a01b0387168152602081018390527ff3136d14e8fd8ddb090effdbecb5d9547b6e17d9006ef9c25ca5b1e00dbfe51791015b60405180910390a15050505050565b6111d4610742565b156111fc57604051635185386160e11b815260016004820152600060248201526044016107d9565b600c546001600160a01b0316611225576040516306cf420760e31b815260040160405180910390fd5b600080600061123333612d01565b600c549295509093509150611260906001600160a01b0316826112568587614073565b6109019190614073565b600c546001600160a01b03166331f5be158261127c8587614073565b6112869190614073565b33888860405160200161129b93929190614120565b6040516020818303038152906040526040518363ffffffff1660e01b81526004016112c7929190614143565b600060405180830381600087803b1580156112e157600080fd5b505af11580156112f5573d6000803e3d6000fd5b505050507f667838b33bdc898470de09e0e746990f2adc11b965b7fe6828e502ebc39e04343384848489896040516111bd9695949392919061415c565b61133a6128fd565b600854600160701b900463ffffffff16156113675760405162dc149f60e41b815260040160405180910390fd5b61139260017f0000000000000000000000000000000000000000000000000000000000000001612e72565b6113c76001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b610a7360057f00000000000000000000000000000000000000000000000000000000000151808484612ef2565b6113fc6128fd565b6001600160a01b0381166114235760405163f6b2911f60e01b815260040160405180910390fd5b600980546001600160a01b0319166001600160a01b0383169081179091556040519081527f79f74fd5964b6943d8a1865abfb7f668c92fa3f32c0a2e3195da7d0946703ad790602001610bfb565b6001600160a01b03811660009081526001602052604081205461010090046001600160601b03168082036114a85750600092915050565b6001600160a01b03831660009081526001602052604090205460ff16156114d25750600092915050565b6001600160a01b0383166000908152600560205260409020546001600160601b031661153d611521837f0000000000000000000000000000000000000000000000000000000000000014612fcc565b6002546201000090046001600160601b03165b60059190612fe2565b610f989190614060565b61154f6128fd565b600854600160701b900463ffffffff16158015906115725750611570610742565b155b1561159a57604051635185386160e11b815260006004820152600160248201526044016107d9565b610a7360018383613008565b6115ae6120b1565b64e8d4a510008110156115db57604051631d820b1760e01b815264e8d4a5100060048201526024016107d9565b60006115ec64e8d4a510008361404c565b90508015611601576115fe8183614060565b91505b3360009081526001602052604090205460ff161561162857611623338361325c565b611632565b61163233836134e2565b610a736001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a16333085612b5c565b600254600090610768906201000090046001600160601b03167f00000000000000000000000000000000000000000000000000000000000000146137c8565b6116ae6120b1565b3360009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152929493860193879084015b8282101561174a576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611702565b50505090825250600291909101546001600160601b031660209091015280519091501561178a5760405163b7195bcb60e01b815260040160405180910390fd5b60006117c661152183602001516001600160601b03167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b33600090815260056020526040812054919250906117ed906001600160601b031683614060565b90506117f882612083565b33600081815260056020526040902080546001600160601b0319166001600160601b03939093169290921790915561185b906001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a169083612817565b60408051338152602081018390527f106f923f993c2149d49b4255ff723acafa1f2d94393f561d3eda32ae348f724191016108a4565b6118996128fd565b6118a1610742565b156118c957604051635185386160e11b815260016004820152600060248201526044016107d9565b6002546118e4906201000090046001600160601b0316610c62565b611392610c75611667565b6004546000906118fd610980565b6040516370a0823160e01b81523060048201527f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a6001600160a01b0316906370a0823190602401602060405180830381865afa158015611961573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611985919061419d565b61198f9190614060565b6107689190614060565b6119a16120b1565b33600090815260016020819052604082200180549091036119d757604051630f1ed8ad60e21b81523360048201526024016107d9565b60008060005b8354811015611a9d5760008482815481106119fa576119fa614086565b9060005260206000209060020201905042858381548110611a1d57611a1d614086565b90600052602060002090600202016001015411611a84578054611a49906001600160601b031685614073565b8154600480549296506001600160601b0390911691600090611a6c908490614060565b90915550839050611a7c8161409c565b935050611a8a565b50611a9d565b5080611a958161409c565b9150506119dd565b508015611b995760005b8354611ab4908390614060565b811015611b405783611ac68383614073565b81548110611ad657611ad6614086565b9060005260206000209060020201848281548110611af657611af6614086565b60009182526020909120825460029092020180546001600160601b0319166001600160601b0390921691909117815560019182015491015580611b388161409c565b915050611aa7565b5060005b81811015611b975783805480611b5c57611b5c6141b6565b60008281526020812060026000199093019283020180546001600160601b031916815560010155905580611b8f8161409c565b915050611b44565b505b8115611bd357611bd36001600160a01b037f000000000000000000000000ba50933c268f567bdc86e1ac131be072c6b0b71a163384612817565b60408051338152602081018490527fbb6ad7a7bb63a1746b89ec2f2339de4e0f983ef7e7b101c8c4f716c08c2a711491016108a4565b611c116128fd565b600a546001600160a01b0316611c3a576040516306cf420760e31b815260040160405180910390fd5b600b54611c4a9062093a80614073565b421015611c6a57604051631decfebb60e31b815260040160405180910390fd5b600a8054600c80546001600160a01b0383166001600160a01b031991821681179092559091169091556040519081527ffa33c052bbee754f3c0482a89962daffe749191fa33c696a61e947fbfd68bd84906020015b60405180910390a1565b611cd16128fd565b6001600160a01b038116611d365760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016107d9565b611d3f81612a19565b50565b606080600060016000016000856001600160a01b03166001600160a01b03168152602001908152602001600020600101805480602002602001604051908101604052809291908181526020016000905b82821015611dda576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101611d92565b505050509050805167ffffffffffffffff811115611dfa57611dfa6141cc565b604051908082528060200260200182016040528015611e23578160200160208202803683370190505b509250805167ffffffffffffffff811115611e4057611e406141cc565b604051908082528060200260200182016040528015611e69578160200160208202803683370190505b50915060005b8151811015611f1557818181518110611e8a57611e8a614086565b602002602001015160000151848281518110611ea857611ea8614086565b60200260200101906001600160601b031690816001600160601b031681525050818181518110611eda57611eda614086565b602002602001015160200151838281518110611ef857611ef8614086565b602090810291909101015280611f0d8161409c565b915050611e6f565b5050915091565b60028301546001600160601b0316821015611f4d57604051630f9e1c3b60e11b8152600481018390526024016107d9565b6002830154600160601b90046001600160601b0316811015611f855760405163bc91aa3360e01b8152600481018290526024016107d9565b60028301546001600160601b03168214611ff857611fa282612083565b6002840180546001600160601b0319166001600160601b03929092169190911790556040518281527f7f4f497e086b2eb55f8a9885ba00d33399bbe0ebcb92ea092834386435a1b9c09060200160405180910390a15b6002830154600160601b90046001600160601b0316811461207e5761201c81612083565b6002840180546001600160601b0392909216600160601b026bffffffffffffffffffffffff60601b199092169190911790556040518181527fb5f554e5ef00806bace1edbb84186512ebcefa2af7706085143f501f29314df7906020016108a4565b505050565b60006001600160601b038211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b5090565b600054600160a01b900460ff1615610a555760405162461bcd60e51b815260206004820152601060248201527f5061757361626c653a207061757365640000000000000000000000000000000060448201526064016107d9565b6001600160a01b03831660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b03168185015293810180548351818602810186018552818152879687969095860193919290879084015b828210156121b6576000848152602090819020604080518082019091526002850290910180546001600160601b0316825260019081015482840152908352909201910161216e565b50505090825250600291909101546001600160601b03166020909101529050600085900361220257604051637ece672b60e11b81526001600160a01b03871660048201526024016107d9565b8481602001516001600160601b0316101561224157602081015160405163477d28dd60e01b81526001600160601b0390911660048201526024016107d9565b60025461225c906201000090046001600160601b0316610c62565b612267610c75611667565b805115612519577f0000000000000000000000000000000000000000000069e10de76676d080000085146122b957604051633ac3109f60e01b81526001600160a01b03871660048201526024016107d9565b60608101516001600160601b0316156122f057604051634ed4c4c760e11b81526001600160a01b03871660048201526024016107d9565b60006122fe87610f7c611667565b905061230986612083565b60028054600e9061232b908490600160701b90046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061235886612083565b6001600160a01b0388166000908152600160208190526040909120805490919061239190849061010090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555084612491576123c386612083565b6001600160601b0316600160030160008282546123e09190614073565b90915550506001600160a01b038716600090815260016020819052604091829020825180840190935201908061241589612083565b6001600160601b0316815260200161244d7f000000000000000000000000000000000000000000000000000000000012750042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b60068054600191906000906124aa90849060ff166141e2565b825461010092830a60ff818102199092169290911602179091556006546001600160a01b038a16600090815260056020526040812080546bffffffffffffffffffffffff60601b1916939092046001600160601b0316600160601b02929092179055945090925061280f915050565b6001600160a01b0386166000908152600560209081526040822054908301516001600160601b03918216916125739161152191167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b61257d9190614060565b905061258886612083565b6002805481906125a89084906201000090046001600160601b03166140d7565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506125d586612083565b6001600160a01b0388166000908152600160208190526040909120805490919061260e90849061010090046001600160601b03166140d7565b82546001600160601b0391821661010093840a90810290830219909116179092556001600160a01b038a16600090815260016020526040812054919091049091169003905061269257600780546001919060009061267390849063ffffffff166141fb565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b8461276e576126a086612083565b6001600160601b0316600160030160008282546126bd9190614073565b90915550506001600160a01b03871660009081526001602081905260409182902082518084019093520190806126f289612083565b6001600160601b0316815260200161272a7f000000000000000000000000000000000000000000000000000000000012750042614073565b90528154600180820184556000938452602093849020835160029093020180546001600160601b0319166001600160601b0390931692909217825591909201519101555b6001600160a01b0387166000908152600160205260409020546127cc906127c7906115219061010090046001600160601b03167f0000000000000000000000000000000000000000000000000000000000000014612fcc565b612083565b6001600160a01b038816600090815260056020526040812080546001600160601b0319166001600160601b039390931692909217909155909350915061280f9050565b935093915050565b6040516001600160a01b03831660248201526044810182905261207e90849063a9059cbb60e01b906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526137d4565b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b9091041660608201819052600092610f989190614073565b6000546001600160a01b03163314610a555760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016107d9565b61295f6138a6565b6000805460ff60a01b191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b039091168152602001611cbf565b6129af610742565b610a5557604051635185386160e11b815260006004820152600160248201526044016107d9565b6129de6120b1565b6000805460ff60a01b1916600160a01b1790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25861298f3390565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b612a766127c783836138ff565b6002830180546001600160601b0392909216640100000000026fffffffffffffffffffffffff0000000019909216919091179055612abb612ab6836139ff565b613a1e565b6002909201805463ffffffff93909316600160801b0263ffffffff60801b199093169290921790915550565b612af56127c7848484613a45565b6001840180546001600160601b0392909216610100026cffffffffffffffffffffffff0019909216919091179055612b2f612ab6846139ff565b6001909301805463ffffffff94909416600160681b0263ffffffff60681b19909416939093179092555050565b6040516001600160a01b0380851660248301528316604482015260648101829052612b949085906323b872dd60e01b90608401612843565b50505050565b6003840154600090849042600160501b90910463ffffffff161115612bf6576003860154612bd6904290600160501b900463ffffffff16614060565b6003870154612bf1919069ffffffffffffffffffff16614218565b612bf9565b60005b612c039190614073565b905081831015612c255760405162da056d60e81b815260040160405180910390fd5b612c32612ab68442614073565b60038601805463ffffffff92909216600160501b026dffffffff0000000000000000000019909216919091179055612c72612c6d848361422f565b613b50565b600395909501805469ffffffffffffffffffff191669ffffffffffffffffffff9096169590951790945550505050565b6001600160a01b038316600090815260208590526040812054600160601b90046001600160601b0316612cd6868585613b7d565b612ce09190614060565b90505b949350505050565b6000818310612cfa5781610f98565b5090919050565b6001600160a01b03811660009081526001602081815260408084208151608081018352815460ff81161515825261010090046001600160601b031681850152938101805483518186028101860185528181528796879687969195949186019390879084015b82821015612dae576000848152602090819020604080518082019091526002850290910180546001600160601b03168252600190810154828401529083529092019101612d66565b50505090825250600291909101546001600160601b039081166020928301529082015191925016600003612e0057604051637256ef3960e11b81526001600160a01b03861660048201526024016107d9565b60608101516001600160601b031615612e3757604051634ed4c4c760e11b81526001600160a01b03861660048201526024016107d9565b600080612e538784602001516001600160601b0316600161210b565b6020909401516001600160601b03169650945091925050509193909250565b6001820154610100900460ff16811115612eb557600182015460405163e709379960e01b815261010090910460ff166004820152602481018290526044016107d9565b6001828101805460ff191690911790556040517fded6ebf04e261e1eb2f3e3b268a2e6aee5b478c15b341eba5cf18b9bc80c2e6390600090a15050565b6000612efd42613a1e565b60038601805471ffffffff00000000000000000000000000001916600160701b63ffffffff84169081029190911790915560018701805463ffffffff60681b1916600160681b830217905560028701805463ffffffff60801b1916600160801b9092029190911790559050612f7485848487612b9a565b60038501546040805185815263ffffffff600160701b840481166020830152600160501b909304909216908201527f4398f7e311b2f8164ce7d424166e6e84fd9a74adda069e902beead8e4eb737b8906060016111bd565b6000612fd883836137c8565b610f989084614060565b600064e8d4a51000612ff485846138ff565b612ffe9085614218565b612ce3919061422f565b60005b818110156132185783600084848481811061302857613028614086565b905060200201602081019061303d9190613e63565b6001600160a01b0316815260208101919091526040016000205460ff16156130aa5782828281811061307157613071614086565b90506020020160208101906130869190613e63565b604051625290b360e11b81526001600160a01b0390911660048201526024016107d9565b600084818585858181106130c0576130c0614086565b90506020020160208101906130d59190613e63565b6001600160a01b0316815260208101919091526040016000205461010090046001600160601b0316111561314f5782828281811061311557613115614086565b905060200201602081019061312a9190613e63565b60405163602d4d1160e01b81526001600160a01b0390911660048201526024016107d9565b600184600085858581811061316657613166614086565b905060200201602081019061317b9190613e63565b6001600160a01b031681526020810191909152604001600020805460ff19169115159190911790557fac6fa858e9350a46cec16539926e0fde25b7629f84b5a72bffaae4df888ae86d8383838181106131d6576131d6614086565b90506020020160208101906131eb9190613e63565b6040516001600160a01b03909116815260200160405180910390a1806132108161409c565b91505061300b565b5061322281613b8a565b60018401546132399190610100900460ff16614243565b6001909301805460ff949094166101000261ff0019909416939093179092555050565b6001600160a01b0382166000908152600160205260408120805490916101009091046001600160601b0316906132928483614073565b90507f0000000000000000000000000000000000000000000069e10de76676d08000008110156132f757604051631d820b1760e01b81527f0000000000000000000000000000000000000000000069e10de76676d080000060048201526024016107d9565b7f0000000000000000000000000000000000000000000069e10de76676d0800000811115613365576133497f0000000000000000000000000000000000000000000069e10de76676d080000082614060565b604051631728673b60e31b81526004016107d991815260200190565b8160000361340857613378610c75611667565b60065460ff16600081900361339e57600680546cffffffffffffffffffffffff00191690555b6133a9816001614243565b6006805460ff191660ff9290921691909117908190556001600160a01b038716600090815260056020526040902080546bffffffffffffffffffffffff60601b19166101009092046001600160601b0316600160601b02919091179055505b61341184612083565b60028054600e90613433908490600160701b90046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061346081612083565b6001600160a01b03861660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018690529081018290527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee90906060016111bd565b6134ea6129a7565b6001600160a01b03821660009081526001602052604081205461010090046001600160601b03169061351c8383614073565b90507f000000000000000000000000000000000000000000000000000000e8d4a5100081101561358157604051631d820b1760e01b81527f000000000000000000000000000000000000000000000000000000e8d4a5100060048201526024016107d9565b600354600160601b90046001600160601b0316808211156135a6576133498382614060565b60006135b26001613bae565b9050808511156135d857604051631728673b60e31b8152600481018290526024016107d9565b6002546135f3906201000090046001600160601b0316610c62565b6135fe610c75611667565b8360000361364157600780546001919060009061362290849063ffffffff1661425c565b92506101000a81548163ffffffff021916908363ffffffff1602179055505b600061366d867f0000000000000000000000000000000000000000000000000000000000000014612fcc565b905061368f6127c7826115346002546001600160601b03620100009091041690565b6001600160a01b038816600090815260056020526040812080549091906136c09084906001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b031602179055506136ed86612083565b60028054819061370d9084906201000090046001600160601b031661400f565b92506101000a8154816001600160601b0302191690836001600160601b0316021790555061373a84612083565b6001600160a01b03881660008181526001602090815260409182902080546001600160601b0395909516610100026cffffffffffffffffffffffff00199095169490941790935580519182529181018890529081018590527f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee909060600160405180910390a150505050505050565b6000610f98828461422f565b6000613829826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316613c0a9092919063ffffffff16565b80519091501561207e578080602001905181019061384791906140b5565b61207e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016107d9565b600054600160a01b900460ff16610a555760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f742070617573656400000000000000000000000060448201526064016107d9565b6000816000036139255750600282015464010000000090046001600160601b0316610c23565b600383015460009042600160501b90910463ffffffff16111561396357600284015461395e90600160801b900463ffffffff1642614060565b61398e565b6002840154600385015461398e9163ffffffff600160801b909104811691600160501b900416614060565b60038501549091506139ce90849064e8d4a51000906139ba90859069ffffffffffffffffffff16614218565b6139c49190614218565b6127c7919061422f565b60028501546139ee919064010000000090046001600160601b031661400f565b6001600160601b0316949350505050565b6003810154600090610c2390600160501b900463ffffffff1642612ceb565b600063ffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b600081600003613a685750600183015461010090046001600160601b0316610f98565b600384015460009042600160501b90910463ffffffff161115613aa6576001850154613aa190600160681b900463ffffffff1642614060565b613ad1565b60018501546003860154613ad19163ffffffff600160681b909104811691600160501b900416614060565b600180870154919250613b2191613aed9160ff90911690613c19565b600387015485908790613b0d90869069ffffffffffffffffffff16614218565b613b179190614218565b6139c4919061422f565b6001860154613b3e919061010090046001600160601b031661400f565b6001600160601b031695945050505050565b600069ffffffffffffffffffff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b6000612ce3848484613a45565b600060ff8211156120ad5760405163408ba96f60e11b815260040160405180910390fd5b60408051608081018252600183015460ff8082161515835261010082041660208301526001600160601b036201000082048116938301849052600160701b909104811660608301526002840154600093610f9892909116614060565b6060612ce38484600085613c28565b6000818311612cfa5781610f98565b606082471015613c895760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016107d9565b600080866001600160a01b03168587604051613ca59190614279565b60006040518083038185875af1925050503d8060008114613ce2576040519150601f19603f3d011682016040523d82523d6000602084013e613ce7565b606091505b5091509150613cf887838387613d03565b979650505050505050565b60608315613d72578251600003613d6b576001600160a01b0385163b613d6b5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107d9565b5081612ce3565b612ce38383815115613d875781518083602001fd5b8060405162461bcd60e51b81526004016107d99190613df1565b60005b83811015613dbc578181015183820152602001613da4565b50506000910152565b60008151808452613ddd816020860160208601613da1565b601f01601f19169290920160200192915050565b602081526000610f986020830184613dc5565b80356001600160a01b0381168114613e1b57600080fd5b919050565b60008060408385031215613e3357600080fd5b613e3c83613e04565b946020939093013593505050565b600060208284031215613e5c57600080fd5b5035919050565b600060208284031215613e7557600080fd5b610f9882613e04565b60008060408385031215613e9157600080fd5b50508035926020909101359150565b60008060208385031215613eb357600080fd5b823567ffffffffffffffff80821115613ecb57600080fd5b818501915085601f830112613edf57600080fd5b813581811115613eee57600080fd5b866020828501011115613f0057600080fd5b60209290920196919550909350505050565b60008060208385031215613f2557600080fd5b823567ffffffffffffffff80821115613f3d57600080fd5b818501915085601f830112613f5157600080fd5b813581811115613f6057600080fd5b8660208260051b8501011115613f0057600080fd5b604080825283519082018190526000906020906060840190828701845b82811015613fb75781516001600160601b031684529284019290840190600101613f92565b5050508381038285015284518082528583019183019060005b81811015613fec57835183529284019291840191600101613fd0565b5090979650505050505050565b634e487b7160e01b600052601160045260246000fd5b6001600160601b0381811683821601908082111561402f5761402f613ff9565b5092915050565b634e487b7160e01b600052601260045260246000fd5b60008261405b5761405b614036565b500690565b81810381811115610c2357610c23613ff9565b80820180821115610c2357610c23613ff9565b634e487b7160e01b600052603260045260246000fd5b6000600182016140ae576140ae613ff9565b5060010190565b6000602082840312156140c757600080fd5b81518015158114610f9857600080fd5b6001600160601b0382811682821603908082111561402f5761402f613ff9565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0384168152604060208201526000612ce06040830184866140f7565b828152604060208201526000612ce36040830184613dc5565b6001600160a01b038716815285602082015284604082015283606082015260a06080820152600061419160a0830184866140f7565b98975050505050505050565b6000602082840312156141af57600080fd5b5051919050565b634e487b7160e01b600052603160045260246000fd5b634e487b7160e01b600052604160045260246000fd5b60ff8281168282160390811115610c2357610c23613ff9565b63ffffffff82811682821603908082111561402f5761402f613ff9565b8082028115828204841417610c2357610c23613ff9565b60008261423e5761423e614036565b500490565b60ff8181168382160190811115610c2357610c23613ff9565b63ffffffff81811683821601908082111561402f5761402f613ff9565b6000825161428b818460208701613da1565b919091019291505056fea26469706673582212204b33beeb15f45e326d7db3f299b4a8074df7350d41370ad4fcb664fb96c7ff3564736f6c63430008120033
Verified Source Code Partial Match
Compiler: v0.8.18+commit.87f61d96
EVM: london
Optimization: Yes (500 runs)
Staking.sol 770 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {IERC20, SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {TypeAndVersionInterface} from "./interfaces/TypeAndVersionInterface.sol";
import {Pausable} from "openzeppelin-contracts/contracts/security/Pausable.sol";
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {IERC165} from "openzeppelin-contracts/contracts/interfaces/IERC165.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
import {IStaking} from "./interfaces/IStaking.sol";
import {IStakingOwner} from "./interfaces/IStakingOwner.sol";
import {INodeStaking} from "./interfaces/INodeStaking.sol";
import {IMigratable} from "./interfaces/IMigratable.sol";
import {StakingPoolLib} from "./libraries/StakingPoolLib.sol";
import {RewardLib, SafeCast} from "./libraries/RewardLib.sol";
import {IMigrationTarget} from "./interfaces/IMigrationTarget.sol";
contract Staking is IStaking, IStakingOwner, INodeStaking, IMigratable, Ownable, TypeAndVersionInterface, Pausable {
using StakingPoolLib for StakingPoolLib.Pool;
using RewardLib for RewardLib.Reward;
using SafeCast for uint256;
using SafeERC20 for IERC20;
/// @notice This struct defines the params required by the Staking contract's
/// constructor.
struct PoolConstructorParams {
/// @notice The ARPA Token
IERC20 arpa;
/// @notice The initial maximum total stake amount across all stakers
uint256 initialMaxPoolSize;
/// @notice The initial maximum stake amount for a single community staker
uint256 initialMaxCommunityStakeAmount;
/// @notice The minimum stake amount that a community staker can stake
uint256 minCommunityStakeAmount;
/// @notice The stake amount that an operator should stake
uint256 operatorStakeAmount;
/// @notice The minimum number of node operators required to initialize the
/// staking pool.
uint256 minInitialOperatorCount;
/// @notice The minimum reward duration after pool config updates and pool
/// reward extensions
uint256 minRewardDuration;
/// @notice Used to calculate delegated stake amount
/// = amount / delegation rate denominator = 100% / 100 = 1%
uint256 delegationRateDenominator;
/// @notice The freezing duration for stakers after unstaking
uint256 unstakeFreezingDuration;
}
IERC20 internal immutable _arpa;
StakingPoolLib.Pool internal _pool;
RewardLib.Reward internal _reward;
/// @notice The address of the controller contract
address internal _controller;
/// @notice The proposed address stakers will migrate funds to
address internal _proposedMigrationTarget;
/// @notice The timestamp of when the migration target was proposed at
uint256 internal _proposedMigrationTargetAt;
/// @notice The address stakers can migrate their funds to
address internal _migrationTarget;
/// @notice The stake amount that a node operator should stake
uint256 internal immutable _operatorStakeAmount;
/// @notice The minimum stake amount that a community staker can stake
uint256 internal immutable _minCommunityStakeAmount;
/// @notice The minimum number of node operators required to initialize the
/// staking pool.
uint256 internal immutable _minInitialOperatorCount;
/// @notice The minimum reward duration after pool config updates and pool
/// reward extensions
uint256 internal immutable _minRewardDuration;
/// @notice Used to calculate delegated stake amount
/// = amount / delegation rate denominator = 100% / 100 = 1%
uint256 internal immutable _delegationRateDenominator;
/// @notice The freeze duration for stakers after unstaking
uint256 internal immutable _unstakeFreezingDuration;
event StakingConfigSet(
address arpaAddress,
uint256 initialMaxPoolSize,
uint256 initialMaxCommunityStakeAmount,
uint256 minCommunityStakeAmount,
uint256 operatorStakeAmount,
uint256 minInitialOperatorCount,
uint256 minRewardDuration,
uint256 delegationRateDenominator,
uint256 unstakeFreezingDuration
);
constructor(PoolConstructorParams memory params) {
if (address(params.arpa) == address(0)) revert InvalidZeroAddress();
if (params.delegationRateDenominator == 0) revert InvalidDelegationRate();
if (RewardLib.REWARD_PRECISION % params.delegationRateDenominator > 0) {
revert InvalidDelegationRate();
}
if (params.operatorStakeAmount == 0) {
revert InvalidOperatorStakeAmount();
}
if (params.minCommunityStakeAmount > params.initialMaxCommunityStakeAmount) {
revert InvalidMinCommunityStakeAmount();
}
_pool._setConfig(params.initialMaxPoolSize, params.initialMaxCommunityStakeAmount);
_arpa = params.arpa;
_operatorStakeAmount = params.operatorStakeAmount;
_minCommunityStakeAmount = params.minCommunityStakeAmount;
_minInitialOperatorCount = params.minInitialOperatorCount;
_minRewardDuration = params.minRewardDuration;
_delegationRateDenominator = params.delegationRateDenominator;
_unstakeFreezingDuration = params.unstakeFreezingDuration;
emit StakingConfigSet(
address(params.arpa),
params.initialMaxPoolSize,
params.initialMaxCommunityStakeAmount,
params.minCommunityStakeAmount,
params.operatorStakeAmount,
params.minInitialOperatorCount,
params.minRewardDuration,
params.delegationRateDenominator,
params.unstakeFreezingDuration
);
}
// =======================
// TypeAndVersionInterface
// =======================
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return "Staking 0.1.0";
}
// =============
// IStakingOwner
// =============
/// @inheritdoc IStakingOwner
function setController(address controller) external override(IStakingOwner) onlyOwner {
if (controller == address(0)) revert InvalidZeroAddress();
_controller = controller;
emit ControllerSet(controller);
}
/// @inheritdoc IStakingOwner
function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount)
external
override(IStakingOwner)
onlyOwner
whenActive
{
_pool._setConfig(maxPoolSize, maxCommunityStakeAmount);
}
/// @inheritdoc IStakingOwner
function start(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner {
if (_reward.startTimestamp != 0) revert AlreadyInitialized();
_pool._open(_minInitialOperatorCount);
// We need to transfer ARPA balance before we initialize the reward to
// calculate the new reward expiry timestamp.
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._initialize(_minRewardDuration, amount, rewardDuration);
}
/// @inheritdoc IStakingOwner
function newReward(uint256 amount, uint256 rewardDuration)
external
override(IStakingOwner)
onlyOwner
whenInactive
{
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._initialize(_minRewardDuration, amount, rewardDuration);
}
/// @inheritdoc IStakingOwner
function addReward(uint256 amount, uint256 rewardDuration) external override(IStakingOwner) onlyOwner whenActive {
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
_arpa.safeTransferFrom(msg.sender, address(this), amount);
_reward._updateReward(amount, rewardDuration, _minRewardDuration);
emit RewardLib.RewardAdded(amount, block.timestamp + rewardDuration);
}
/// @dev Required conditions for adding operators:
/// - Operators can only be added to the pool if they have no prior stake.
/// - Operators cannot be added to the pool after staking ends.
/// @inheritdoc IStakingOwner
function addOperators(address[] calldata operators) external override(IStakingOwner) onlyOwner {
// If reward was initialized (meaning the pool was active) but the pool is
// no longer active we want to prevent adding new operators.
if (_reward.startTimestamp > 0 && !isActive()) {
revert StakingPoolLib.InvalidPoolStatus(false, true);
}
_pool._addOperators(operators);
}
/// @inheritdoc IStakingOwner
function emergencyPause() external override(IStakingOwner) onlyOwner {
_pause();
}
/// @inheritdoc IStakingOwner
function emergencyUnpause() external override(IStakingOwner) onlyOwner {
_unpause();
}
// ===========
// IMigratable
// ===========
/// @inheritdoc IMigratable
function getMigrationTarget() external view override(IMigratable) returns (address) {
return _migrationTarget;
}
/// @inheritdoc IMigratable
function proposeMigrationTarget(address migrationTarget) external override(IMigratable) onlyOwner {
if (
migrationTarget.code.length == 0 || migrationTarget == address(this)
|| _proposedMigrationTarget == migrationTarget || _migrationTarget == migrationTarget
|| !IERC165(migrationTarget).supportsInterface(IMigrationTarget.migrateFrom.selector)
) {
revert InvalidMigrationTarget();
}
_migrationTarget = address(0);
_proposedMigrationTarget = migrationTarget;
_proposedMigrationTargetAt = block.timestamp;
emit MigrationTargetProposed(migrationTarget);
}
/// @inheritdoc IMigratable
function acceptMigrationTarget() external override(IMigratable) onlyOwner {
if (_proposedMigrationTarget == address(0)) {
revert InvalidMigrationTarget();
}
if (block.timestamp < (uint256(_proposedMigrationTargetAt) + 7 days)) {
revert AccessForbidden();
}
_migrationTarget = _proposedMigrationTarget;
_proposedMigrationTarget = address(0);
emit MigrationTargetAccepted(_migrationTarget);
}
/// @inheritdoc IMigratable
function migrate(bytes calldata data) external override(IMigratable) whenInactive {
if (_migrationTarget == address(0)) revert InvalidMigrationTarget();
(uint256 amount, uint256 baseReward, uint256 delegationReward) = _exit(msg.sender);
_arpa.safeTransfer(_migrationTarget, uint256(amount + baseReward + delegationReward));
// call migrate function
IMigrationTarget(_migrationTarget).migrateFrom(
uint256(amount + baseReward + delegationReward), abi.encode(msg.sender, data)
);
emit Migrated(msg.sender, amount, baseReward, delegationReward, data);
}
// ========
// INodeStaking
// ========
/// @inheritdoc INodeStaking
function lock(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
if (stakerAccount.stakedAmount < amount) {
revert StakingPoolLib.InsufficientStakeAmount(amount);
}
stakerAccount.lockedStakeAmount += amount._toUint96();
emit Locked(staker, amount);
}
/// @inheritdoc INodeStaking
function unlock(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker storage stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
if (stakerAccount.lockedStakeAmount < amount) {
revert INodeStaking.InadequateOperatorLockedStakingAmount(stakerAccount.lockedStakeAmount);
}
stakerAccount.lockedStakeAmount -= amount._toUint96();
emit Unlocked(staker, amount);
}
/// @inheritdoc INodeStaking
function slashDelegationReward(address staker, uint256 amount) external override(INodeStaking) onlyController {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) {
revert StakingPoolLib.OperatorDoesNotExist(staker);
}
uint256 earnedRewards = _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
// max capped by earnings
uint256 slashedRewards = Math.min(amount, earnedRewards);
_reward.missed[staker].delegated += slashedRewards._toUint96();
_arpa.safeTransfer(owner(), slashedRewards);
emit DelegationRewardSlashed(staker, slashedRewards);
}
/// @inheritdoc INodeStaking
function getLockedAmount(address staker) external view override(INodeStaking) returns (uint256) {
return _pool.stakers[staker].lockedStakeAmount;
}
// ========
// IStaking
// ========
/// @inheritdoc IStaking
function stake(uint256 amount) external override(IStaking) whenNotPaused {
if (amount < RewardLib.REWARD_PRECISION) {
revert StakingPoolLib.InsufficientStakeAmount(RewardLib.REWARD_PRECISION);
}
// Round down input amount to avoid cumulative rounding errors.
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
}
if (_pool._isOperator(msg.sender)) {
_stakeAsOperator(msg.sender, amount);
} else {
_stakeAsCommunityStaker(msg.sender, amount);
}
_arpa.safeTransferFrom(msg.sender, address(this), amount);
}
/// @inheritdoc IStaking
function unstake(uint256 amount) external override(IStaking) whenNotPaused {
// Round down unstake amount to avoid cumulative rounding errors.
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
}
(uint256 baseReward, uint256 delegationReward) = _exit(msg.sender, amount, false);
_arpa.safeTransfer(msg.sender, baseReward + delegationReward);
emit Unstaked(msg.sender, amount, baseReward, delegationReward);
}
/// @inheritdoc IStaking
function claim() external override(IStaking) whenNotPaused {
claimReward();
if (_pool.stakers[msg.sender].frozenPrincipals.length > 0) {
claimFrozenPrincipal();
}
}
/// @inheritdoc IStaking
function claimReward() public override(IStaking) whenNotPaused {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[msg.sender];
if (stakerAccount.isOperator) {
revert StakingPoolLib.NoBaseRewardForOperator();
}
uint256 accruedReward = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
);
uint256 claimingReward = accruedReward - uint256(_reward.missed[msg.sender].base);
_reward.missed[msg.sender].base = accruedReward._toUint96();
_arpa.safeTransfer(msg.sender, claimingReward);
emit RewardClaimed(msg.sender, claimingReward);
}
/// @inheritdoc IStaking
function claimFrozenPrincipal() public override(IStaking) whenNotPaused {
StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
if (frozenPrincipals.length == 0) revert StakingPoolLib.FrozenPrincipalDoesNotExist(msg.sender);
uint256 claimingPrincipal = 0;
uint256 popCount = 0;
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
claimingPrincipal += frozenPrincipal.amount;
_pool.totalFrozenAmount -= frozenPrincipal.amount;
popCount++;
} else {
break;
}
}
if (popCount > 0) {
for (uint256 i = 0; i < frozenPrincipals.length - popCount; i++) {
frozenPrincipals[i] = frozenPrincipals[i + popCount];
}
for (uint256 i = 0; i < popCount; i++) {
frozenPrincipals.pop();
}
}
if (claimingPrincipal > 0) {
_arpa.safeTransfer(msg.sender, claimingPrincipal);
}
emit FrozenPrincipalClaimed(msg.sender, claimingPrincipal);
}
/// @inheritdoc IStaking
function getStake(address staker) public view override(IStaking) returns (uint256) {
return _pool.stakers[staker].stakedAmount;
}
/// @inheritdoc IStaking
function isOperator(address staker) external view override(IStaking) returns (bool) {
return _pool._isOperator(staker);
}
/// @inheritdoc IStaking
function isActive() public view override(IStaking) returns (bool) {
return _pool.state.isOpen && !_reward._isDepleted();
}
/// @inheritdoc IStaking
function getMaxPoolSize() external view override(IStaking) returns (uint256) {
return uint256(_pool.limits.maxPoolSize);
}
/// @inheritdoc IStaking
function getCommunityStakerLimits() external view override(IStaking) returns (uint256, uint256) {
return (_minCommunityStakeAmount, uint256(_pool.limits.maxCommunityStakeAmount));
}
/// @inheritdoc IStaking
function getOperatorLimit() external view override(IStaking) returns (uint256) {
return _operatorStakeAmount;
}
/// @inheritdoc IStaking
function getRewardTimestamps() external view override(IStaking) returns (uint256, uint256) {
return (uint256(_reward.startTimestamp), uint256(_reward.endTimestamp));
}
/// @inheritdoc IStaking
function getRewardRate() external view override(IStaking) returns (uint256) {
return uint256(_reward.rate);
}
/// @inheritdoc IStaking
function getDelegationRateDenominator() external view override(IStaking) returns (uint256) {
return _delegationRateDenominator;
}
/// @inheritdoc IStaking
function getAvailableReward() public view override(IStaking) returns (uint256) {
return _arpa.balanceOf(address(this)) - getTotalStakedAmount() - _pool.totalFrozenAmount;
}
/// @inheritdoc IStaking
function getBaseReward(address staker) public view override(IStaking) returns (uint256) {
uint256 stakedAmount = _pool.stakers[staker].stakedAmount;
if (stakedAmount == 0) return 0;
if (_pool._isOperator(staker)) {
return 0;
}
return _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakedAmount, _delegationRateDenominator), getTotalCommunityStakedAmount()
) - uint256(_reward.missed[staker].base);
}
/// @inheritdoc IStaking
function getDelegationReward(address staker) public view override(IStaking) returns (uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (!stakerAccount.isOperator) return 0;
if (stakerAccount.stakedAmount == 0) return 0;
return _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
}
/// @inheritdoc IStaking
function getTotalDelegatedAmount() public view override(IStaking) returns (uint256) {
return RewardLib._getDelegatedAmount(_pool.state.totalCommunityStakedAmount, _delegationRateDenominator);
}
/// @inheritdoc IStaking
function getDelegatesCount() external view override(IStaking) returns (uint256) {
return uint256(_reward.delegated.delegatesCount);
}
function getCommunityStakersCount() external view returns (uint256) {
return uint256(_reward.base.communityStakersCount);
}
/// @inheritdoc IStaking
function getTotalStakedAmount() public view override(IStaking) returns (uint256) {
return _pool._getTotalStakedAmount();
}
/// @inheritdoc IStaking
function getTotalCommunityStakedAmount() public view override(IStaking) returns (uint256) {
return _pool.state.totalCommunityStakedAmount;
}
/// @inheritdoc IStaking
function getTotalFrozenAmount() external view override(IStaking) returns (uint256) {
return _pool.totalFrozenAmount;
}
/// @inheritdoc IStaking
function getFrozenPrincipal(address staker)
external
view
override(IStaking)
returns (uint96[] memory amounts, uint256[] memory unlockTimestamps)
{
StakingPoolLib.FrozenPrincipal[] memory frozenPrincipals = _pool.stakers[staker].frozenPrincipals;
amounts = new uint96[](frozenPrincipals.length);
unlockTimestamps = new uint256[](frozenPrincipals.length);
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
amounts[i] = frozenPrincipals[i].amount;
unlockTimestamps[i] = frozenPrincipals[i].unlockTimestamp;
}
}
/// @inheritdoc IStaking
function getClaimablePrincipalAmount(address) external view returns (uint256 claimingPrincipal) {
StakingPoolLib.FrozenPrincipal[] storage frozenPrincipals = _pool.stakers[msg.sender].frozenPrincipals;
if (frozenPrincipals.length == 0) return 0;
for (uint256 i = 0; i < frozenPrincipals.length; i++) {
StakingPoolLib.FrozenPrincipal storage frozenPrincipal = frozenPrincipals[i];
if (frozenPrincipals[i].unlockTimestamp <= block.timestamp) {
claimingPrincipal += frozenPrincipal.amount;
} else {
break;
}
}
}
/// @inheritdoc IStaking
function getArpaToken() public view override(IStaking) returns (address) {
return address(_arpa);
}
/// @inheritdoc IStaking
function getController() external view override(IStaking) returns (address) {
return _controller;
}
// =======
// Internal
// =======
/// @notice Helper function for when a community staker enters the pool
/// @param staker The staker address
/// @param amount The amount of principal staked
function _stakeAsCommunityStaker(address staker, uint256 amount) internal whenActive {
uint256 currentStakedAmount = _pool.stakers[staker].stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
// Check that the amount is greater than or equal to the minimum required
if (newStakedAmount < _minCommunityStakeAmount) {
revert StakingPoolLib.InsufficientStakeAmount(_minCommunityStakeAmount);
}
// Check that the amount is less than or equal to the maximum allowed
uint256 maxCommunityStakeAmount = uint256(_pool.limits.maxCommunityStakeAmount);
if (newStakedAmount > maxCommunityStakeAmount) {
revert StakingPoolLib.ExcessiveStakeAmount(maxCommunityStakeAmount - currentStakedAmount);
}
// Check if the amount supplied increases the total staked amount above
// the maximum pool size
uint256 remainingPoolSpace = _pool._getRemainingPoolSpace();
if (amount > remainingPoolSpace) {
revert StakingPoolLib.ExcessiveStakeAmount(remainingPoolSpace);
}
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
// On first stake
if (currentStakedAmount == 0) {
_reward.base.communityStakersCount += 1;
}
uint256 extraNonDelegatedAmount = RewardLib._getNonDelegatedAmount(amount, _delegationRateDenominator);
_reward.missed[staker].base +=
_reward._calculateAccruedBaseRewards(extraNonDelegatedAmount, getTotalCommunityStakedAmount())._toUint96();
_pool.state.totalCommunityStakedAmount += amount._toUint96();
_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
/// @notice Helper function for when an operator enters the pool
/// @param staker The staker address
/// @param amount The amount of principal staked
function _stakeAsOperator(address staker, uint256 amount) internal {
StakingPoolLib.Staker storage operator = _pool.stakers[staker];
uint256 currentStakedAmount = operator.stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
// Check that the amount is greater than or less than the required
if (newStakedAmount < _operatorStakeAmount) {
revert StakingPoolLib.InsufficientStakeAmount(_operatorStakeAmount);
}
if (newStakedAmount > _operatorStakeAmount) {
revert StakingPoolLib.ExcessiveStakeAmount(newStakedAmount - _operatorStakeAmount);
}
// On first stake
if (currentStakedAmount == 0) {
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
uint8 delegatesCount = _reward.delegated.delegatesCount;
// Prior to the first operator staking, we reset the accumulated value
// so it doesn't count towards missed rewards.
if (delegatesCount == 0) {
delete _reward.delegated.cumulativePerDelegate;
}
_reward.delegated.delegatesCount = delegatesCount + 1;
_reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;
}
_pool.state.totalOperatorStakedAmount += amount._toUint96();
_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
/// @notice Helper function when staker exits the pool
/// @param staker The staker address
function _exit(address staker) internal returns (uint256, uint256, uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (stakerAccount.stakedAmount == 0) {
revert StakingPoolLib.StakeNotFound(staker);
}
if (stakerAccount.lockedStakeAmount > 0) {
revert StakingPoolLib.ExistingLockedStakeFound(staker);
}
(uint256 baseReward, uint256 delegationReward) = _exit(staker, stakerAccount.stakedAmount, true);
return (stakerAccount.stakedAmount, baseReward, delegationReward);
}
/// @notice Helper function when staker exits the pool
/// @param staker The staker address
function _exit(address staker, uint256 amount, bool isMigrate) internal returns (uint256, uint256) {
StakingPoolLib.Staker memory stakerAccount = _pool.stakers[staker];
if (amount == 0) {
revert StakingPoolLib.UnstakeWithZeroAmount(staker);
}
if (stakerAccount.stakedAmount < amount) {
revert StakingPoolLib.InadequateStakingAmount(stakerAccount.stakedAmount);
}
_reward._accumulateBaseRewards(getTotalCommunityStakedAmount());
_reward._accumulateDelegationRewards(getTotalDelegatedAmount(), getTotalCommunityStakedAmount());
if (stakerAccount.isOperator) {
if (amount != _operatorStakeAmount) {
revert StakingPoolLib.UnstakeOperatorWithPartialAmount(staker);
}
if (stakerAccount.lockedStakeAmount > 0) {
revert StakingPoolLib.ExistingLockedStakeFound(staker);
}
uint256 delegationReward = _reward._getOperatorEarnedDelegatedRewards(
staker, getTotalDelegatedAmount(), getTotalCommunityStakedAmount()
);
_pool.state.totalOperatorStakedAmount -= amount._toUint96();
_pool.stakers[staker].stakedAmount -= amount._toUint96();
if (!isMigrate) {
_pool.totalFrozenAmount += amount._toUint96();
_pool.stakers[staker].frozenPrincipals.push(
StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
);
}
_reward.delegated.delegatesCount -= 1;
_reward.missed[staker].delegated = _reward.delegated.cumulativePerDelegate;
return (0, delegationReward);
} else {
uint256 baseReward = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stakerAccount.stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
) - uint256(_reward.missed[staker].base);
_pool.state.totalCommunityStakedAmount -= amount._toUint96();
_pool.stakers[staker].stakedAmount -= amount._toUint96();
if (_pool.stakers[staker].stakedAmount == 0) {
_reward.base.communityStakersCount -= 1;
}
if (!isMigrate) {
_pool.totalFrozenAmount += amount._toUint96();
_pool.stakers[staker].frozenPrincipals.push(
StakingPoolLib.FrozenPrincipal(amount._toUint96(), block.timestamp + _unstakeFreezingDuration)
);
}
_reward.missed[staker].base = _reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(_pool.stakers[staker].stakedAmount, _delegationRateDenominator),
getTotalCommunityStakedAmount()
)._toUint96();
return (baseReward, 0);
}
}
// =========
// Modifiers
// =========
/// @dev Having a private function for the modifer saves on the contract size
function _isActive() private view {
if (!isActive()) revert StakingPoolLib.InvalidPoolStatus(false, true);
}
/// @dev Reverts if the staking pool is inactive (not open for staking or
/// expired)
modifier whenActive() {
_isActive();
_;
}
/// @dev Reverts if the staking pool is active (open for staking)
modifier whenInactive() {
if (isActive()) revert StakingPoolLib.InvalidPoolStatus(true, false);
_;
}
/// @dev Reverts if not sent from the LINK token
modifier onlyController() {
if (msg.sender != _controller) revert SenderNotController();
_;
}
}
SafeCast.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
library SafeCast {
error CastError();
/// @notice This is used to safely case timestamps to uint8
uint256 private constant MAX_UINT_8 = type(uint8).max;
/// @notice This is used to safely case timestamps to uint32
uint256 private constant MAX_UINT_32 = type(uint32).max;
/// @notice This is used to safely case timestamps to uint80
uint256 private constant MAX_UINT_80 = type(uint80).max;
/// @notice This is used to safely case timestamps to uint96
uint256 private constant MAX_UINT_96 = type(uint96).max;
function _toUint8(uint256 value) internal pure returns (uint8) {
if (value > MAX_UINT_8) revert CastError();
return uint8(value);
}
function _toUint32(uint256 value) internal pure returns (uint32) {
if (value > MAX_UINT_32) revert CastError();
return uint32(value);
}
function _toUint80(uint256 value) internal pure returns (uint80) {
if (value > MAX_UINT_80) revert CastError();
return uint80(value);
}
function _toUint96(uint256 value) internal pure returns (uint96) {
if (value > MAX_UINT_96) revert CastError();
return uint96(value);
}
}
IStaking.sol 144 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IStaking {
/// @notice This event is emitted when the controller is set.
/// @param controller Controller address
event ControllerSet(address controller);
/// @notice This event is emitted when a staker adds stake to the pool.
/// @param staker Staker address
/// @param newStake New principal amount staked
/// @param totalStake Total principal amount staked
event Staked(address staker, uint256 newStake, uint256 totalStake);
/// @notice This event is emitted when a staker exits the pool.
/// @param staker Staker address
/// @param principal Principal amount frozen after unstaking
/// @param baseReward base reward earned
/// @param delegationReward delegation reward earned, if any
event Unstaked(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward);
/// @notice This event is emitted when a staker claims base reward.
/// @param staker Staker address
/// @param baseReward Base reward amount claimed
event RewardClaimed(address staker, uint256 baseReward);
/// @notice This event is emitted when a staker claims frozen principal.
/// @param staker Staker address
/// @param principal Principal amount claimed
event FrozenPrincipalClaimed(address staker, uint256 principal);
/// @notice This error is thrown whenever an address does not have access
/// to successfully execute a transaction
error AccessForbidden();
/// @notice This error is thrown whenever a zero-address is supplied when
/// a non-zero address is required
error InvalidZeroAddress();
/// @notice This error is thrown whenever the sender is not controller contract
error SenderNotController();
/// @notice This function allows stakers to stake.
function stake(uint256 amount) external;
/// @notice This function allows stakers to unstake.
/// It returns base and delegation rewards, and makes principle frozen for later claiming.
function unstake(uint256 amount) external;
/// @notice This function allows community stakers to claim base rewards and frozen principals(if any).
function claim() external;
/// @notice This function allows stakers to claim base rewards.
function claimReward() external;
/// @notice This function allows stakers to claim frozen principals.
function claimFrozenPrincipal() external;
/// @return address ARPA token contract's address that is used by the pool
function getArpaToken() external view returns (address);
/// @param staker address
/// @return uint256 staker's staked principal amount
function getStake(address staker) external view returns (uint256);
/// @notice Returns true if an address is an operator
function isOperator(address staker) external view returns (bool);
/// @notice The staking pool starts closed and only allows
/// stakers to stake once it's opened
/// @return bool pool status
function isActive() external view returns (bool);
/// @return uint256 current maximum staking pool size
function getMaxPoolSize() external view returns (uint256);
/// @return uint256 minimum amount that can be staked by a community staker
/// @return uint256 maximum amount that can be staked by a community staker
function getCommunityStakerLimits() external view returns (uint256, uint256);
/// @return uint256 amount that should be staked by an operator
function getOperatorLimit() external view returns (uint256);
/// @return uint256 reward initialization timestamp
/// @return uint256 reward expiry timestamp
function getRewardTimestamps() external view returns (uint256, uint256);
/// @return uint256 current reward rate, expressed in arpa weis per second
function getRewardRate() external view returns (uint256);
/// @return uint256 current delegation rate
function getDelegationRateDenominator() external view returns (uint256);
/// @return uint256 total amount of ARPA tokens made available for rewards in
/// ARPA wei
/// @dev This reflects how many rewards were made available over the
/// lifetime of the staking pool.
function getAvailableReward() external view returns (uint256);
/// @return uint256 amount of base rewards earned by a staker in ARPA wei
function getBaseReward(address) external view returns (uint256);
/// @return uint256 amount of delegation rewards earned by an operator in ARPA wei
function getDelegationReward(address) external view returns (uint256);
/// @notice Total delegated amount is calculated by dividing the total
/// community staker staked amount by the delegation rate, i.e.
/// totalDelegatedAmount = pool.totalCommunityStakedAmount / delegationRateDenominator
/// @return uint256 staked amount that is used when calculating delegation rewards in ARPA wei
function getTotalDelegatedAmount() external view returns (uint256);
/// @notice Delegates count increases after an operator is added to the list
/// of operators and stakes the required amount.
/// @return uint256 number of staking operators that are eligible for delegation rewards
function getDelegatesCount() external view returns (uint256);
/// @notice This count all community stakers that have a staking balance greater than 0.
/// @return uint256 number of staking community stakers that are eligible for base rewards
function getCommunityStakersCount() external view returns (uint256);
/// @return uint256 total amount staked by community stakers and operators in ARPA wei
function getTotalStakedAmount() external view returns (uint256);
/// @return uint256 total amount staked by community stakers in ARPA wei
function getTotalCommunityStakedAmount() external view returns (uint256);
/// @return uint256 the sum of frozen operator principals that have not been
/// withdrawn from the staking pool in ARPA wei.
/// @dev Used to make sure that contract's balance is correct.
/// total staked amount + total frozen amount + available rewards = current balance
function getTotalFrozenAmount() external view returns (uint256);
/// @return amounts total amounts of ARPA wei that is currently frozen by the staker
/// @return unlockTimestamps timestamps when the frozen principal can be withdrawn
function getFrozenPrincipal(address)
external
view
returns (uint96[] memory amounts, uint256[] memory unlockTimestamps);
/// @return uint256 amount of ARPA wei that can be claimed as frozen principal by a staker
function getClaimablePrincipalAmount(address) external view returns (uint256);
/// @return address controller contract's address that is used by the pool
function getController() external view returns (address);
}
RewardLib.sol 237 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {SafeCast} from "./SafeCast.sol";
import {StakingPoolLib} from "./StakingPoolLib.sol";
import {Math} from "openzeppelin-contracts/contracts/utils/math/Math.sol";
library RewardLib {
using SafeCast for uint256;
/// @notice emitted when the reward is initialized for the first time
/// @param available the amount of rewards available for distribution in the
/// staking pool
/// @param startTimestamp the start timestamp when rewards are started
/// @param endTimestamp the timestamp when the reward will run out
event RewardInitialized(uint256 available, uint256 startTimestamp, uint256 endTimestamp);
/// @notice emitted when owner adds more rewards to the pool
/// @param amountAdded the amount of ARPA rewards added to the pool
/// @param endTimestamp the timestamp when the reward will run out
event RewardAdded(uint256 amountAdded, uint256 endTimestamp);
/// @notice emitted when owner withdraws leftover rewards
/// @param amount the amount of rewards withdrawn
event RewardWithdrawn(uint256 amount);
/// @notice emitted when an operator gets slashed.
/// Node operators are not slashed more than the amount of rewards they
/// have earned.
event RewardSlashed(address[] operator, uint256[] slashedDelegatedRewards);
/// @notice This error is thrown when the updated reward duration is too short
error RewardDurationTooShort();
/// @notice This is the reward calculation precision variable. ARPA token has the
/// 1e18 multiplier which means that rewards are floored after 6 decimals
/// points. Micro ARPA is the smallest unit that is eligible for rewards.
uint256 internal constant REWARD_PRECISION = 1e12;
struct DelegatedRewards {
// Count of delegates who are eligible for a share of a reward
uint8 delegatesCount;
// Tracks base reward amounts that goes to an operator as delegation rewards.
// Used to correctly account for any changes in operator count, delegated amount, or reward rate.
uint96 cumulativePerDelegate;
// Timestamp of the last time accumulate was called
// `startTimestamp` <= `delegated.lastAccumulateTimestamp`
uint32 lastAccumulateTimestamp;
}
struct BaseRewards {
// Count of community stakers who are eligible for a share of a reward
uint32 communityStakersCount;
// The cumulative ARPA accrued per stake from past reward rates
// expressed in ARPA wei per micro ARPA
uint96 cumulativePerShare;
// Timestamp of the last time the base reward rate was accumulated
uint32 lastAccumulateTimestamp;
}
struct MissedRewards {
// Tracks missed base rewards that are deducted from late stakers
uint96 base;
// Tracks missed delegation rewards that are deducted from late delegates
uint96 delegated;
}
struct Reward {
mapping(address => MissedRewards) missed;
DelegatedRewards delegated;
BaseRewards base;
// Reward rate expressed in arpa weis per second
uint80 rate;
// Timestamp when the reward stops accumulating. Has to support a very long
// duration for scenarios with low reward rate.
// `endTimestamp` >= `startTimestamp`
uint32 endTimestamp;
// Timestamp when the reward comes into effect
// `startTimestamp` <= `endTimestamp`
uint32 startTimestamp;
}
/// @notice initializes the reward with the defined parameters
/// @param minRewardDuration the minimum duration rewards need to last for
/// @param newReward the amount of rewards to be added to the pool
/// @param rewardDuration the duration for which the reward will be distributed
function _initialize(Reward storage reward, uint256 minRewardDuration, uint256 newReward, uint256 rewardDuration)
internal
{
uint32 blockTimestamp = block.timestamp._toUint32();
reward.startTimestamp = blockTimestamp;
reward.delegated.lastAccumulateTimestamp = blockTimestamp;
reward.base.lastAccumulateTimestamp = blockTimestamp;
_updateReward(reward, newReward, rewardDuration, minRewardDuration);
emit RewardInitialized(newReward, reward.startTimestamp, reward.endTimestamp);
}
/// @return bool true if the reward is expired (end <= now)
function _isDepleted(Reward storage reward) internal view returns (bool) {
return reward.endTimestamp <= block.timestamp;
}
/// @notice Helper function to accumulate base rewards
/// Accumulate reward per micro ARPA before changing reward rate.
/// This keeps rewards prior to rate change unaffected.
function _accumulateBaseRewards(Reward storage reward, uint256 totalStakedAmount) internal {
reward.base.cumulativePerShare = _calculateCumulativeBaseRewards(reward, totalStakedAmount)._toUint96();
reward.base.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
}
/// @notice Helper function to accumulate delegation rewards
/// @dev This function is necessary to correctly account for any changes in
/// eligible operators, delegated amount or reward rate.
function _accumulateDelegationRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal {
reward.delegated.cumulativePerDelegate =
_calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)._toUint96();
reward.delegated.lastAccumulateTimestamp = _getCappedTimestamp(reward)._toUint32();
}
function _calculateCumulativeBaseRewards(Reward storage reward, uint256 totalStakedAmount)
internal
view
returns (uint256)
{
if (totalStakedAmount == 0) return reward.base.cumulativePerShare;
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? (uint256(reward.endTimestamp) - uint256(reward.base.lastAccumulateTimestamp))
: block.timestamp - uint256(reward.base.lastAccumulateTimestamp);
return reward.base.cumulativePerShare
+ (uint256(reward.rate) * elapsedDurationSinceLastAccumulate * REWARD_PRECISION / totalStakedAmount)._toUint96();
}
function _calculateCumulativeDelegatedRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
if (totalStakedAmount == 0) return reward.delegated.cumulativePerDelegate;
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? uint256(reward.endTimestamp) - uint256(reward.delegated.lastAccumulateTimestamp)
: block.timestamp - uint256(reward.delegated.lastAccumulateTimestamp);
return reward.delegated.cumulativePerDelegate
+ (
uint256(reward.rate) * elapsedDurationSinceLastAccumulate * totalDelegatedAmount / totalStakedAmount
/ Math.max(uint256(reward.delegated.delegatesCount), 1)
)._toUint96();
}
/// @notice Calculates the amount of delegated rewards accumulated so far.
/// @dev This function takes into account the amount of delegated
/// rewards accumulated from previous delegate counts and amounts and
/// the latest additional value.
function _calculateAccruedDelegatedRewards(
Reward storage reward,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
return _calculateCumulativeDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount);
}
/// @notice Calculates the amount of rewards accrued so far.
/// @dev This function takes into account the amount of
/// rewards accumulated from previous rates in addition to
/// the rewards that will be accumulated based off the current rate
/// over a given duration.
function _calculateAccruedBaseRewards(Reward storage reward, uint256 amount, uint256 totalStakedAmount)
internal
view
returns (uint256)
{
return amount * _calculateCumulativeBaseRewards(reward, totalStakedAmount) / REWARD_PRECISION;
}
/// @notice calculates an amount that community stakers have to delegate to operators
/// @param amount base staked amount to calculate delegated amount against
/// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
function _getDelegatedAmount(uint256 amount, uint256 delegationRateDenominator) internal pure returns (uint256) {
return amount / delegationRateDenominator;
}
/// @notice calculates the amount of stake that remains after accounting for delegation requirement
/// @param amount base staked amount to calculate non-delegated amount against
/// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
function _getNonDelegatedAmount(uint256 amount, uint256 delegationRateDenominator)
internal
pure
returns (uint256)
{
return amount - _getDelegatedAmount(amount, delegationRateDenominator);
}
/// @notice This function is called when the staking pool is initialized,
/// rewards are added, TODO and an alert is raised
/// @param newReward new reward amount
/// @param rewardDuration duration of the reward
function _updateReward(Reward storage reward, uint256 newReward, uint256 rewardDuration, uint256 minRewardDuration)
internal
{
uint256 remainingRewards =
(_isDepleted(reward) ? 0 : (reward.rate * (uint256(reward.endTimestamp) - block.timestamp))) + newReward;
// Validate that the new reward duration is at least the min reward duration.
// This is a safety mechanism to guard against operational mistakes.
if (rewardDuration < minRewardDuration) {
revert RewardDurationTooShort();
}
reward.endTimestamp = (block.timestamp + rewardDuration)._toUint32();
reward.rate = (remainingRewards / rewardDuration)._toUint80();
}
/// @return The amount of delegated rewards an operator
/// has earned.
function _getOperatorEarnedDelegatedRewards(
Reward storage reward,
address operator,
uint256 totalDelegatedAmount,
uint256 totalStakedAmount
) internal view returns (uint256) {
return _calculateAccruedDelegatedRewards(reward, totalDelegatedAmount, totalStakedAmount)
- uint256(reward.missed[operator].delegated);
}
/// @return The current timestamp or, if the current timestamp has passed reward
/// end timestamp, reward end timestamp.
/// @dev This is necessary to ensure that rewards are calculated correctly
/// after the reward is depleted.
function _getCappedTimestamp(Reward storage reward) internal view returns (uint256) {
return Math.min(uint256(reward.endTimestamp), block.timestamp);
}
}
IMigratable.sol 37 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IMigratable {
/// @notice This event is emitted when a migration target is proposed by the contract owner.
/// @param migrationTarget Contract address to migrate stakes to.
event MigrationTargetProposed(address migrationTarget);
/// @notice This event is emitted after a 7 day period has passed since a migration target is proposed, and the target is accepted.
/// @param migrationTarget Contract address to migrate stakes to.
event MigrationTargetAccepted(address migrationTarget);
/// @notice This event is emitted when a staker migrates their stake to the migration target.
/// @param staker Staker address
/// @param principal Principal amount deposited
/// @param baseReward Amount of base rewards withdrawn
/// @param delegationReward Amount of delegation rewards withdrawn (if applicable)
/// @param data Migration payload
event Migrated(address staker, uint256 principal, uint256 baseReward, uint256 delegationReward, bytes data);
/// @notice This error is raised when the contract owner supplies a non-contract migration target.
error InvalidMigrationTarget();
/// @notice This function returns the migration target contract address
function getMigrationTarget() external view returns (address);
/// @notice This function allows the contract owner to set a proposed
/// migration target address. If the migration target is valid it renounces
/// the previously accepted migration target (if any).
/// @param migrationTarget Contract address to migrate stakes to.
function proposeMigrationTarget(address migrationTarget) external;
/// @notice This function allows the contract owner to accept a proposed migration target address after a waiting period.
function acceptMigrationTarget() external;
/// @notice This function allows stakers to migrate funds to a new staking pool.
/// @param data Migration path details
function migrate(bytes calldata data) external;
}
INodeStaking.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface INodeStaking {
/// @notice This event is emitted when a node locks stake in the pool.
/// @param staker Staker address
/// @param newLock New principal amount locked
event Locked(address staker, uint256 newLock);
/// @notice This event is emitted when a node unlocks stake in the pool.
/// @param staker Staker address
/// @param newUnlock New principal amount unlocked
event Unlocked(address staker, uint256 newUnlock);
/// @notice This event is emitted when a node gets delegation reward slashed.
/// @param staker Staker address
/// @param amount Amount slashed
event DelegationRewardSlashed(address staker, uint256 amount);
/// @notice This error is raised when attempting to unlock with more than the current locked staking amount
/// @param currentLockedStakingAmount Current locked staking amount
error InadequateOperatorLockedStakingAmount(uint256 currentLockedStakingAmount);
/// @notice This function allows controller to lock staking amount for a node.
/// @param staker Node address
/// @param amount Amount to lock
function lock(address staker, uint256 amount) external;
/// @notice This function allows controller to unlock staking amount for a node.
/// @param staker Node address
/// @param amount Amount to unlock
function unlock(address staker, uint256 amount) external;
/// @notice This function allows controller to slash delegation reward of a node.
/// @param staker Node address
/// @param amount Amount to slash
function slashDelegationReward(address staker, uint256 amount) external;
/// @notice This function returns the locked amount of a node.
/// @param staker Node address
function getLockedAmount(address staker) external view returns (uint256);
}
IStakingOwner.sol 61 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/// @notice Owner functions restricted to the setup and maintenance
/// of the staking contract by the owner.
interface IStakingOwner {
/// @notice This error is thrown when an zero delegation rate is supplied
error InvalidDelegationRate();
/// @notice This error is thrown when an invalid operator stake amount is
/// supplied
error InvalidOperatorStakeAmount();
/// @notice This error is thrown when an invalid min community stake amount
/// is supplied
error InvalidMinCommunityStakeAmount();
/// @notice This error is thrown when the reward is already initialized
error AlreadyInitialized();
/// @notice Adds one or more operators to a list of operators
/// @dev Should only callable by the Owner
/// @param operators A list of operator addresses to add
function addOperators(address[] calldata operators) external;
/// @notice This function can be called to add rewards to the pool when the reward is depleted
/// @dev Should only callable by the Owner
/// @param amount The amount of rewards to add to the pool
/// @param rewardDuration The duration of the reward
function newReward(uint256 amount, uint256 rewardDuration) external;
/// @notice This function can be called to add rewards to the pool when the reward is not depleted
/// @dev Should only be callable by the owner
/// @param amount The amount of rewards to add to the pool
/// @param rewardDuration The duration of the reward
function addReward(uint256 amount, uint256 rewardDuration) external;
/// @notice Set the pool config
/// @param maxPoolSize The max amount of staked ARPA by community stakers allowed in the pool
/// @param maxCommunityStakeAmount The max amount of ARPA a community staker can stake
function setPoolConfig(uint256 maxPoolSize, uint256 maxCommunityStakeAmount) external;
/// @notice Set controller contract address
/// @dev Should only be callable by the owner
/// @param controller The address of the controller contract
function setController(address controller) external;
/// @notice Transfers ARPA tokens and initializes the reward
/// @dev Uses ERC20 approve + transferFrom flow
/// @param amount rewards amount in ARPA
/// @param rewardDuration rewards duration in seconds
function start(uint256 amount, uint256 rewardDuration) external;
/// @notice This function pauses staking
/// @dev Sets the pause flag to true
function emergencyPause() external;
/// @notice This function unpauses staking
/// @dev Sets the pause flag to false
function emergencyUnpause() external;
}
StakingPoolLib.sol 190 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {SafeCast} from "./SafeCast.sol";
library StakingPoolLib {
using SafeCast for uint256;
/// @notice This event is emitted when the staking pool is opened for stakers
event PoolOpened();
/// @notice This event is emitted when the staking pool's maximum size is
/// increased
/// @param maxPoolSize the new maximum pool size
event PoolSizeIncreased(uint256 maxPoolSize);
/// @notice This event is emitted when the maximum stake amount
// for community stakers is increased
/// @param maxStakeAmount the new maximum stake amount
event MaxCommunityStakeAmountIncreased(uint256 maxStakeAmount);
/// @notice This event is emitted when an operator is added
/// @param operator address of the operator that was added to the staking pool
event OperatorAdded(address operator);
/// @notice Surfaces the required pool status to perform an operation
/// (true if open / false if closed)
/// @param currentStatus current status of the pool
/// @param requiredStatus required status of the pool to proceed
error InvalidPoolStatus(bool currentStatus, bool requiredStatus);
/// @notice This error is raised when attempting to decrease maximum pool size.
/// @param maxPoolSize the current maximum pool size
error InvalidPoolSize(uint256 maxPoolSize);
/// @notice This error is raised when attempting to decrease maximum stake amount
/// for community stakers or node operators
/// @param maxStakeAmount the current maximum stake amount
error InvalidMaxStakeAmount(uint256 maxStakeAmount);
/// @param requiredAmount minimum required stake amount
error InsufficientStakeAmount(uint256 requiredAmount);
/// @notice This error is raised when stakers attempt to stake past pool limits.
/// @param remainingAmount maximum remaining amount that can be staked. This is
/// the difference between the existing staked amount and the individual and global limits.
error ExcessiveStakeAmount(uint256 remainingAmount);
/// @notice This error is raised when stakers attempt to exit the pool.
/// @param staker address of the staker who attempted to withdraw funds
error StakeNotFound(address staker);
/// @notice This error is raised when addresses with existing stake is added as an operator.
/// @param staker address of the staker who is being added as an operator
error ExistingStakeFound(address staker);
/// @notice This error is raised when an address is duplicated in the supplied list of operators.
/// This can happen in addOperators and setFeedOperators functions.
/// @param operator address of the operator
error OperatorAlreadyExists(address operator);
/// @notice This error is raised when lock/unlock/slash is called on an operator that does not exist.
/// @param operator address of the operator
error OperatorDoesNotExist(address operator);
/// @notice This error is raised when attempting to claim rewards by an operator.
error NoBaseRewardForOperator();
/// @notice This error is raised when attempting to start staking with less
/// than the minimum required node operators
/// @param currentOperatorsCount The current number of operators in the staking pool
/// @param minInitialOperatorsCount The minimum required number of operators
/// in the staking pool before opening
error InadequateInitialOperatorsCount(uint256 currentOperatorsCount, uint256 minInitialOperatorsCount);
/// @notice This error is raised when attempting to unstake with more than the current staking amount.
error InadequateStakingAmount(uint256 currentStakingAmount);
/// @notice This error is raised when attempting to claim frozen principal that does not exist.
error FrozenPrincipalDoesNotExist(address staker);
/// @notice This error is raised when attempting to unstake with zero amount.
error UnstakeWithZeroAmount(address staker);
/// @notice This error is raised when attempting to unstake with partial amount by an operator.
error UnstakeOperatorWithPartialAmount(address operator);
/// @notice This error is raised when attempting to unstake with existing locked staking amount.
error ExistingLockedStakeFound(address operator);
struct PoolLimits {
// The max amount of staked ARPA by community stakers allowed in the pool
uint96 maxPoolSize;
// The max amount of ARPA a community staker can stake
uint96 maxCommunityStakeAmount;
}
struct PoolState {
// Flag that signals if the staking pool is open for staking
bool isOpen;
// Total number of operators added to the staking pool
uint8 operatorsCount;
// Total amount of ARPA staked by community stakers
uint96 totalCommunityStakedAmount;
// Total amount of ARPA staked by operators
uint96 totalOperatorStakedAmount;
}
struct FrozenPrincipal {
// Amount of ARPA frozen after unstaking
uint96 amount;
// Timestamp when the principal is unlocked
uint256 unlockTimestamp;
}
struct Staker {
// Flag that signals whether a staker is an operator
bool isOperator;
// Amount of ARPA staked by a staker
uint96 stakedAmount;
// Frozen principals of a staker
FrozenPrincipal[] frozenPrincipals;
// Locked staking amount of an operator
uint96 lockedStakeAmount;
}
struct Pool {
mapping(address => Staker) stakers;
PoolState state;
PoolLimits limits;
// Sum of frozen principals that have not been withdrawn.
// Used to make sure that contract's balance is correct.
// total staked amount + total frozen amount + available rewards = current balance
uint256 totalFrozenAmount;
}
/// @notice Sets staking pool parameters
/// @param maxPoolSize Maximum total stake amount across all stakers
/// @param maxCommunityStakeAmount Maximum stake amount for a single community staker
function _setConfig(Pool storage pool, uint256 maxPoolSize, uint256 maxCommunityStakeAmount) internal {
if (pool.limits.maxPoolSize > maxPoolSize) {
revert InvalidPoolSize(maxPoolSize);
}
if (pool.limits.maxCommunityStakeAmount > maxCommunityStakeAmount) {
revert InvalidMaxStakeAmount(maxCommunityStakeAmount);
}
if (pool.limits.maxPoolSize != maxPoolSize) {
pool.limits.maxPoolSize = maxPoolSize._toUint96();
emit PoolSizeIncreased(maxPoolSize);
}
if (pool.limits.maxCommunityStakeAmount != maxCommunityStakeAmount) {
pool.limits.maxCommunityStakeAmount = maxCommunityStakeAmount._toUint96();
emit MaxCommunityStakeAmountIncreased(maxCommunityStakeAmount);
}
}
/// @notice Opens the staking pool
function _open(Pool storage pool, uint256 minInitialOperatorCount) internal {
if (uint256(pool.state.operatorsCount) < minInitialOperatorCount) {
revert InadequateInitialOperatorsCount(pool.state.operatorsCount, minInitialOperatorCount);
}
pool.state.isOpen = true;
emit PoolOpened();
}
/// @notice Returns true if a supplied staker address is in the operators list
/// @param staker Address of a staker
/// @return bool
function _isOperator(Pool storage pool, address staker) internal view returns (bool) {
return pool.stakers[staker].isOperator;
}
/// @notice Returns the sum of all principal staked in the pool
/// @return totalStakedAmount
function _getTotalStakedAmount(Pool storage pool) internal view returns (uint256) {
StakingPoolLib.PoolState memory poolState = pool.state;
return uint256(poolState.totalCommunityStakedAmount) + uint256(poolState.totalOperatorStakedAmount);
}
/// @notice Returns the amount of remaining space available in the pool for
/// community stakers. Community stakers can only stake up to this amount
/// even if they are within their individual limits.
/// @return remainingPoolSpace
function _getRemainingPoolSpace(Pool storage pool) internal view returns (uint256) {
StakingPoolLib.PoolState memory poolState = pool.state;
return uint256(pool.limits.maxPoolSize) - uint256(poolState.totalCommunityStakedAmount);
}
/// @dev Required conditions for adding operators:
/// - Operators can only been added to the pool if they have no prior stake.
/// - Operators cannot be added to the pool after staking ends.
function _addOperators(Pool storage pool, address[] calldata operators) internal {
for (uint256 i; i < operators.length; i++) {
if (pool.stakers[operators[i]].isOperator) {
revert OperatorAlreadyExists(operators[i]);
}
if (pool.stakers[operators[i]].stakedAmount > 0) {
revert ExistingStakeFound(operators[i]);
}
pool.stakers[operators[i]].isOperator = true;
emit OperatorAdded(operators[i]);
}
// Safely update operators count with respect to the maximum of 255 operators
pool.state.operatorsCount = pool.state.operatorsCount + operators.length._toUint8();
}
}
IMigrationTarget.sol 9 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IMigrationTarget {
/// @notice This function allows stakers to migrate funds from an old staking pool.
/// @param amount Amount of tokens to migrate
/// @param data Migration path details
function migrateFrom(uint256 amount, bytes calldata data) external;
}
Address.sol 244 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @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;
}
}
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../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.
*
* By default, the owner account will be the one that deploys the contract. 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;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @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 {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing 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 {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_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);
}
}
Math.sol 345 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
Pausable.sol 105 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
TypeAndVersionInterface.sol 6 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol) pragma solidity ^0.8.0; import "../utils/introspection/IERC165.sol";
IERC20.sol 82 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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 amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` 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 amount) 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 `amount` 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 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` 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 amount
) external returns (bool);
}
SafeERC20.sol 116 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
draft-IERC20Permit.sol 60 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Read Contract
getArpaToken 0xd174e658 → address
getAvailableReward 0xe0974ea5 → uint256
getBaseReward 0x9a109bc2 → uint256
getClaimablePrincipalAmount 0x3ff08985 → uint256
getCommunityStakerLimits 0x0641bdd8 → uint256, uint256
getCommunityStakersCount 0xed63e807 → uint256
getController 0x3018205f → address
getDelegatesCount 0x32e28850 → uint256
getDelegationRateDenominator 0x5e8b40d7 → uint256
getDelegationReward 0x87e900b1 → uint256
getFrozenPrincipal 0xfa4934e7 → uint96[], uint256[]
getLockedAmount 0x929ec537 → uint256
getMaxPoolSize 0x0fbc8f5b → uint256
getMigrationTarget 0x1ddb5552 → address
getOperatorLimit 0x2624c83c → uint256
getRewardRate 0x7e1a3786 → uint256
getRewardTimestamps 0x59f01879 → uint256, uint256
getStake 0x7a766460 → uint256
getTotalCommunityStakedAmount 0x049b2ca0 → uint256
getTotalDelegatedAmount 0xa7a2f5aa → uint256
getTotalFrozenAmount 0xe9f37cdf → uint256
getTotalStakedAmount 0x38adb6f0 → uint256
isActive 0x22f3e2d4 → bool
isOperator 0x6d70f7ae → bool
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
typeAndVersion 0x181f5a77 → string
Write Contract 21 functions
These functions modify contract state and require a wallet transaction to execute.
acceptMigrationTarget 0xe937fdaa
No parameters
addOperators 0xa07aea1c
address[] operators
addReward 0x75c93bb9
uint256 amount
uint256 rewardDuration
claim 0x4e71d92d
No parameters
claimFrozenPrincipal 0xe8376b8a
No parameters
claimReward 0xb88a802f
No parameters
emergencyPause 0x51858e27
No parameters
emergencyUnpause 0x4a4e3bd5
No parameters
lock 0x282d3fdf
address staker
uint256 amount
migrate 0x8932a90d
bytes data
newReward 0xcfcd8fd8
uint256 amount
uint256 rewardDuration
proposeMigrationTarget 0x63b2c85a
address migrationTarget
renounceOwnership 0x715018a6
No parameters
setController 0x92eefe9b
address controller
setPoolConfig 0x4aba2ca9
uint256 maxPoolSize
uint256 maxCommunityStakeAmount
slashDelegationReward 0x8899fdeb
address staker
uint256 amount
stake 0xa694fc3a
uint256 amount
start 0x8fb4b573
uint256 amount
uint256 rewardDuration
transferOwnership 0xf2fde38b
address newOwner
unlock 0x7eee288d
address staker
uint256 amount
unstake 0x2e17de78
uint256 amount
Recent Transactions
No transactions found for this address