Address Contract Verified
Address
0x6335aB4ccBa45f99BE81Dc647657DE23a254493e
Balance
0 ETH
Nonce
1
Code Size
10336 bytes
Creator
0x880fED51...5db5 at tx 0xd74d1941...02aba5
Indexed Transactions
0
Contract Bytecode
10336 bytes
0x60806040526004361061013f5760003560e01c80638da5cb5b116100b6578063b218f0691161006f578063b218f069146103e7578063b9f7a7b514610407578063c34531531461043b578063cb43b2dd1461045b578063d68124c71461047b578063f7c618c11461049057600080fd5b80638da5cb5b146102b05780639168ae72146102c557806393ce53431461034e57806397e1b4bc146103635780639bdcecd11461038d578063a694fc3a146103d457600080fd5b8063372500ab11610108578063372500ab146101ee5780634fc3f41a146102035780635357e91614610223578063646b8b8a1461025057806372f702f3146102665780638caaa2711461029a57600080fd5b80621b79341461014457806313af40351461016657806316c621e0146101865780632d89fd5d146101a65780632e1a7d4d146101ce575b600080fd5b34801561015057600080fd5b5061016461015f36600461241d565b6104c4565b005b34801561017257600080fd5b5061016461018136600461243f565b610637565b34801561019257600080fd5b506101646101a136600461246f565b610668565b3480156101b257600080fd5b506101bb61069f565b6040519081526020015b60405180910390f35b3480156101da57600080fd5b506101646101e936600461246f565b6106c4565b3480156101fa57600080fd5b506101646106f4565b34801561020f57600080fd5b5061016461021e36600461246f565b610729565b34801561022f57600080fd5b5061024361023e36600461246f565b610817565b6040516101c59190612488565b34801561025c57600080fd5b506101bb60085481565b34801561027257600080fd5b506102437f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b60481565b3480156102a657600080fd5b506101bb60035481565b3480156102bc57600080fd5b50610243610841565b3480156102d157600080fd5b5061031c6102e036600461243f565b6005602052600090815260409020805460018201546002909201546001600160801b03821692600160801b9092046001600160401b0316919084565b604080516001600160801b0390951685526001600160401b0390931660208501529183015260608201526080016101c5565b34801561035a57600080fd5b506007546101bb565b34801561036f57600080fd5b50610378610850565b604080519283526020830191909152016101c5565b34801561039957600080fd5b506103c17f000000000000000000000000000000000000000000000000000000000000001281565b60405161ffff90911681526020016101c5565b6101646103e236600461246f565b6108d6565b3480156103f357600080fd5b5061016461040236600461249c565b610906565b34801561041357600080fd5b506103c17f000000000000000000000000000000000000000000000000000000000000001281565b34801561044757600080fd5b5061037861045636600461243f565b610a59565b34801561046757600080fd5b5061016461047636600461246f565b610a86565b34801561048757600080fd5b506101bb610ab6565b34801561049c57600080fd5b506102437f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b60481565b6104cc610afe565b6104f15760405162461bcd60e51b81526004016104e8906124c5565b60405180910390fd5b6002546000906006908290610511906001906001600160401b0316612503565b6001600160401b031681526020808201929092526040908101600020815160a08101835281546001600160501b038082168352600160501b8204811695830195909552600160a01b900490931691830191909152600181015460608301819052600290910154608083015290915083141580610591575080608001518214155b6105d75760405162461bcd60e51b81526020600482015260176024820152762932bbb0b932103930ba34b7903ab731b430b733b2b21760491b60448201526064016104e8565b80516105e4908484610b21565b60608082015160808084015160408051938452602084018890528301529181018490527feb6684a1e7c9bd2adc792fb253558f022bcbef39fb6ad31dc58cdfefdd5b5190910160405180910390a1505050565b61063f610afe565b61065c576040516316ccb9cb60e11b815260040160405180910390fd5b61066581610cdb565b50565b60026001540361068a5760405162461bcd60e51b81526004016104e89061252a565b600260015561069881610d2b565b5060018055565b60006008544210156106be57426008546106b99190612561565b905090565b50600090565b6002600154036106e65760405162461bcd60e51b81526004016104e89061252a565b600260015561069881610f83565b6002600154036107165760405162461bcd60e51b81526004016104e89061252a565b6002600155610723610fee565b60018055565b60026001540361074b5760405162461bcd60e51b81526004016104e89061252a565b6002600155610758610841565b6001600160a01b0316336001600160a01b0316146107885760405162461bcd60e51b81526004016104e8906124c5565b428110156107d85760405162461bcd60e51b815260206004820152601e60248201527f436f6f6c646f776e206d75737420626520696e2074686520667574757265000060448201526064016104e8565b60088190556040518181527ff4f81c8df394c367119ef9185e49924fbc14f33668d8c0fe5a767cc822858ae29060200160405180910390a15060018055565b6004818154811061082757600080fd5b6000918252602090912001546001600160a01b0316905081565b6000546001600160a01b031690565b60025460009081906006908290610872906001906001600160401b0316612503565b6001600160401b03168152602001908152602001600020600101549150600660006001600260009054906101000a90046001600160401b03166108b59190612503565b6001600160401b031681526020019081526020016000206002015490509091565b6002600154036108f85760405162461bcd60e51b81526004016104e89061252a565b600260015561069881611059565b61090e610afe565b61092a5760405162461bcd60e51b81526004016104e8906124c5565b600254600090600690829061094a906001906001600160401b0316612503565b6001600160401b031681526020808201929092526040908101600020815160a08101835281546001600160501b03808216808452600160501b8304821696840196909652600160a01b90910481169382019390935260018201546060820152600290910154608082015292508316036109fc5760405162461bcd60e51b81526020600482015260146024820152732a34b6b296bab734ba103ab731b430b733b2b21760611b60448201526064016104e8565b610a0f8282606001518360800151610b21565b8051604080516001600160501b03928316815291841660208301527fd968de290ed68f978b9e4816f7d4be9ef46189fe8eeb3eeb86199e7229cf2de0910160405180910390a15050565b6001600160a01b03811660009081526005602052604081206001015490610a7f83611400565b9050915091565b600260015403610aa85760405162461bcd60e51b81526004016104e89061252a565b600260015561069881611476565b6002546000906006908290610ad6906001906001600160401b0316612503565b6001600160401b031681526020810191909152604001600020546001600160501b0316919050565b6000610b08610841565b6001600160a01b0316336001600160a01b031614905090565b80600003610b5f5760405162461bcd60e51b815260206004820152600b60248201526a064697669646520627920360ac1b60448201526064016104e8565b826001600160501b0316600003610baf5760405162461bcd60e51b8152602060048201526014602482015273074696d652d756e69742063616e277420626520360641b60448201526064016104e8565b600280546001600160401b0316906001906000610bcc8385612574565b82546001600160401b039182166101009390930a9283029190920219909116179055506040805160a0810182526001600160501b03808716825242811660208084019182526000848601818152606086018a8152608087018a815289845260069094529690912094518554935191518516600160a01b0269ffffffffffffffffffff60a01b19928616600160501b026001600160a01b0319909516919095161792909217919091169190911782559151600182015590516002909101558015610cd5574260066000610c9f600185612561565b815260200190815260200160002060000160146101000a8154816001600160501b0302191690836001600160501b031602179055505b50505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d769190a35050565b610d33610841565b6001600160a01b0316336001600160a01b031614610d635760405162461bcd60e51b81526004016104e8906124c5565b6040516370a0823160e01b81526000906001600160a01b037f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b60416906370a0823190610db2903090600401612488565b602060405180830381865afa158015610dcf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610df39190612594565b6040516323b872dd60e01b81529091506001600160a01b037f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b60416906323b872dd90610e46903390309087906004016125ad565b6020604051808303816000875af1158015610e65573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e8991906125d1565b610ecb5760405162461bcd60e51b8152602060048201526013602482015272151c985b9cd9995c919c9bdb4819985a5b1959606a1b60448201526064016104e8565b6000817f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b6046001600160a01b03166370a08231306040518263ffffffff1660e01b8152600401610f1a9190612488565b602060405180830381865afa158015610f37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f5b9190612594565b610f659190612561565b90508060076000828254610f7991906125f3565b9091555050505050565b600854421015610fe55760405162461bcd60e51b815260206004820152602760248201527f436f6f6c646f776e206163746976653a207769746864726177206e6f7420617660448201526661696c61626c6560c81b60648201526084016104e8565b6106658161165b565b60085442101561104f5760405162461bcd60e51b815260206004820152602660248201527f436f6f6c646f776e206163746976653a2072657761726473206e6f7420617661604482015265696c61626c6560d01b60648201526084016104e8565b611057611924565b565b8060000361109c5760405162461bcd60e51b815260206004820152601060248201526f5374616b696e67203020746f6b656e7360801b60448201526064016104e8565b600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed197f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b6046001600160a01b03160161110a57507f0000000000000000000000000000000000000000000000000000000000000001611169565b34156111465760405162461bcd60e51b815260206004820152600b60248201526a056616c7565206e6f7420360ac1b60448201526064016104e8565b507f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b6045b336000908152600560205260409020600101541561118f5761118a33611a4e565b611247565b6004805460018082019092557f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b0180546001600160a01b03191633908117909155600090815260056020526040902080546001600160801b0319166001600160501b03421617905560025461120d91906001600160401b0316612503565b33600090815260056020526040902080546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790555b6040516370a0823160e01b81526000906001600160a01b038316906370a0823190611276903090600401612488565b602060405180830381865afa158015611293573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112b79190612594565b90506113067f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b6043330867f0000000000000000000000000000000000000000000000000000000000000001611b1e565b600081836001600160a01b03166370a08231306040518263ffffffff1660e01b81526004016113359190612488565b602060405180830381865afa158015611352573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113769190612594565b6113809190612561565b336000908152600560205260408120600101805492935083929091906113a79084906125f3565b9250508190555080600360008282546113c091906125f3565b909155505060405181815233907fb539ca1e5c8d398ddf1c41c30166f33404941683be4683319b57669a93dad4ef9060200160405180910390a250505050565b6001600160a01b038116600090815260056020526040812060010154810361144157506001600160a01b031660009081526005602052604090206002015490565b61144a82611c73565b6001600160a01b03831660009081526005602052604090206002015461147091906125f3565b92915050565b61147e610841565b6001600160a01b0316336001600160a01b0316146114ae5760405162461bcd60e51b81526004016104e8906124c5565b60075481116114ca57806007546114c59190612561565b6114cd565b60005b60075560405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b604169063a9059cbb9061151e9033908590600401612606565b6020604051808303816000875af115801561153d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061156191906125d1565b61157d5760405162461bcd60e51b81526004016104e89061261f565b6003546040516370a0823160e01b81526001600160a01b037f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b60416906370a08231906115cc903090600401612488565b602060405180830381865afa1580156115e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061160d9190612594565b10156106655760405162461bcd60e51b815260206004820152601e60248201527f5374616b696e6720746f6b656e2062616c616e636520726564756365642e000060448201526064016104e8565b33600090815260056020526040812060010154908290036116b55760405162461bcd60e51b81526020600482015260146024820152735769746864726177696e67203020746f6b656e7360601b60448201526064016104e8565b818110156117055760405162461bcd60e51b815260206004820152601c60248201527f5769746864726177696e67206d6f7265207468616e207374616b65640000000060448201526064016104e8565b61170e33611a4e565b81810361185d576000600480548060200260200160405190810160405280929190818152602001828054801561176d57602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161174f575b5050505050905060005b815181101561185a57336001600160a01b031682828151811061179c5761179c612648565b60200260200101516001600160a01b0316036118525781600183516117c19190612561565b815181106117d1576117d1612648565b6020026020010151600482815481106117ec576117ec612648565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b03160217905550600480548061182b5761182b61265e565b600082815260209020810160001990810180546001600160a01b031916905501905561185a565b600101611777565b50505b336000908152600560205260408120600101805484929061187f908490612561565b9250508190555081600360008282546118989190612561565b909155506118eb90507f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b6043033857f0000000000000000000000000000000000000000000000000000000000000001611b1e565b60405182815233907f6352c5382c4a4578e712449ca65e83cdb392d045dfcf1cad9615189db2da244b9060200160405180910390a25050565b600061192f33611c73565b3360009081526005602052604090206002015461194c91906125f3565b90508060000361198b5760405162461bcd60e51b815260206004820152600a6024820152694e6f207265776172647360b01b60448201526064016104e8565b33600090815260056020526040812080546001600160801b031916426001600160501b0316178155600290810191909155546119d2906001906001600160401b0316612503565b33600081815260056020526040902080546001600160401b0393909316600160801b0267ffffffffffffffff60801b1990931692909217909155611a169082611ea7565b60405181815233907ffc30cddea38e2bf4d6ea7d3f9ed3b6ad7f176419f4963bd81318067a4aee73fe9060200160405180910390a250565b6000611a5982611c73565b6001600160a01b038316600090815260056020526040812060020180549293508392909190611a899084906125f3565b90915550506001600160a01b038216600090815260056020526040902080546001600160801b0319166001600160501b034216179055600254611ad7906001906001600160401b0316612503565b6001600160a01b03909216600090815260056020526040902080546001600160401b0393909316600160801b0267ffffffffffffffff60801b199093169290921790915550565b8115611c6c5773eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03861601611c6057306001600160a01b03851603611bc357604051632e1a7d4d60e01b8152600481018390526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b158015611b9b57600080fd5b505af1158015611baf573d6000803e3d6000fd5b50505050611bbe838383611fbc565b611c6c565b306001600160a01b03841603611c5557348214611bfc576040516303e085f960e01b8152346004820152602481018390526044016104e8565b806001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b158015611c3757600080fd5b505af1158015611c4b573d6000803e3d6000fd5b5050505050611c6c565b611bbe838383611fbc565b611c6c85858585612081565b5050505050565b6001600160a01b0381166000908152600560209081526040808320815160808101835281546001600160801b03811682526001600160401b03600160801b90910481169482018590526001830154938201939093526002918201546060820152905490929116815b81811015611e30576000818152600660209081526040808320815160a08101835281546001600160501b038082168352600160501b8204811695830195909552600160a01b9004909316918301919091526001810154606083015260020154608082015290848303611d4e578551611d5d565b81602001516001600160501b03165b6001600160801b03169050600082604001516001600160501b0316600003611d855742611d94565b82604001516001600160501b03165b9050600080611dc189604001518585611dad9190612561565b611db79190612674565b86606001516120d9565b91509150600080611df98c886080015189600001516001600160501b031686611dea91906126a1565b611df491906126a1565b612124565b91509150838015611e075750815b611e11578b611e13565b805b9b5050505050505050600181611e2991906125f3565b9050611cdb565b50611e6584611e607f0000000000000000000000000000000000000000000000000000000000000012600a6127a7565b6120d9565b9450611e9490507f0000000000000000000000000000000000000000000000000000000000000012600a6127a7565b611e9e90856126a1565b95945050505050565b600754811115611ef45760405162461bcd60e51b81526020600482015260186024820152774e6f7420656e6f7567682072657761726420746f6b656e7360401b60448201526064016104e8565b8060076000828254611f069190612561565b909155505060405163a9059cbb60e01b81526001600160a01b037f000000000000000000000000f477ac7719e2e659001455cdda0cc8f3ad10b604169063a9059cbb90611f599085908590600401612606565b6020604051808303816000875af1158015611f78573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f9c91906125d1565b611fb85760405162461bcd60e51b81526004016104e89061261f565b5050565b6000836001600160a01b03168360405160006040518083038185875af1925050503d8060008114612009576040519150601f19603f3d011682016040523d82523d6000602084013e61200e565b606091505b5050905080610cd557816001600160a01b031663d0e30db0846040518263ffffffff1660e01b81526004016000604051808303818588803b15801561205257600080fd5b505af1158015612066573d6000803e3d6000fd5b50610cd5935050506001600160a01b0384169050858561213f565b816001600160a01b0316836001600160a01b03160315610cd557306001600160a01b038416036120c4576120bf6001600160a01b038516838361213f565b610cd5565b610cd56001600160a01b03851684848461219a565b600080836000036120f0575060019050600061211d565b838302838582816121035761210361268b565b041461211657600080925092505061211d565b6001925090505b9250929050565b6000808383018481101561211657600080925092505061211d565b6121958363a9059cbb60e01b848460405160240161215e929190612606565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b0319909316929092179091526121bb565b505050565b610cd5846323b872dd60e01b85858560405160240161215e939291906125ad565b6000612210826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661228d9092919063ffffffff16565b805190915015612195578080602001905181019061222e91906125d1565b6121955760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b60648201526084016104e8565b606061229c84846000856122a4565b949350505050565b6060824710156123055760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b60648201526084016104e8565b600080866001600160a01b0316858760405161232191906127db565b60006040518083038185875af1925050503d806000811461235e576040519150601f19603f3d011682016040523d82523d6000602084013e612363565b606091505b50915091506123748783838761237f565b979650505050505050565b606083156123ee5782516000036123e7576001600160a01b0385163b6123e75760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016104e8565b508161229c565b61229c83838151156124035781518083602001fd5b8060405162461bcd60e51b81526004016104e891906127f7565b6000806040838503121561243057600080fd5b50508035926020909101359150565b60006020828403121561245157600080fd5b81356001600160a01b038116811461246857600080fd5b9392505050565b60006020828403121561248157600080fd5b5035919050565b6001600160a01b0391909116815260200190565b6000602082840312156124ae57600080fd5b81356001600160501b038116811461246857600080fd5b6020808252600e908201526d139bdd08185d5d1a1bdc9a5e995960921b604082015260600190565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03828116828216039080821115612523576125236124ed565b5092915050565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b81810381811115611470576114706124ed565b6001600160401b03818116838216019080821115612523576125236124ed565b6000602082840312156125a657600080fd5b5051919050565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6000602082840312156125e357600080fd5b8151801515811461246857600080fd5b80820180821115611470576114706124ed565b6001600160a01b03929092168252602082015260400190565b6020808252600f908201526e151c985b9cd9995c8819985a5b1959608a1b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052603160045260246000fd5b8082028115828204841417611470576114706124ed565b634e487b7160e01b600052601260045260246000fd5b6000826126be57634e487b7160e01b600052601260045260246000fd5b500490565b600181815b808511156126fe5781600019048211156126e4576126e46124ed565b808516156126f157918102915b93841c93908002906126c8565b509250929050565b60008261271557506001611470565b8161272257506000611470565b816001811461273857600281146127425761275e565b6001915050611470565b60ff841115612753576127536124ed565b50506001821b611470565b5060208310610133831016604e8410600b8410161715612781575081810a611470565b61278b83836126c3565b806000190482111561279f5761279f6124ed565b029392505050565b600061246861ffff841683612706565b60005b838110156127d25781810151838201526020016127ba565b50506000910152565b600082516127ed8184602087016127b7565b9190910192915050565b60208152600082518060208401526128168160408501602087016127b7565b601f01601f1916919091016040019291505056fea2646970667358221220207f59e31c0bd9994a15b7ce73eb3b5da0b3cab1ec98e4d2c8632af5ecdd629064736f6c63430008170033
Verified Source Code Full Match
Compiler: v0.8.23+commit.f704f362
EVM: paris
Optimization: Yes (100 runs)
IERC20.sol 24 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
IERC20Metadata.sol 14 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/**
* @title ERC20Metadata interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20Metadata {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
IOwnable.sol 21 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
* information about who the contract's owner is.
*/
interface IOwnable {
/// @dev Returns the owner of the contract.
function owner() external view returns (address);
/// @dev Lets a module admin set a new owner for the contract. The new owner must be a module admin.
function setOwner(address _newOwner) external;
/// @dev Emitted when a new Owner is set.
event OwnerUpdated(address indexed prevOwner, address indexed newOwner);
}
IStaking20.sol 97 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;
/// @author thirdweb
interface IStaking20 {
/// @dev Emitted when tokens are staked.
event TokensStaked(address indexed staker, uint256 amount);
/// @dev Emitted when a tokens are withdrawn.
event TokensWithdrawn(address indexed staker, uint256 amount);
/// @dev Emitted when a staker claims staking rewards.
event RewardsClaimed(address indexed staker, uint256 rewardAmount);
/// @dev Emitted when contract admin updates timeUnit.
event UpdatedTimeUnit(uint256 oldTimeUnit, uint256 newTimeUnit);
/// @dev Emitted when contract admin updates rewardsPerUnitTime.
event UpdatedRewardRatio(
uint256 oldNumerator,
uint256 newNumerator,
uint256 oldDenominator,
uint256 newDenominator
);
/// @dev Emitted when contract admin updates minimum staking amount.
event UpdatedMinStakeAmount(uint256 oldAmount, uint256 newAmount);
/**
* @notice Staker Info.
*
* @param amountStaked Total number of tokens staked by the staker.
*
* @param timeOfLastUpdate Last reward-update timestamp.
*
* @param unclaimedRewards Rewards accumulated but not claimed by user yet.
*
* @param conditionIdOflastUpdate Condition-Id when rewards were last updated for user.
*/
struct Staker {
uint128 timeOfLastUpdate;
uint64 conditionIdOflastUpdate;
uint256 amountStaked;
uint256 unclaimedRewards;
}
/**
* @notice Staking Condition.
*
* @param timeUnit Unit of time specified in number of seconds. Can be set as 1 seconds, 1 days, 1 hours, etc.
*
* @param rewardRatioNumerator Rewards ratio is the number of reward tokens for a number of staked tokens,
* per unit of time.
*
* @param rewardRatioDenominator Rewards ratio is the number of reward tokens for a number of staked tokens,
* per unit of time.
*
* @param startTimestamp Condition start timestamp.
*
* @param endTimestamp Condition end timestamp.
*/
struct StakingCondition {
uint80 timeUnit;
uint80 startTimestamp;
uint80 endTimestamp;
uint256 rewardRatioNumerator;
uint256 rewardRatioDenominator;
}
/**
* @notice Stake ERC721 Tokens.
*
* @param amount Amount to stake.
*/
function stake(uint256 amount) external payable;
/**
* @notice Withdraw staked tokens.
*
* @param amount Amount to withdraw.
*/
function withdraw(uint256 amount) external;
/**
* @notice Claim accumulated rewards.
*
*/
function claimRewards() external;
/**
* @notice View amount staked and total rewards for a user.
*
* @param staker Address for which to calculated rewards.
*/
function getStakeInfo(address staker) external view returns (uint256 _tokensStaked, uint256 _rewards);
}
Ownable.sol 58 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "./interface/IOwnable.sol";
/**
* @title Ownable
* @notice Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
* information about who the contract's owner is.
*/
abstract contract Ownable is IOwnable {
/// @dev The sender is not authorized to perform the action
error OwnableUnauthorized();
/// @dev Owner of the contract (purpose: OpenSea compatibility)
address private _owner;
/// @dev Reverts if caller is not the owner.
modifier onlyOwner() {
if (msg.sender != _owner) {
revert OwnableUnauthorized();
}
_;
}
/**
* @notice Returns the owner of the contract.
*/
function owner() public view override returns (address) {
return _owner;
}
/**
* @notice Lets an authorized wallet set a new owner for the contract.
* @param _newOwner The address to set as the new owner of the contract.
*/
function setOwner(address _newOwner) external override {
if (!_canSetOwner()) {
revert OwnableUnauthorized();
}
_setupOwner(_newOwner);
}
/// @dev Lets a contract admin set a new owner for the contract. The new owner must be a contract admin.
function _setupOwner(address _newOwner) internal {
address _prevOwner = _owner;
_owner = _newOwner;
emit OwnerUpdated(_prevOwner, _newOwner);
}
/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view virtual returns (bool);
}
Staking20.sol 376 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.11;
/// @author thirdweb
import "../external-deps/openzeppelin/security/ReentrancyGuard.sol";
import "../external-deps/openzeppelin/utils/math/SafeMath.sol";
import "../eip/interface/IERC20.sol";
import { CurrencyTransferLib } from "../lib/CurrencyTransferLib.sol";
import "./interface/IStaking20.sol";
abstract contract Staking20 is ReentrancyGuard, IStaking20 {
/*///////////////////////////////////////////////////////////////
State variables / Mappings
//////////////////////////////////////////////////////////////*/
/// @dev The address of the native token wrapper contract.
address internal immutable nativeTokenWrapper;
///@dev Address of ERC20 contract -- staked tokens belong to this contract.
address public immutable stakingToken;
/// @dev Decimals of staking token.
uint16 public immutable stakingTokenDecimals;
/// @dev Decimals of reward token.
uint16 public immutable rewardTokenDecimals;
///@dev Next staking condition Id. Tracks number of conditon updates so far.
uint64 private nextConditionId;
/// @dev Total amount of tokens staked in the contract.
uint256 public stakingTokenBalance;
/// @dev List of accounts that have staked that token-id.
address[] public stakersArray;
///@dev Mapping staker address to Staker struct. See {struct IStaking20.Staker}.
mapping(address => Staker) public stakers;
///@dev Mapping from condition Id to staking condition. See {struct IStaking721.StakingCondition}
mapping(uint256 => StakingCondition) private stakingConditions;
constructor(
address _nativeTokenWrapper,
address _stakingToken,
uint16 _stakingTokenDecimals,
uint16 _rewardTokenDecimals
) ReentrancyGuard() {
require(_stakingToken != address(0) && _nativeTokenWrapper != address(0), "address 0");
require(_stakingTokenDecimals != 0 && _rewardTokenDecimals != 0, "decimals 0");
nativeTokenWrapper = _nativeTokenWrapper;
stakingToken = _stakingToken;
stakingTokenDecimals = _stakingTokenDecimals;
rewardTokenDecimals = _rewardTokenDecimals;
}
/*///////////////////////////////////////////////////////////////
External/Public Functions
//////////////////////////////////////////////////////////////*/
/**
* @notice Stake ERC20 Tokens.
*
* @dev See {_stake}. Override that to implement custom logic.
*
* @param _amount Amount to stake.
*/
function stake(uint256 _amount) external payable nonReentrant {
_stake(_amount);
}
/**
* @notice Withdraw staked ERC20 tokens.
*
* @dev See {_withdraw}. Override that to implement custom logic.
*
* @param _amount Amount to withdraw.
*/
function withdraw(uint256 _amount) external nonReentrant {
_withdraw(_amount);
}
/**
* @notice Claim accumulated rewards.
*
* @dev See {_claimRewards}. Override that to implement custom logic.
* See {_calculateRewards} for reward-calculation logic.
*/
function claimRewards() external nonReentrant {
_claimRewards();
}
/**
* @notice Set time unit. Set as a number of seconds.
* Could be specified as -- x * 1 hours, x * 1 days, etc.
*
* @dev Only admin/authorized-account can call it.
*
* @param _timeUnit New time unit.
*/
function setTimeUnit(uint80 _timeUnit) external virtual {
if (!_canSetStakeConditions()) {
revert("Not authorized");
}
StakingCondition memory condition = stakingConditions[nextConditionId - 1];
require(_timeUnit != condition.timeUnit, "Time-unit unchanged.");
_setStakingCondition(_timeUnit, condition.rewardRatioNumerator, condition.rewardRatioDenominator);
emit UpdatedTimeUnit(condition.timeUnit, _timeUnit);
}
/**
* @notice Set rewards per unit of time.
* Interpreted as (numerator/denominator) rewards per second/per day/etc based on time-unit.
*
* For e.g., ratio of 1/20 would mean 1 reward token for every 20 tokens staked.
*
* @dev Only admin/authorized-account can call it.
*
* @param _numerator Reward ratio numerator.
* @param _denominator Reward ratio denominator.
*/
function setRewardRatio(uint256 _numerator, uint256 _denominator) external virtual {
if (!_canSetStakeConditions()) {
revert("Not authorized");
}
StakingCondition memory condition = stakingConditions[nextConditionId - 1];
require(
_numerator != condition.rewardRatioNumerator || _denominator != condition.rewardRatioDenominator,
"Reward ratio unchanged."
);
_setStakingCondition(condition.timeUnit, _numerator, _denominator);
emit UpdatedRewardRatio(
condition.rewardRatioNumerator,
_numerator,
condition.rewardRatioDenominator,
_denominator
);
}
/**
* @notice View amount staked and rewards for a user.
*
* @param _staker Address for which to calculated rewards.
* @return _tokensStaked Amount of tokens staked.
* @return _rewards Available reward amount.
*/
function getStakeInfo(address _staker) external view virtual returns (uint256 _tokensStaked, uint256 _rewards) {
_tokensStaked = stakers[_staker].amountStaked;
_rewards = _availableRewards(_staker);
}
function getTimeUnit() public view returns (uint256 _timeUnit) {
_timeUnit = stakingConditions[nextConditionId - 1].timeUnit;
}
function getRewardRatio() public view returns (uint256 _numerator, uint256 _denominator) {
_numerator = stakingConditions[nextConditionId - 1].rewardRatioNumerator;
_denominator = stakingConditions[nextConditionId - 1].rewardRatioDenominator;
}
/*///////////////////////////////////////////////////////////////
Internal Functions
//////////////////////////////////////////////////////////////*/
/// @dev Staking logic. Override to add custom logic.
function _stake(uint256 _amount) internal virtual {
require(_amount != 0, "Staking 0 tokens");
address _stakingToken;
if (stakingToken == CurrencyTransferLib.NATIVE_TOKEN) {
_stakingToken = nativeTokenWrapper;
} else {
require(msg.value == 0, "Value not 0");
_stakingToken = stakingToken;
}
if (stakers[_stakeMsgSender()].amountStaked > 0) {
_updateUnclaimedRewardsForStaker(_stakeMsgSender());
} else {
stakersArray.push(_stakeMsgSender());
stakers[_stakeMsgSender()].timeOfLastUpdate = uint80(block.timestamp);
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
}
uint256 balanceBefore = IERC20(_stakingToken).balanceOf(address(this));
CurrencyTransferLib.transferCurrencyWithWrapper(
stakingToken,
_stakeMsgSender(),
address(this),
_amount,
nativeTokenWrapper
);
uint256 actualAmount = IERC20(_stakingToken).balanceOf(address(this)) - balanceBefore;
stakers[_stakeMsgSender()].amountStaked += actualAmount;
stakingTokenBalance += actualAmount;
emit TokensStaked(_stakeMsgSender(), actualAmount);
}
/// @dev Withdraw logic. Override to add custom logic.
function _withdraw(uint256 _amount) internal virtual {
uint256 _amountStaked = stakers[_stakeMsgSender()].amountStaked;
require(_amount != 0, "Withdrawing 0 tokens");
require(_amountStaked >= _amount, "Withdrawing more than staked");
_updateUnclaimedRewardsForStaker(_stakeMsgSender());
if (_amountStaked == _amount) {
address[] memory _stakersArray = stakersArray;
for (uint256 i = 0; i < _stakersArray.length; ++i) {
if (_stakersArray[i] == _stakeMsgSender()) {
stakersArray[i] = _stakersArray[_stakersArray.length - 1];
stakersArray.pop();
break;
}
}
}
stakers[_stakeMsgSender()].amountStaked -= _amount;
stakingTokenBalance -= _amount;
CurrencyTransferLib.transferCurrencyWithWrapper(
stakingToken,
address(this),
_stakeMsgSender(),
_amount,
nativeTokenWrapper
);
emit TokensWithdrawn(_stakeMsgSender(), _amount);
}
/// @dev Logic for claiming rewards. Override to add custom logic.
function _claimRewards() internal virtual {
uint256 rewards = stakers[_stakeMsgSender()].unclaimedRewards + _calculateRewards(_stakeMsgSender());
require(rewards != 0, "No rewards");
stakers[_stakeMsgSender()].timeOfLastUpdate = uint80(block.timestamp);
stakers[_stakeMsgSender()].unclaimedRewards = 0;
stakers[_stakeMsgSender()].conditionIdOflastUpdate = nextConditionId - 1;
_mintRewards(_stakeMsgSender(), rewards);
emit RewardsClaimed(_stakeMsgSender(), rewards);
}
/// @dev View available rewards for a user.
function _availableRewards(address _staker) internal view virtual returns (uint256 _rewards) {
if (stakers[_staker].amountStaked == 0) {
_rewards = stakers[_staker].unclaimedRewards;
} else {
_rewards = stakers[_staker].unclaimedRewards + _calculateRewards(_staker);
}
}
/// @dev Update unclaimed rewards for a users. Called for every state change for a user.
function _updateUnclaimedRewardsForStaker(address _staker) internal virtual {
uint256 rewards = _calculateRewards(_staker);
stakers[_staker].unclaimedRewards += rewards;
stakers[_staker].timeOfLastUpdate = uint80(block.timestamp);
stakers[_staker].conditionIdOflastUpdate = nextConditionId - 1;
}
/// @dev Set staking conditions.
function _setStakingCondition(uint80 _timeUnit, uint256 _numerator, uint256 _denominator) internal virtual {
require(_denominator != 0, "divide by 0");
require(_timeUnit != 0, "time-unit can't be 0");
uint256 conditionId = nextConditionId;
nextConditionId += 1;
stakingConditions[conditionId] = StakingCondition({
timeUnit: _timeUnit,
rewardRatioNumerator: _numerator,
rewardRatioDenominator: _denominator,
startTimestamp: uint80(block.timestamp),
endTimestamp: 0
});
if (conditionId > 0) {
stakingConditions[conditionId - 1].endTimestamp = uint80(block.timestamp);
}
}
/// @dev Calculate rewards for a staker.
function _calculateRewards(address _staker) internal view virtual returns (uint256 _rewards) {
Staker memory staker = stakers[_staker];
uint256 _stakerConditionId = staker.conditionIdOflastUpdate;
uint256 _nextConditionId = nextConditionId;
for (uint256 i = _stakerConditionId; i < _nextConditionId; i += 1) {
StakingCondition memory condition = stakingConditions[i];
uint256 startTime = i != _stakerConditionId ? condition.startTimestamp : staker.timeOfLastUpdate;
uint256 endTime = condition.endTimestamp != 0 ? condition.endTimestamp : block.timestamp;
(bool noOverflowProduct, uint256 rewardsProduct) = SafeMath.tryMul(
(endTime - startTime) * staker.amountStaked,
condition.rewardRatioNumerator
);
(bool noOverflowSum, uint256 rewardsSum) = SafeMath.tryAdd(
_rewards,
(rewardsProduct / condition.timeUnit) / condition.rewardRatioDenominator
);
_rewards = noOverflowProduct && noOverflowSum ? rewardsSum : _rewards;
}
(, _rewards) = SafeMath.tryMul(_rewards, 10 ** rewardTokenDecimals);
_rewards /= (10 ** stakingTokenDecimals);
}
/*////////////////////////////////////////////////////////////////////
Optional hooks that can be implemented in the derived contract
///////////////////////////////////////////////////////////////////*/
/// @dev Exposes the ability to override the msg sender -- support ERC2771.
function _stakeMsgSender() internal virtual returns (address) {
return msg.sender;
}
/*///////////////////////////////////////////////////////////////
Virtual functions to be implemented in derived contract
//////////////////////////////////////////////////////////////*/
/**
* @notice View total rewards available in the staking contract.
*
*/
function getRewardTokenBalance() external view virtual returns (uint256 _rewardsAvailableInContract);
/**
* @dev Mint/Transfer ERC20 rewards to the staker. Must override.
*
* @param _staker Address for which to calculated rewards.
* @param _rewards Amount of tokens to be given out as reward.
*
* For example, override as below to mint ERC20 rewards:
*
* ```
* function _mintRewards(address _staker, uint256 _rewards) internal override {
*
* TokenERC20(rewardTokenAddress).mintTo(_staker, _rewards);
*
* }
* ```
*/
function _mintRewards(address _staker, uint256 _rewards) internal virtual;
/**
* @dev Returns whether staking restrictions can be set in given execution context.
* Must override.
*
*
* For example, override as below to restrict access to admin:
*
* ```
* function _canSetStakeConditions() internal override {
*
* return msg.sender == adminAddress;
*
* }
* ```
*/
function _canSetStakeConditions() internal view virtual returns (bool);
}
ReentrancyGuard.sol 32 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
SafeERC20.sol 78 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../../../../../eip/interface/IERC20.sol";
import { Address } from "../../../../../lib/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));
}
}
/**
* @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");
}
}
}
SafeMath.sol 215 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)
pragma solidity ^0.8.0;
// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.
/**
* @dev Wrappers over Solidity's arithmetic operations.
*
* NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
* now has built in overflow checking.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*
* _Available since v3.4._
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*
* _Available since v3.4._
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
*
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
*
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
/**
* @dev Returns the integer division of two unsigned integers, reverting on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator.
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return a % b;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {trySub}.
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
*
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b <= a, errorMessage);
return a - b;
}
}
/**
* @dev Returns the integer division of two unsigned integers, reverting with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a / b;
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting with custom message when dividing by zero.
*
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryMod}.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
*
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
unchecked {
require(b > 0, errorMessage);
return a % b;
}
}
}
IWETH.sol 10 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
interface IWETH {
function deposit() external payable;
function withdraw(uint256 amount) external;
function transfer(address to, uint256 value) external returns (bool);
}
Address.sol 244 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.1;
/// @author thirdweb, OpenZeppelin Contracts (v4.9.0)
/**
* @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
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [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://consensys.net/diligence/blog/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.8.0/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);
}
}
}
CurrencyTransferLib.sol 96 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
// Helper interfaces
import { IWETH } from "../infra/interface/IWETH.sol";
import { SafeERC20, IERC20 } from "../external-deps/openzeppelin/token/ERC20/utils/SafeERC20.sol";
library CurrencyTransferLib {
using SafeERC20 for IERC20;
error CurrencyTransferLibMismatchedValue(uint256 expected, uint256 actual);
error CurrencyTransferLibFailedNativeTransfer(address recipient, uint256 value);
/// @dev The address interpreted as native token of the chain.
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Transfers a given amount of currency.
function transferCurrency(address _currency, address _from, address _to, uint256 _amount) internal {
if (_amount == 0) {
return;
}
if (_currency == NATIVE_TOKEN) {
safeTransferNativeToken(_to, _amount);
} else {
safeTransferERC20(_currency, _from, _to, _amount);
}
}
/// @dev Transfers a given amount of currency. (With native token wrapping)
function transferCurrencyWithWrapper(
address _currency,
address _from,
address _to,
uint256 _amount,
address _nativeTokenWrapper
) internal {
if (_amount == 0) {
return;
}
if (_currency == NATIVE_TOKEN) {
if (_from == address(this)) {
// withdraw from weth then transfer withdrawn native token to recipient
IWETH(_nativeTokenWrapper).withdraw(_amount);
safeTransferNativeTokenWithWrapper(_to, _amount, _nativeTokenWrapper);
} else if (_to == address(this)) {
// store native currency in weth
if (_amount != msg.value) {
revert CurrencyTransferLibMismatchedValue(msg.value, _amount);
}
IWETH(_nativeTokenWrapper).deposit{ value: _amount }();
} else {
safeTransferNativeTokenWithWrapper(_to, _amount, _nativeTokenWrapper);
}
} else {
safeTransferERC20(_currency, _from, _to, _amount);
}
}
/// @dev Transfer `amount` of ERC20 token from `from` to `to`.
function safeTransferERC20(address _currency, address _from, address _to, uint256 _amount) internal {
if (_from == _to) {
return;
}
if (_from == address(this)) {
IERC20(_currency).safeTransfer(_to, _amount);
} else {
IERC20(_currency).safeTransferFrom(_from, _to, _amount);
}
}
/// @dev Transfers `amount` of native token to `to`.
function safeTransferNativeToken(address to, uint256 value) internal {
// solhint-disable avoid-low-level-calls
// slither-disable-next-line low-level-calls
(bool success, ) = to.call{ value: value }("");
if (!success) {
revert CurrencyTransferLibFailedNativeTransfer(to, value);
}
}
/// @dev Transfers `amount` of native token to `to`. (With native token wrapping)
function safeTransferNativeTokenWithWrapper(address to, uint256 value, address _nativeTokenWrapper) internal {
// solhint-disable avoid-low-level-calls
// slither-disable-next-line low-level-calls
(bool success, ) = to.call{ value: value }("");
if (!success) {
IWETH(_nativeTokenWrapper).deposit{ value: value }();
IERC20(_nativeTokenWrapper).safeTransfer(to, value);
}
}
}
Staking20Base.sol 158 lines
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.23;
/// ERC20 Token Staking
import "@thirdweb-dev/contracts/extension/Ownable.sol";
import "@thirdweb-dev/contracts/extension/Staking20.sol";
import "@thirdweb-dev/contracts/eip/interface/IERC20.sol";
import "@thirdweb-dev/contracts/eip/interface/IERC20Metadata.sol";
/**
*
* The `Staking20Base` smart contract implements Token staking mechanism.
* Allows users to stake their ERC-20 Tokens and earn rewards in form of same or another ERC-20 tokens.
*
* Fork and Customized from thirdweb, link: https://raw.githubusercontent.com/thirdweb-dev/contracts/refs/heads/main/contracts/base/Staking20Base.sol
*
* Following features and implementation setup must be noted:
*
* - ERC-20 Tokens from only one contract can be staked.
*
* - Contract admin can choose to give out rewards by either transferring the rewardToken,
* which is same or different ERC20 token. See {_mintRewards}.
*
* - Ownership of the contract, with the ability to restrict certain functions to
* only be called by the contract's owner.
*
* - Cooldown capability to stop withdraw and claim for a duration.
*
*/
contract Staking20Base is Ownable, Staking20 {
/// @dev ERC20 Reward Token address. See {_mintRewards} below.
address public immutable rewardToken;
/// @dev Total amount of reward tokens in the contract.
uint256 private rewardTokenBalance;
/// @dev Global cooldown timer. Blocks withdraw and claim if active.
uint256 public cooldownEndsAt;
event CooldownSet(uint256 endsAt);
constructor(
uint80 _timeUnit,
address _defaultAdmin,
uint256 _rewardRatioNumerator,
uint256 _rewardRatioDenominator,
address _stakingToken,
address _rewardToken
)
Staking20(
address(1), // placeholder since nativeTokenWrapper is unused
_stakingToken,
IERC20Metadata(_stakingToken).decimals(),
IERC20Metadata(_rewardToken).decimals()
)
{
require(_rewardToken != address(0), "Reward token is zero");
require(_stakingToken != address(0), "Staking token is zero");
_setupOwner(_defaultAdmin);
_setStakingCondition(_timeUnit, _rewardRatioNumerator, _rewardRatioDenominator);
rewardToken = _rewardToken;
}
/*//////////////////////////////////////////////////////////////
Admin Functions
//////////////////////////////////////////////////////////////*/
/// @dev Admin can set a cooldown period.
function setCooldown(uint256 _cooldownEndsAt) external virtual nonReentrant {
require(msg.sender == owner(), "Not authorized");
require(_cooldownEndsAt >= block.timestamp, "Cooldown must be in the future");
cooldownEndsAt = _cooldownEndsAt;
emit CooldownSet(_cooldownEndsAt);
}
/// @notice View get active and remaining cool down.
function getCooldownRemaining() external view returns (uint256) {
return block.timestamp >= cooldownEndsAt ? 0 : cooldownEndsAt - block.timestamp;
}
/// @dev Admin deposits reward tokens.
function depositRewardTokens(uint256 amount) external virtual nonReentrant {
_depositRewardTokens(amount); // override this for custom logic.
}
/// @dev Admin can withdraw excess reward tokens.
function withdrawRewardTokens(uint256 amount) external virtual nonReentrant {
_withdrawRewardTokens(amount); // override this for custom logic.
}
/// @notice View total rewards available in the staking contract.
function getRewardTokenBalance() external view virtual override returns (uint256) {
return rewardTokenBalance;
}
/*//////////////////////////////////////////////////////////////
Core Logic Overrides
//////////////////////////////////////////////////////////////*/
/// @dev Transfer ERC20 rewards to the staker.
function _mintRewards(address _staker, uint256 _rewards) internal virtual override {
require(_rewards <= rewardTokenBalance, "Not enough reward tokens");
rewardTokenBalance -= _rewards;
require(IERC20(rewardToken).transfer(_staker, _rewards), "Transfer failed");
}
/// @dev Withdraw logic with cooldown check.
function _withdraw(uint256 _amount) internal virtual override {
require(block.timestamp >= cooldownEndsAt, "Cooldown active: withdraw not available");
super._withdraw(_amount);
}
/// @dev Claim rewards with cooldown check.
function _claimRewards() internal virtual override {
require(block.timestamp >= cooldownEndsAt, "Cooldown active: rewards not available");
super._claimRewards();
}
/// @dev Admin deposits reward tokens
function _depositRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");
uint256 balanceBefore = IERC20(rewardToken).balanceOf(address(this));
require(IERC20(rewardToken).transferFrom(msg.sender, address(this), _amount), "TransferFrom failed");
uint256 actualAmount = IERC20(rewardToken).balanceOf(address(this)) - balanceBefore;
rewardTokenBalance += actualAmount;
}
/// @dev Admin can withdraw excess reward tokens
function _withdrawRewardTokens(uint256 _amount) internal virtual {
require(msg.sender == owner(), "Not authorized");
rewardTokenBalance = _amount > rewardTokenBalance ? 0 : rewardTokenBalance - _amount;
require(IERC20(rewardToken).transfer(msg.sender, _amount), "Transfer failed");
require(
IERC20(stakingToken).balanceOf(address(this)) >= stakingTokenBalance,
"Staking token balance reduced."
);
}
/// @dev Staking restrictions set in given execution context.
function _canSetStakeConditions() internal view virtual override returns (bool) {
return msg.sender == owner();
}
/// @dev Owner can be set in the given execution context.
function _canSetOwner() internal view virtual override returns (bool) {
return msg.sender == owner();
}
}
Read Contract
cooldownEndsAt 0x646b8b8a → uint256
getCooldownRemaining 0x2d89fd5d → uint256
getRewardRatio 0x97e1b4bc → uint256, uint256
getRewardTokenBalance 0x93ce5343 → uint256
getStakeInfo 0xc3453153 → uint256, uint256
getTimeUnit 0xd68124c7 → uint256
owner 0x8da5cb5b → address
rewardToken 0xf7c618c1 → address
rewardTokenDecimals 0x9bdcecd1 → uint16
stakers 0x9168ae72 → uint128, uint64, uint256, uint256
stakersArray 0x5357e916 → address
stakingToken 0x72f702f3 → address
stakingTokenBalance 0x8caaa271 → uint256
stakingTokenDecimals 0xb9f7a7b5 → uint16
Write Contract 9 functions
These functions modify contract state and require a wallet transaction to execute.
claimRewards 0x372500ab
No parameters
depositRewardTokens 0x16c621e0
uint256 amount
setCooldown 0x4fc3f41a
uint256 _cooldownEndsAt
setOwner 0x13af4035
address _newOwner
setRewardRatio 0x001b7934
uint256 _numerator
uint256 _denominator
setTimeUnit 0xb218f069
uint80 _timeUnit
stake 0xa694fc3a
uint256 _amount
withdraw 0x2e1a7d4d
uint256 _amount
withdrawRewardTokens 0xcb43b2dd
uint256 amount
Recent Transactions
No transactions found for this address