Cryo Explorer Ethereum Mainnet

Address Contract Partially Verified

Address 0xF3D6Af45C6dFeC43216CC3347Ea91fEfBa0849D1
Balance 0 ETH
Nonce 1
Code Size 18722 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

18722 bytes
0x608060405234801561001057600080fd5b50600436106101f05760003560e01c8063688d872c1161010f578063a9059cbb116100a2578063dc3b52d711610071578063dc3b52d714610455578063dd62ed3e14610468578063f2fde38b1461047b578063fe9d93031461048e576101f0565b8063a9059cbb146103fb578063af6eec541461040e578063b44be68c1461042f578063b55ff88414610442576101f0565b80638da5cb5b116100de5780638da5cb5b146103ab57806395d89b41146103c05780639cc0b8b3146103c8578063a457c2d7146103e8576101f0565b8063688d872c1461036a57806370a082311461037d578063715018a6146103905780637675dda414610398576101f0565b80632d0335ab1161018757806340c10f191161015657806340c10f191461031e5780634598980b14610331578063470e5a92146103445780634cc9512314610357576101f0565b80632d0335ab146102b65780632ec4ac0a146102d6578063313ce567146102f6578063395093511461030b576101f0565b806318160ddd116101c357806318160ddd1461025b57806323b872dd14610270578063240fa6de14610283578063252b2d20146102a3576101f0565b806306fdde03146101f5578063095ea7b31461021357806309ee40a01461023357806310b4a23d14610246575b600080fd5b6101fd6104a1565b60405161020a9190614182565b60405180910390f35b610226610221366004613969565b610538565b60405161020a919061405c565b610226610241366004613774565b61055c565b6102596102543660046138ff565b6105fd565b005b61026361062f565b60405161020a91906146d7565b61022661027e366004613734565b610635565b61029661029136600461385a565b61073b565b60405161020a9190614676565b6102966102b1366004613994565b6107fe565b6102c96102c43660046136e0565b610868565b60405161020a91906146f9565b6102e96102e43660046136e0565b61089f565b60405161020a919061469f565b6102fe6108c9565b60405161020a919061470d565b610226610319366004613969565b6108ce565b61025961032c366004613969565b610918565b61025961033f366004613969565b610957565b61025961035236600461385a565b61099f565b610259610365366004613a67565b610a58565b610259610378366004613bd4565b610acf565b61026361038b3660046136e0565b610b6a565b610259610b8e565b6102596103a63660046139c1565b610c0d565b6103b3610c7f565b60405161020a9190613edf565b6101fd610c8e565b6103db6103d63660046136e0565b610cef565b60405161020a9190614647565b6102266103f6366004613969565b610d4e565b610226610409366004613969565b610dae565b61042161041c36600461380d565b610e27565b60405161020a92919061409e565b61025961043d36600461388f565b610ef4565b610259610450366004613969565b610f19565b610259610463366004613ce7565b610f61565b6102636104763660046136fc565b611030565b6102596104893660046136e0565b61105b565b61025961049c366004613d8a565b611112565b60068054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b820191906000526020600020905b81548152906001019060200180831161051057829003601f168201915b505050505090505b90565b600033610545818561115e565b61055081858561119a565b60019150505b92915050565b6000610568868661115e565b610570613091565b61057987611202565b90506105be8787878488888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506112a692505050565b6105e35760405162461bcd60e51b81526004016105da9061438e565b60405180910390fd5b6105ee87878761132c565b60019150505b95945050505050565b6106056113a4565b6106215760405162461bcd60e51b81526004016105da906141f6565b61062b82826113ec565b5050565b60055490565b6000610641848461115e565b3361064a613091565b61065386611202565b80519091508015610665575080602001515b156106c557816001600160a01b0316866001600160a01b03161461069b5760405162461bcd60e51b81526004016105da90614313565b6106ba600083888888604051806020016040528060008152508761152d565b600192505050610734565b6040805180820182526007815266115490cc8c0b4d60ca1b6020808301919091526001600160a01b03808a16600090815260098352848120918716815291529190912054610722918891859161071d91908990611644565b61119a565b61072d86868661132c565b6001925050505b9392505050565b6107436130b8565b600060036000610757868660200151611670565b81526020810191909152604001600020805490915061010090046001600160401b03166107965760405162461bcd60e51b81526004016105da906141d8565b8251815460ff9081169116146107be5760405162461bcd60e51b81526004016105da90614216565b60408051606081018252915460ff8116835261010081046001600160401b03166020840152600160481b90046001600160a01b0316908201529392505050565b6108066130b8565b600360006108148585611670565b815260208082019290925260409081016000208151606081018352905460ff8116825261010081046001600160401b031693820193909352600160481b9092046001600160a01b0316908201529392505050565b60006108726130b8565b6001600160a01b038316600090815260086020526040902054610894906116a3565b604001519392505050565b6108a76130b8565b6001600160a01b038216600090815260086020526040902054610556906116a3565b601290565b60006108da338461115e565b3360008181526009602090815260408083206001600160a01b038816845290915290205461090f9190859061071d90866116da565b50600192915050565b6109206116ff565b6004546001600160a01b0390811691161461094d5760405162461bcd60e51b81526004016105da906144d4565b61062b8282611703565b336001600160a01b037f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db0161461094d5760405162461bcd60e51b81526004016105da906143f0565b805160006109ad8484611815565b905060006109bf8585602001516118f6565b90506109cb8585611941565b60ff83166109e3576109de8186846119dd565b610a12565b60ff8316600114156109fa576109de818684611b55565b60405162461bcd60e51b81526004016105da906143ce565b7ffd1941e7a2d0c6f1cc53e8a73873275917cb8a1cf357e47f86f6e681e5d0f0f185856020015185604051610a499392919061402f565b60405180910390a15050505050565b60008251118015610a6a575080518251145b610a865760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610ac2838281518110610aa157fe5b6020026020010151838381518110610ab557fe5b6020026020010151610f61565b600101610a89565b505050565b6020820151610adc6130b8565b6001600160a01b038216600090815260086020526040902054610afe906116a3565b9050610b128460a001518260400151611cef565b610b2f8460200151610b248633611d27565b8660a0015186611dd2565b610b376130d8565b610b4683866080015184612066565b9050610b63856020015183876040015188606001516001866122e6565b5050505050565b6001600160a01b03166000908152600860205260409020546001600160601b031690565b610b966116ff565b6004546001600160a01b03908116911614610bc35760405162461bcd60e51b81526004016105da906144d4565b6004546040516000916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600480546001600160a01b0319169055565b60008251118015610c1f575080518251145b610c3b5760405162461bcd60e51b81526004016105da906141b7565b60005b8251811015610aca57610c77838281518110610c5657fe5b6020026020010151838381518110610c6a57fe5b6020026020010151610acf565b600101610c3e565b6004546001600160a01b031690565b60078054604080516020601f600260001961010060018816150201909516949094049384018190048102820181019092528281526060939092909183018282801561052d5780601f106105025761010080835404028352916020019161052d565b610cf76130b8565b506001600160a01b0316600090815260016020908152604091829020825160608101845290546001600160401b038082168352600160401b8204811693830193909352600160801b90049091169181019190915290565b6000610d5a338461115e565b604080518082018252600881526708aa48664605a62760c31b602080830191909152336000818152600983528481206001600160a01b03891682529092529290205461090f9291869161071d918790611644565b6000610dba338461115e565b610dc26113a4565b610e1c5733610dcf613091565b610dd882611202565b80519091508015610dea575080602001515b15610e1957610e0e600033338888604051806020016040528060008152508761152d565b600192505050610556565b50505b61090f33848461132c565b6000806000835111610e4b5760405162461bcd60e51b81526004016105da90614352565b6000610e5684612430565b905060ff8116610e9c57610e686130ef565b84806020019051810190610e7c9190613c17565b9050610e888187612474565b8160c0015160200151935093505050610eed565b60ff811660011415610ee457610eb061314c565b84806020019051810190610ec49190613b15565b9050610ed08187611d27565b8160a0015160200151935093505050610eed565b60008092509250505b9250929050565b610f008484848461250a565b610f138484600001518560200151612539565b50505050565b336001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f12161461094d5760405162461bcd60e51b81526004016105da906145c8565b60208201516040830151610f736130b8565b6001600160a01b038316600090815260086020526040902054610f95906116a3565b9050610f9f6130b8565b6001600160a01b038316600090815260086020526040902054610fc1906116a3565b9050610fd58660c001518360400151611cef565b610ff28660200151610fe78833612474565b8860c0015188611dd2565b610ffa6130d8565b611009858860a00151856125c3565b90506110278760200151848960400151858b6060015160018761274c565b50505050505050565b6001600160a01b03918216600090815260096020908152604080832093909416825291909152205490565b6110636116ff565b6004546001600160a01b039081169116146110905760405162461bcd60e51b81526004016105da906144d4565b6001600160a01b0381166110b65760405162461bcd60e51b81526004016105da90614254565b6004546040516001600160a01b038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3600480546001600160a01b0319166001600160a01b0392909216919091179055565b61111a613091565b61112333611202565b80519091508015611135575080602001515b156111515761114b60013333600087878761152d565b5061062b565b610aca338484600061287b565b6001600160a01b0382161580159061117e57506001600160a01b03811615155b61062b5760405162461bcd60e51b81526004016105da90614494565b6001600160a01b0380841660008181526009602090815260408083209487168084529490915290819020849055517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906111f59085906146d7565b60405180910390a3505050565b61120a613091565b604051630f425aed60e11b81526001600160a01b037f000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c61690631e84b5da90611256908590600401613edf565b60806040518083038186803b15801561126e57600080fd5b505afa158015611282573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105569190613d1a565b60006112b06113a4565b6112bc575060006105f4565b336001600160a01b037f000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e388416146112f4575060016105f4565b60018281015190818116141561130e5760019150506105f4565b8360200151801561131d575083515b156105ee5760009150506105f4565b6113346130b8565b6001600160a01b038416600090815260086020526040902054611356906116a3565b90506113606130b8565b6001600160a01b038416600090815260086020526040902054611382906116a3565b905061138c6130d8565b61139c868487858860008761274c565b505050505050565b6000336001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f121614156113df57506001610535565b6113e76128e3565b905090565b678ac7230489e800006001600160601b031681602001516001600160601b0316111561142a5760405162461bcd60e51b81526004016105da906145e8565b6001600160a01b0382161580159061144b57506001600160a01b0382163314155b6114675760405162461bcd60e51b81526004016105da906145a8565b805160ff16600214156115155761147c6130b8565b6001600160a01b03831660009081526008602052604090205461149e906116a3565b905081602001516001600160601b031681600001516001600160601b031610156114da5760405162461bcd60e51b81526004016105da90614234565b60208201518151036001600160601b031681526114f6816129cf565b6001600160a01b0384166000908152600860205260409020555061062b565b60405162461bcd60e51b81526004016105da906142f3565b6115356130d8565b61154082878a612a16565b905061154a613187565b611558828989898989612bb4565b905080600a600061156d8a8660200151611670565b8152602080820192909252604090810160002083518154858501516001600160601b03908116600160a01b9081026001600160a01b039485166001600160a01b03199485161785161785559487015160018501805460608a01519093169096029084169190921617909116179091556080830151805191926115f7926002850192909101906131b7565b505050602082015182516040517fa824f616acaccd5102c745c13e260dc1ca704e95425ebc56e73a6e495d5a3d8992611631928b9261402f565b60405180910390a1505050505050505050565b600081848411156116685760405162461bcd60e51b81526004016105da9190614182565b505050900390565b60008282604051602001611685929190613e64565b60405160208183030381529060405280519060200120905092915050565b6116ab6130b8565b6116b36130b8565b6001600160601b038381168252606084901c16602082015260c09290921c60408301525090565b6000828201838110156107345760405162461bcd60e51b81526004016105da906142bc565b3390565b6001600160a01b0382166117295760405162461bcd60e51b81526004016105da90614608565b80606081901c6001600160601b0381161561174c576001600160601b0381166005555b6117546130b8565b6001600160a01b038516600090815260086020526040902054611776906116a3565b80516020820151919250808501916001600160601b0391821690830190911610156117b35760405162461bcd60e51b81526004016105da90614509565b6001600160601b03811682526117c8826129cf565b6001600160a01b0387166000818152600860205260408082209390935591519091906000805160206148cd8339815191529061180590889061471b565b60405180910390a3505050505050565b600061181f6130b8565b611829848461073b565b6020810151909150611839613091565b61184286611202565b8051909150611855575091506105569050565b611863868660200151612c18565b60405163c2288c7160e01b81527385ae45a05971170b70744292e2f051c0c49cf9099063c2288c719061189c9086908590600401614684565b60206040518083038186803b1580156118b457600080fd5b505af41580156118c8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ec9190613db8565b9695505050505050565b600080600a60006119078686611670565b8152602081019190915260400160002080549091506001600160a01b03166107345760405162461bcd60e51b81526004016105da90614472565b6001600160a01b0382166000908152600260209081526040808320600190925291829020915163c112c77160e01b81529091907385ae45a05971170b70744292e2f051c0c49cf9099063c112c771906119a7908790879087908790600390600401613f0d565b60006040518083038186803b1580156119bf57600080fd5b505af41580156119d3573d6000803e3d6000fd5b5050505050505050565b600183015483546001600160a01b0390911690600160a01b90046001600160601b031630611a096130b8565b6001600160a01b038216600090815260086020526040902054611a2b906116a3565b9050611a356130b8565b6001600160a01b038516600090815260086020526040902054611a57906116a3565b9050836001600160601b031682600001516001600160601b03161015611a8f5760405162461bcd60e51b81526004016105da90614509565b81516001600160601b0390859003811683528151808601919081169082161015611acb5760405162461bcd60e51b81526004016105da90614509565b6001600160601b0381168252611ae0836129cf565b6001600160a01b038516600090815260086020526040902055611b02826129cf565b6001600160a01b0380881660008181526008602052604090819020939093559151908a16906000805160206148cd83398151915290611b4290899061471b565b60405180910390a3505050505050505050565b8254600160a01b90046001600160601b0316611b6f6130b8565b6001600160a01b038416600090815260086020526040902054611b91906116a3565b9050611b9b6130d8565b6000611bc48684868a60010160149054906101000a90046001600160601b031689876001612c63565b905030611bcf6130b8565b6001600160a01b038216600090815260086020526040902054611bf1906116a3565b9050826001600160601b031681600001516001600160601b03161015611c295760405162461bcd60e51b81526004016105da90614509565b80518390036001600160601b03168152611c42816129cf565b6001600160a01b038316600090815260086020526040902055611c64856129cf565b6001600160a01b0389166000908152600860205260409081902091909155517fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c190611cb590889060028d019061472f565b60405180910390a160006001600160a01b0316886001600160a01b03166000805160206148cd83398151915288604051611b42919061471b565b60018260400151036001600160401b0316816001600160401b03161461062b5760405162461bcd60e51b81526004016105da90614587565b60006107347fcc8d9dbb126d24ffe7913ca013b4ed4cae09e128eedc4622e4be8004699cf3c27f72079e1ca444dd2cbc0e28bb80962e9f4fbafb3705580b6b9f66186befba0395600186602001518760400151886060015180519060200120611d938a60800151612c6e565b611da18b60a001518b612cd6565b604051602001611db797969594939291906140fa565b60405160208183030381529060405280519060200120612d1d565b81516001600160a01b03163314611dfb5760405162461bcd60e51b81526004016105da906143b0565b6000807f000000000000000000000000a916bc21d2429645585abede4ae00742a16dd1c66001600160a01b0316630296287733886040518363ffffffff1660e01b8152600401611e4c929190613ef3565b604080518083038186803b158015611e6357600080fd5b505afa158015611e77573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e9b9190613ae8565b9150915081611ebc5760405162461bcd60e51b81526004016105da90614370565b6020840151429082906001600160401b03610e10840181169181169182111591600191851610611f0557826001600160401b0316886020015185036001600160401b0316111590505b818015611f0f5750805b611f2b5760405162461bcd60e51b81526004016105da9061454b565b876060015115611f4157611f3e89612d32565b98505b600060018a89604001518a600001518b6020015160405160008152602001604052604051611f729493929190614136565b6020604051602081039080840390855afa158015611f94573d6000803e3d6000fd5b5050506020604051035190508860600151158015611fc457508a6001600160a01b0316816001600160a01b031614155b15612028576001611fd48b612d32565b6040808b01518b516020808e015184516000815290910193849052611ff99493614136565b6020604051602081039080840390855afa15801561201b573d6000803e3d6000fd5b5050506020604051035190505b806001600160a01b03168b6001600160a01b0316146120595760405162461bcd60e51b81526004016105da906144b6565b5050505050505050505050565b61206e6130d8565b6120766130d8565b83516001600160601b03161561211e578351678ac7230489e800006001600160601b0390911611156120ba5760405162461bcd60e51b81526004016105da906145e8565b835183516001600160601b03918216911610156120e95760405162461bcd60e51b81526004016105da90614234565b83518351036001600160601b03908116845284511660208201528060035b9081600481111561211457fe5b9052509050610734565b60208401516001600160601b0316156121e957604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d9161219c918991600401613f53565b600060405180830381600087803b1580156121b657600080fd5b505af11580156121ca573d6000803e3d6000fd5b5050506020808601516001600160601b03169083015250806001612107565b60408401516001600160601b0316156122b35760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d91612266918991600401613f53565b600060405180830381600087803b15801561228057600080fd5b505af1158015612294573d6000803e3d6000fd5b50505060408501516001600160601b0316602083015250806002612107565b60608401516001600160601b0316156122de5760405162461bcd60e51b81526004016105da906142f3565b949350505050565b60006122f9878787600042876000612c63565b90508215612316576040860180516001016001600160401b031690525b6001600160601b038116156123635785516001600160601b03808316911610156123525760405162461bcd60e51b81526004016105da9061452a565b85518190036001600160601b031686525b61236c866129cf565b6001600160a01b03881660009081526008602052604081209190915582516001600160601b038716919060048111156123a157fe5b905060608160ff16901b82179150606384602001516001600160601b0316901b821791507fe7b1342ce7f88416536f0a97fd9274421e3718dd094f96e9cefec28f6d7002c182876040516123f69291906146e0565b60405180910390a160006001600160a01b0316896001600160a01b03166000805160206148cd83398151915285604051611b42919061471b565b60008082601f8151811061244057fe5b0160209081015160f81c91508110610556578281601f0160ff168151811061246457fe5b016020015160f81c905092915050565b60006107347fcc8d9dbb126d24ffe7913ca013b4ed4cae09e128eedc4622e4be8004699cf3c27f7d1602bfd7297f6720514f0c3dabe77313ce0d25cd5085644e3fcdc5635df66060008660200151876040015188606001518960800151805190602001206124e58b60a00151612c6e565b6124f38c60c001518c612cd6565b604051602001611db79897969594939291906140b5565b6125126130b8565b61251c858561073b565b905061252f858583602001518686612d45565b610b638585611941565b600061254584836118f6565b805460018201549192506001600160601b03600160a01b91829004811692919091041661257330878461132c565b6125808686868585610b63565b7f74c334759f9bfb4a4342d46ed6f4dfb2a3c6b7fe1c7c2288988e8b44bc281b8a8685876040516125b39392919061402f565b60405180910390a1505050505050565b6125cb6130d8565b6125d36130d8565b83516001600160601b031615612651578351678ac7230489e800006001600160601b0390911611156126175760405162461bcd60e51b81526004016105da906145e8565b80602001516001600160601b031683600001516001600160601b031610156120e95760405162461bcd60e51b81526004016105da90614234565b60208401516001600160601b0316156126cf57604080518082018252600081526020868101516001600160601b03169082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d9161219c918991600401613f53565b60408401516001600160601b0316156122de5760408051808201825260018152858201516001600160601b0316602082015290516310b4a23d60e01b81526001600160a01b037f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1216916310b4a23d91612266918991600401613f53565b846001600160a01b0316876001600160a01b0316141561277e5760405162461bcd60e51b81526004016105da9061429a565b8115612799576040860180516001016001600160401b031690525b855183906001600160601b03808316911610156127c85760405162461bcd60e51b81526004016105da90614195565b86516001600160601b03908290038116885285518083019190811690821610156128045760405162461bcd60e51b81526004016105da90614450565b6001600160601b0381168652612819886129cf565b6001600160a01b038a1660009081526008602052604090205561283b866129cf565b6001600160a01b0380891660008181526008602052604090819020939093559151908b16906000805160206148cd83398151915290611b4290869061471b565b6001600160a01b0384166128a15760405162461bcd60e51b81526004016105da90614410565b6128a96130b8565b6001600160a01b0385166000908152600860205260409020546128cb906116a3565b90506128d56130d8565b61139c8683878787866122e6565b6000336001600160a01b037f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db016141561291e57506001610535565b336001600160a01b037f0000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae916141561295757506001610535565b336001600160a01b037f000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a16141561299057506001610535565b336001600160a01b037f000000000000000000000000e7cd2797ac6f08b5721c7e7fcc991251df5e38841614156129c957506001610535565b50600090565b805160208201516040909201516001600160601b0390911660609290921b6bffffffffffffffffffffffff60601b169190911760c09190911b6001600160c01b0319161790565b612a1e6130d8565b6000612a2984612e20565b9050612a336130d8565b5060408051808201825260ff851681526001600160401b0383166020808301919091526001600160a01b038716600090815260029091529190912054601911612a8e5760405162461bcd60e51b81526004016105da90614334565b6040868101516001600160a01b0387811660009081526002602090815284822080546001810182559083528183208751910180548884015160ff1990911660ff9384161768ffffffffffffffff0019166101006001600160401b0392831602179091558651606081018852918b168252421691810191909152918316938201939093529091600390612b208987611670565b815260208082019290925260409081016000208351815493850151949092015160ff1990931660ff9092169190911768ffffffffffffffff0019166101006001600160401b0390941693909302929092177fffffff0000000000000000000000000000000000000000ffffffffffffffffff16600160481b6001600160a01b03909216919091021790555095945050505050565b612bbc613187565b612bc785308561132c565b612bcf613187565b50506040805160a0810182526001600160a01b0380881682526001600160601b038516602083015285169181019190915260006060820152608081018290529695505050505050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160401b9092041614610aca5760405162461bcd60e51b81526004016105da90614569565b509295945050505050565b60007ff1f7d09b7527273cfd8370620354f0cd9a62e7bd3efaafdeca15664a7b3e045c8260000151836020015184604001518560600151604051602001612cb9959493929190614154565b604051602081830303815290604052805190602001209050919050565b60007ff45b7e63030d0a046d85957a672f9a0806d76fff4cc32355d75f64eb6e83d00882846020015185604001518660600151604051602001611685959493929190614067565b60008282604051602001611685929190613ec4565b600081604051602001612cb99190613e93565b612d53858560200151612ebd565b612d5b613091565b612d6486611202565b80519091508015612d8a575080604001516001600160a01b0316336001600160a01b0316145b612da65760405162461bcd60e51b81526004016105da90614629565b6060612db0612f08565b60405163e17e413160e01b81529091507385ae45a05971170b70744292e2f051c0c49cf9099063e17e413190612df4908a90899087908a9088908b90600401613f85565b60006040518083038186803b158015612e0c57600080fd5b505af4158015612059573d6000803e3d6000fd5b6001600160a01b03811660009081526001602081905260408220805490916001600160401b0380831690910191600160401b900416612e825781546fffffffffffffffff00000000000000001916600160401b6001600160401b038316021782555b815467ffffffffffffffff60801b1916600160801b6001600160401b0383169081029190911767ffffffffffffffff19161790915592915050565b6001600160a01b038216600090815260016020526040902080546001600160401b03838116600160801b9092041614610aca5760405162461bcd60e51b81526004016105da90614431565b60408051600580825260c082019092526060918291906020820160a0803683370190505090503081600081518110612f3c57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000b628bc994e39ce264eca6f6ee1620909816a9f1281600181518110612f8a57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000ac0122e9258a85ba5479db764dc8ef91cab08db081600281518110612fd857fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f0000000000000000000000003b4da358199060bcc5a527ab60099fb6a908aae98160038151811061302657fe5b60200260200101906001600160a01b031690816001600160a01b0316815250507f000000000000000000000000025dbd03ed18b4b8425af51b4d05f5b00e78208a8160048151811061307457fe5b6001600160a01b0390921660209283029190910190910152905090565b60408051608081018252600080825260208201819052918101829052606081019190915290565b604080516060810182526000808252602082018190529181019190915290565b604080518082019091526000808252602082015290565b6040518060e00160405280600060ff16815260200160006001600160a01b0316815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b8152602001613147613091565b905290565b6040518060c00160405280600060ff16815260200160006001600160a01b03168152602001600081526020016060815260200161313a613091565b6040805160a081018252600080825260208201819052918101829052606080820192909252608081019190915290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106131f857805160ff1916838001178555613225565b82800160010185558215613225579182015b8281111561322557825182559160200191906001019061320a565b50613231929150613235565b5090565b5b808211156132315760008155600101613236565b80356105568161486d565b80516105568161486d565b600082601f830112613270578081fd5b813561328361327e826147f3565b6147cd565b81815291506020808301908481016060808502870183018810156132a657600080fd5b60005b858110156132cd576132bb8984613686565b855293830193918101916001016132a9565b50505050505092915050565b600082601f8301126132e9578081fd5b81356132f761327e82614812565b915080825283602082850101111561330e57600080fd5b8060208401602084013760009082016020015292915050565b600082601f830112613337578081fd5b815161334561327e82614812565b915080825283602082850101111561335c57600080fd5b61336d816020840160208601614841565b5092915050565b60006101808284031215613386578081fd5b61339060c06147cd565b9050813561339d816148a8565b815260208201356133ad8161486d565b60208201526040828101359082015260608201356001600160401b038111156133d557600080fd5b6133e1848285016132d9565b6060830152506133f483608084016134c0565b6080820152613407836101008401613587565b60a082015292915050565b60006101a08284031215613424578081fd5b61342e60e06147cd565b905061343a83836136ca565b8152613449836020840161324a565b602082015261345b836040840161324a565b60408201526060820135606082015260808201356001600160401b0381111561348357600080fd5b61348f848285016132d9565b6080830152506134a28360a084016134c0565b60a08201526134b5836101208401613587565b60c082015292915050565b6000608082840312156134d1578081fd5b6134db60806147cd565b905081356134e8816148b7565b815260208201356134f8816148b7565b6020820152604082013561350b816148b7565b6040820152606082013561351e816148b7565b606082015292915050565b60006080828403121561353a578081fd5b61354460806147cd565b90508151613551816148b7565b81526020820151613561816148b7565b60208201526040820151613574816148b7565b6040820152606082015161351e816148b7565b600060808284031215613598578081fd5b6135a260806147cd565b905081356135af8161486d565b815260208201356135bf81614893565b602082015260408201356135d281614893565b6040820152606082013561351e81614885565b6000608082840312156135f6578081fd5b61360060806147cd565b9050815161360d8161486d565b8152602082015161361d81614893565b6020820152604082015161363081614893565b6040820152606082015161351e81614885565b600060408284031215613654578081fd5b61365e60406147cd565b9050813561366b816148a8565b8152602082013561367b81614893565b602082015292915050565b600060608284031215613697578081fd5b6136a160606147cd565b9050813581526020820135602082015260408201356136bf816148a8565b604082015292915050565b8035610556816148a8565b8051610556816148a8565b6000602082840312156136f1578081fd5b81356107348161486d565b6000806040838503121561370e578081fd5b82356137198161486d565b915060208301356137298161486d565b809150509250929050565b600080600060608486031215613748578081fd5b83356137538161486d565b925060208401356137638161486d565b929592945050506040919091013590565b60008060008060006080868803121561378b578283fd5b85356137968161486d565b945060208601356137a68161486d565b93506040860135925060608601356001600160401b03808211156137c8578283fd5b818801915088601f8301126137db578283fd5b8135818111156137e9578384fd5b8960208285010111156137fa578384fd5b9699959850939650602001949392505050565b6000806040838503121561381f578182fd5b823561382a8161486d565b915060208301356001600160401b03811115613844578182fd5b613850858286016132d9565b9150509250929050565b6000806060838503121561386c578182fd5b82356138778161486d565b91506138868460208501613643565b90509250929050565b60008060008060e085870312156138a4578182fd5b84356138af8161486d565b93506138be8660208701613643565b925060608501356001600160401b038111156138d8578283fd5b6138e4878288016132d9565b9250506138f48660808701613686565b905092959194509250565b6000808284036060811215613912578283fd5b833561391d8161486d565b92506040601f1982011215613930578182fd5b5061393b60406147cd565b6020840135613949816148a8565b81526040840135613959816148b7565b6020820152919491935090915050565b6000806040838503121561397b578182fd5b82356139868161486d565b946020939093013593505050565b600080604083850312156139a6578182fd5b82356139b18161486d565b9150602083013561372981614893565b600080604083850312156139d3578182fd5b82356001600160401b03808211156139e9578384fd5b818501915085601f8301126139fc578384fd5b8135613a0a61327e826147f3565b81815260208082019190858101885b85811015613a4257613a308c8484358b0101613374565b85529382019390820190600101613a19565b50919750880135945050505080821115613a5a578283fd5b5061385085828601613260565b60008060408385031215613a79578182fd5b82356001600160401b0380821115613a8f578384fd5b818501915085601f830112613aa2578384fd5b8135613ab061327e826147f3565b81815260208082019190858101885b85811015613a4257613ad68c8484358b0101613412565b85529382019390820190600101613abf565b60008060408385031215613afa578182fd5b8251613b0581614885565b6020939093015192949293505050565b600060208284031215613b26578081fd5b81516001600160401b0380821115613b3c578283fd5b908301906101808286031215613b50578283fd5b613b5a60c06147cd565b8251613b65816148a8565b8152613b748660208501613255565b602082015260408301516040820152606083015182811115613b94578485fd5b613ba087828601613327565b606083015250613bb38660808501613529565b6080820152613bc68661010085016135e5565b60a082015295945050505050565b60008060808385031215613be6578182fd5b82356001600160401b03811115613bfb578283fd5b613c0785828601613374565b9250506138868460208501613686565b600060208284031215613c28578081fd5b81516001600160401b0380821115613c3e578283fd5b908301906101a08286031215613c52578283fd5b613c5c60e06147cd565b613c6686846136d5565b8152613c758660208501613255565b6020820152613c878660408501613255565b604082015260608301516060820152608083015182811115613ca7578485fd5b613cb387828601613327565b608083015250613cc68660a08501613529565b60a0820152613cd98661012085016135e5565b60c082015295945050505050565b60008060808385031215613cf9578182fd5b82356001600160401b03811115613d0e578283fd5b613c0785828601613412565b600060808284031215613d2b578081fd5b613d3560806147cd565b8251613d4081614885565b81526020830151613d5081614885565b60208201526040830151613d638161486d565b6040820152606083015163ffffffff81168114613d7e578283fd5b60608201529392505050565b60008060408385031215613d9c578182fd5b8235915060208301356001600160401b03811115613844578182fd5b600060208284031215613dc9578081fd5b815161073481614893565b60008151808452613dec816020860160208601614841565b601f01601f19169290920160200192915050565b805160ff1682526020808201516001600160401b0316908301526040908101516001600160a01b0316910152565b8051151582526020808201511515908301526040808201516001600160a01b03169083015260609081015163ffffffff16910152565b60609290921b6bffffffffffffffffffffffff1916825260c01b6001600160c01b0319166014820152601c0190565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000008152601c810191909152603c0190565b61190160f01b81526002810192909252602282015260420190565b6001600160a01b0391909116815260200190565b6001600160a01b0392831681529116602082015260400190565b6001600160a01b03959095168552835160ff16602080870191909152909301516001600160401b031660408501526060840191909152608083015260a082015260c00190565b6001600160a01b03929092168252805160ff1660208084019190915201516001600160601b0316604082015260600190565b600061016060018060a01b03808a16845260206001600160401b038a1681860152613fb3604086018a613e2e565b8260c0860152613fc583860189613dd4565b85810360e08701528751808252828901945090820190855b81811015613ffb578551851683529483019491830191600101613fdd565b50508094505085516101008601528086015161012086015250505060ff604084015116610140830152979650505050505050565b6001600160a01b039390931683526001600160401b0391909116602083015260ff16604082015260600190565b901515815260200190565b9485526001600160a01b039390931660208501526001600160401b0391821660408501521660608301521515608082015260a00190565b9182526001600160401b0316602082015260400190565b97885260ff9690961660208801526001600160a01b039485166040880152929093166060860152608085015260a084019190915260c083015260e08201526101000190565b96875260ff9590951660208701526001600160a01b039390931660408601526060850191909152608084015260a083015260c082015260e00190565b93845260ff9290921660208401526040830152606082015260800190565b9485526001600160601b03938416602086015291831660408501528216606084015216608082015260a00190565b6000602082526107346020830184613dd4565b602080825260089082015267045524332302d31360c41b604082015260600190565b60208082526007908201526622a9219918169b60c91b604082015260600190565b60208082526004908201526350422d3160e01b604082015260600190565b602080825260069082015265445542492d3160d01b604082015260600190565b6020808252600490820152632821169960e11b604082015260600190565b602080825260069082015265445542492d3760d01b604082015260600190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526008908201526745524332302d313960c01b604082015260600190565b6020808252601b908201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604082015260600190565b602080825260069082015265088aa84925a760d31b604082015260600190565b60208082526007908201526645524332302d3760c81b604082015260600190565b60208082526004908201526350422d3360e01b604082015260600190565b60208082526004908201526350422d3760e01b604082015260600190565b60208082526004908201526341422d3360e01b604082015260600190565b60208082526008908201526745524332302d313760c01b604082015260600190565b60208082526004908201526320a1169960e11b604082015260600190565b60208082526008908201526745524332302d313560c01b604082015260600190565b602080825260069082015265222aa124969960d11b604082015260600190565b60208082526007908201526608aa48664605a760cb1b604082015260600190565b602080825260059082015264050422d31360dc1b604082015260600190565b60208082526008908201526722a921991816989960c11b604082015260600190565b60208082526008908201526722a921991816989b60c11b604082015260600190565b60208082526008908201526745524332302d313360c01b604082015260600190565b60208082526004908201526341422d3560e01b604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526007908201526622a9219918169960c91b604082015260600190565b60208082526007908201526645524332302d3960c81b604082015260600190565b60208082526004908201526310508b4d60e21b604082015260600190565b60208082526004908201526350422d3960e01b604082015260600190565b60208082526007908201526645524332302d3560c81b604082015260600190565b602080825260069082015265222aa124969b60d11b604082015260600190565b602080825260069082015265445542492d3360d01b604082015260600190565b602080825260069082015265445542492d3560d01b604082015260600190565b60208082526007908201526645524332302d3160c81b604082015260600190565b6020808252600490820152632821169b60e11b604082015260600190565b81516001600160401b039081168252602080840151821690830152604092830151169181019190915260600190565b606081016105568284613e00565b60e081016146928285613e00565b6107346060830184613e2e565b81516001600160601b039081168252602080840151909116908201526040918201516001600160401b03169181019190915260600190565b90815260200190565b6000838252604060208301526122de6040830184613dd4565b6001600160401b0391909116815260200190565b60ff91909116815260200190565b6001600160601b0391909116815260200190565b6000604082016001600160601b038516835260206040818501528285546001808216600081146147665760018114614784576147bf565b60028304607f16865260ff19831660608901526080880193506147bf565b6002830461479281886146d7565b61479b8b614835565b895b838110156147b65781548382015290850190880161479d565b91909101955050505b509198975050505050505050565b6040518181016001600160401b03811182821017156147eb57600080fd5b604052919050565b60006001600160401b03821115614808578081fd5b5060209081020190565b60006001600160401b03821115614827578081fd5b50601f01601f191660200190565b60009081526020902090565b60005b8381101561485c578181015183820152602001614844565b83811115610f135750506000910152565b6001600160a01b038116811461488257600080fd5b50565b801515811461488257600080fd5b6001600160401b038116811461488257600080fd5b60ff8116811461488257600080fd5b6001600160601b038116811461488257600080fdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fead6c60ce95b63b6730927ed7e3e8d7e2d20fc92757ccec5ef000b845349d3e64736f6c634300060c0033

Verified Source Code Partial Match

Compiler: v0.6.12+commit.27d51765 EVM: istanbul Optimization: Yes (200 runs)
Dubi.sol 228 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ERC20.sol";
import "./Purpose.sol";

contract Dubi is ERC20 {
    Purpose private immutable _prps;

    constructor(
        uint256 initialSupply,
        address optIn,
        address purpose,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    )
        public
        ERC20(
            "Decentralized Universal Basic Income",
            "DUBI",
            optIn,
            hodl,
            externalAddress1,
            externalAddress2,
            externalAddress3
        )
    {
        _mintInitialSupply(msg.sender, initialSupply);

        _prps = Purpose(purpose);
    }

    function hodlMint(address to, uint256 amount) public {
        require(msg.sender == _hodlAddress, "DUBI-2");
        _mint(to, amount);
    }

    function purposeMint(address to, uint256 amount) public {
        require(msg.sender == address(_prps), "DUBI-3");
        _mint(to, amount);
    }

    function _callerIsDeployTimeKnownContract()
        internal
        override
        view
        returns (bool)
    {
        if (msg.sender == address(_prps)) {
            return true;
        }

        return super._callerIsDeployTimeKnownContract();
    }

    //---------------------------------------------------------------
    // Fuel
    //---------------------------------------------------------------

    /**
     * @dev Burns `fuel` from `from`. Can only be called by one of the deploy-time known contracts.
     */
    function burnFuel(address from, TokenFuel memory fuel) public override {
        require(_callerIsDeployTimeKnownContract(), "DUBI-1");
        _burnFuel(from, fuel);
    }

    function _burnFuel(address from, TokenFuel memory fuel) private {
        require(fuel.amount <= MAX_BOOSTER_FUEL, "DUBI-5");
        require(from != address(0) && from != msg.sender, "DUBI-6");

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_DUBI) {
            // Burn fuel from DUBI
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.balance >= fuel.amount, "DUBI-7");
            unpacked.balance -= fuel.amount;
            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        revert("DUBI-8");
    }

    /**
     *@dev Burn the fuel of a `boostedSend`
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.dubi > 0) {
            require(fuel.dubi <= MAX_BOOSTER_FUEL, "DUBI-5");

            // From uses his own DUBI to fuel the boost
            require(unpacked.balance >= fuelBurn.amount, "DUBI-7");
            unpacked.balance -= fuel.dubi;

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;

            return fuelBurn;
        }

        // If the fuel is PRPS, then we have to reach out to the PRPS contract.
        if (fuel.unlockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_UNLOCKED_PRPS,
                    amount: fuel.unlockedPrps
                })
            );

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_LOCKED_PRPS,
                    amount: fuel.lockedPrps
                })
            );

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // No fuel at all
        return fuelBurn;
    }

    /**
     *@dev Burn the fuel of a `boostedBurn`
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        // If the fuel is DUBI, then we can remove it directly
        if (fuel.dubi > 0) {
            require(fuel.dubi <= MAX_BOOSTER_FUEL, "DUBI-5");

            require(unpacked.balance >= fuel.dubi, "DUBI-7");
            unpacked.balance -= fuel.dubi;

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;

            return fuelBurn;
        }

        // If the fuel is PRPS, then we have to reach out to the PRPS contract.
        if (fuel.unlockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_UNLOCKED_PRPS,
                    amount: fuel.unlockedPrps
                })
            );

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;

            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            // Reverts if the requested amount cannot be burned
            _prps.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_LOCKED_PRPS,
                    amount: fuel.lockedPrps
                })
            );

            // No direct fuel, but we still return a indirect fuel so that it can be added
            // to the burn event.
            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // DUBI has no intrinsic fuel
        if (fuel.intrinsicFuel > 0) {
            revert("DUBI-8");
        }

        // No fuel at all
        return fuelBurn;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------
    function _getHasherContracts()
        internal
        override
        returns (address[] memory)
    {
        address[] memory hashers = new address[](5);
        hashers[0] = address(this);
        hashers[1] = address(_prps);
        hashers[2] = _hodlAddress;
        hashers[3] = _externalAddress1;
        hashers[4] = _externalAddress2;

        return hashers;
    }
}
ERC20.sol 1212 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/introspection/IERC1820Registry.sol";
import "./IBoostableERC20.sol";
import "./BoostableERC20.sol";

/**
 * @dev This is a heavily modified fork of @openzeppelin/contracts/token/ERC20/ERC20.sol (3.1.0)
 */
abstract contract ERC20 is IERC20, IBoostableERC20, BoostableERC20, Ownable {
    using SafeMath for uint256;

    // NOTE: In contrary to the Transfer event, the Burned event always
    // emits the amount including the burned fuel if any.
    // The amount is stored in the lower 96 bits of `amountAndFuel`,
    // followed by 3 bits to encode the type of fuel used and finally
    // another 96 bits for the fuel amount.
    //
    // 0         96        99                 195             256
    //   amount    fuelType      fuelAmount         padding
    //
    event Burned(uint256 amountAndFuel, bytes data);

    enum FuelType {NONE, UNLOCKED_PRPS, LOCKED_PRPS, DUBI, AUTO_MINTED_DUBI}

    struct FuelBurn {
        FuelType fuelType;
        uint96 amount;
    }

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    address internal immutable _hodlAddress;

    address internal immutable _externalAddress1;
    address internal immutable _externalAddress2;
    address internal immutable _externalAddress3;

    IERC1820Registry internal constant _ERC1820_REGISTRY = IERC1820Registry(
        0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24
    );

    // Mapping of address to packed data.
    // For efficiency reasons the token balance is a packed uint96 alongside
    // other data. The packed data has the following layout:
    //
    //   MSB                      uint256                      LSB
    //      uint64 nonce | uint96 hodlBalance | uint96 balance
    //
    // balance: the balance of a token holder that can be transferred freely
    // hodlBalance: the balance of a token holder that is hodled
    // nonce: a sequential number used for booster replay protection
    //
    // Only PRPS utilizes `hodlBalance`. For DUBI it is always 0.
    //
    mapping(address => uint256) internal _packedData;

    struct UnpackedData {
        uint96 balance;
        uint96 hodlBalance;
        uint64 nonce;
    }

    function _unpackPackedData(uint256 packedData)
        internal
        pure
        returns (UnpackedData memory)
    {
        UnpackedData memory unpacked;

        // 1) Read balance from the first 96 bits
        unpacked.balance = uint96(packedData);

        // 2) Read hodlBalance from the next 96 bits
        unpacked.hodlBalance = uint96(packedData >> 96);

        // 3) Read nonce from the next 64 bits
        unpacked.nonce = uint64(packedData >> (96 + 96));

        return unpacked;
    }

    function _packUnpackedData(UnpackedData memory unpacked)
        internal
        pure
        returns (uint256)
    {
        uint256 packedData;

        // 1) Write balance to the first 96 bits
        packedData |= unpacked.balance;

        // 2) Write hodlBalance to the the next 96 bits
        packedData |= uint256(unpacked.hodlBalance) << 96;

        // 3) Write nonce to the next 64 bits
        packedData |= uint256(unpacked.nonce) << (96 + 96);

        return packedData;
    }

    // ERC20-allowances
    mapping(address => mapping(address => uint256)) private _allowances;

    //---------------------------------------------------------------
    // Pending state for non-boosted operations while opted-in
    //---------------------------------------------------------------
    uint8 internal constant OP_TYPE_SEND = BOOST_TAG_SEND;
    uint8 internal constant OP_TYPE_BURN = BOOST_TAG_BURN;

    struct PendingTransfer {
        // NOTE: For efficiency reasons balances are stored in a uint96 which is sufficient
        // since we only use 18 decimals.
        //
        // Two amounts are associated with a pending transfer, to allow deriving contracts
        // to store extra information.
        //
        // E.g. PRPS makes use of this by encoding the pending locked PRPS in the
        // `occupiedAmount` field.
        //
        address spender;
        uint96 transferAmount;
        address to;
        uint96 occupiedAmount;
        bytes data;
    }

    // A mapping of hash(user, opId) to pending transfers. Pending burns are also considered regular transfers.
    mapping(bytes32 => PendingTransfer) private _pendingTransfers;

    //---------------------------------------------------------------

    constructor(
        string memory name,
        string memory symbol,
        address optIn,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    ) public Ownable() BoostableERC20(optIn) {
        _name = name;
        _symbol = symbol;

        _hodlAddress = hodl;
        _externalAddress1 = externalAddress1;
        _externalAddress2 = externalAddress2;
        _externalAddress3 = externalAddress3;

        // register interfaces
        _ERC1820_REGISTRY.setInterfaceImplementer(
            address(this),
            keccak256("BoostableERC20Token"),
            address(this)
        );
        _ERC1820_REGISTRY.setInterfaceImplementer(
            address(this),
            keccak256("ERC20Token"),
            address(this)
        );
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals.
     */
    function decimals() public pure returns (uint8) {
        return 18;
    }

    /**
     * @dev Returns the current nonce of `account`
     */
    function getNonce(address account) external override view returns (uint64) {
        UnpackedData memory unpacked = _unpackPackedData(_packedData[account]);
        return unpacked.nonce;
    }

    /**
     * @dev Returns the total supply
     */
    function totalSupply()
        external
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        return _totalSupply;
    }

    /**
     * @dev Returns the amount of tokens owned by an account (`tokenHolder`).
     */
    function balanceOf(address tokenHolder)
        public
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        // Return the balance of the holder that is not hodled (i.e. first 96 bits of the packeData)
        return uint96(_packedData[tokenHolder]);
    }

    /**
     * @dev Returns the unpacked data struct of `tokenHolder`
     */
    function unpackedDataOf(address tokenHolder)
        public
        view
        returns (UnpackedData memory)
    {
        return _unpackPackedData(_packedData[tokenHolder]);
    }

    /**
     * @dev Mints `amount` new tokens for `to`.
     *
     * To make things more efficient, the total supply is optionally packed into the passed
     * amount where the first 96 bits are used for the actual amount and the following 96 bits
     * for the total supply.
     *
     */
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _mintInitialSupply(address to, uint256 amount) internal {
        // _mint does not update the totalSupply by default, unless the second 96 bits
        // passed are non-zero - in which case the non-zero value becomes the new total supply.
        // So in order to get the correct initial supply, we have to mirror the lower 96 bits
        // to the following 96 bits.
        amount = amount | (amount << 96);
        _mint(to, amount);
    }

    function _mint(address to, uint256 amount) internal {
        require(to != address(0), "ERC20-1");

        // The actual amount to mint (=lower 96 bits)
        uint96 amountToMint = uint96(amount);

        // The new total supply, which may be 0 in which case no update is performed.
        uint96 updatedTotalSupply = uint96(amount >> 96);

        // Update state variables
        if (updatedTotalSupply > 0) {
            _totalSupply = updatedTotalSupply;
        }

        // Update packed data and check for uint96 overflow
        UnpackedData memory unpacked = _unpackPackedData(_packedData[to]);
        uint96 updatedBalance = unpacked.balance + amountToMint;

        // The overflow check also takes the hodlBalance into account
        require(
            updatedBalance + unpacked.hodlBalance >= unpacked.balance,
            "ERC20-2"
        );

        unpacked.balance = updatedBalance;
        _packedData[to] = _packUnpackedData(unpacked);

        emit Transfer(address(0), to, amountToMint);
    }

    /**
     * @dev Transfer `amount` from msg.sender to `recipient`
     */
    function transfer(address recipient, uint256 amount)
        public
        override(IBoostableERC20, IERC20)
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, recipient);

        // Never create a pending transfer if msg.sender is a deploy-time known contract
        if (!_callerIsDeployTimeKnownContract()) {
            // Create pending transfer if sender is opted-in and the permaboost is active
            address from = msg.sender;
            IOptIn.OptInStatus memory optInStatus = getOptInStatus(from);
            if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
                _createPendingTransfer({
                    opType: OP_TYPE_SEND,
                    spender: msg.sender,
                    from: msg.sender,
                    to: recipient,
                    amount: amount,
                    data: "",
                    optInStatus: optInStatus
                });

                return true;
            }
        }

        _move({from: msg.sender, to: recipient, amount: amount});

        return true;
    }

    /**
     * @dev Burns `amount` of msg.sender.
     *
     * Also emits a {IERC20-Transfer} event for ERC20 compatibility.
     */
    function burn(uint256 amount, bytes memory data) public {
        // Create pending burn if sender is opted-in and the permaboost is active
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(msg.sender);
        if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
            _createPendingTransfer({
                opType: OP_TYPE_BURN,
                spender: msg.sender,
                from: msg.sender,
                to: address(0),
                amount: amount,
                data: data,
                optInStatus: optInStatus
            });

            return;
        }

        _burn({
            from: msg.sender,
            amount: amount,
            data: data,
            incrementNonce: false
        });
    }

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient`.
     *
     * Can only be used by deploy-time known contracts.
     *
     * IBoostableERC20 extension
     */
    function boostedTransferFrom(
        address sender,
        address recipient,
        uint256 amount,
        bytes calldata data
    ) public override returns (bool) {
        _assertSenderRecipient(sender, recipient);

        IOptIn.OptInStatus memory optInStatus = getOptInStatus(sender);

        // Only transfer if `sender` is a deploy-time known contract, otherwise
        // revert.
        require(
            _isDeployTimeKnownContractAndCanTransfer(
                sender,
                recipient,
                amount,
                optInStatus,
                data
            ),
            "ERC20-17"
        );

        _move({from: sender, to: recipient, amount: amount});
        return true;
    }

    function _isDeployTimeKnownContractAndCanTransfer(
        address sender,
        address recipient,
        uint256 amount,
        IOptIn.OptInStatus memory optInStatus,
        bytes memory data
    ) private view returns (bool) {
        // If the caller not a deploy-time known contract, the transfer is not allowed
        if (!_callerIsDeployTimeKnownContract()) {
            return false;
        }

        if (msg.sender != _externalAddress3) {
            return true;
        }

        // _externalAddress3 passes a flag via `data` that indicates whether it is a boosted transaction
        // or not.
        uint8 isBoostedBits;
        assembly {
            // Load flag using a 1-byte offset, because `mload` always reads
            // 32-bytes at once and the first 32 bytes of `data` contain it's length.
            isBoostedBits := mload(add(data, 0x01))
        }

        // Reading into a 'bool' directly doesn't work for some reason
        if (isBoostedBits & 1 == 1) {
            return true;
        }

        //  If the latter, then _externalAddress3 can only transfer the funds if either:
        // - the permaboost is not active
        // - `sender` is not opted-in to begin with
        //
        // If `sender` is opted-in and the permaboost is active, _externalAddress3 cannot
        // take funds, except when boosted. Here the booster trusts _externalAddress3, since it already
        // verifies that `sender` provided a valid signature.
        //
        // This is special to _externalAddress3, other deploy-time known contracts do not make use of `data`.
        if (optInStatus.permaBoostActive && optInStatus.isOptedIn) {
            return false;
        }

        return true;
    }

    /**
     * @dev Verify the booster payload against the nonce that is stored in the packed data of an account.
     * The increment happens outside of this function, when the balance is updated.
     */
    function _verifyNonce(BoosterPayload memory payload, uint64 currentNonce)
        internal
        pure
    {
        require(currentNonce == payload.nonce - 1, "ERC20-5");
    }

    //---------------------------------------------------------------
    // Boosted functions
    //---------------------------------------------------------------

    /**
     * @dev Perform multiple `boostedSend` calls in a single transaction.
     *
     * NOTE: Booster extension
     */
    function boostedSendBatch(
        BoostedSend[] memory sends,
        Signature[] memory signatures
    ) external {
        require(
            sends.length > 0 && sends.length == signatures.length,
            "ERC20-6"
        );

        for (uint256 i = 0; i < sends.length; i++) {
            boostedSend(sends[i], signatures[i]);
        }
    }

    /**
     * @dev Perform multiple `boostedBurn` calls in a single transaction.
     *
     * NOTE: Booster extension
     */
    function boostedBurnBatch(
        BoostedBurn[] memory burns,
        Signature[] memory signatures
    ) external {
        require(
            burns.length > 0 && burns.length == signatures.length,
            "ERC20-6"
        );

        for (uint256 i = 0; i < burns.length; i++) {
            boostedBurn(burns[i], signatures[i]);
        }
    }

    /**
     * @dev Send `amount` tokens from `sender` to recipient`.
     * The `sender` must be opted-in and the `msg.sender` must be a trusted booster.
     *
     * NOTE: Booster extension
     */
    function boostedSend(BoostedSend memory send, Signature memory signature)
        public
    {
        address from = send.sender;
        address to = send.recipient;

        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // We verify the nonce separately, since it's stored next to the balance
        _verifyNonce(send.boosterPayload, unpackedFrom.nonce);

        _verifyBoostWithoutNonce(
            send.sender,
            hashBoostedSend(send, msg.sender),
            send.boosterPayload,
            signature
        );

        FuelBurn memory fuelBurn = _burnBoostedSendFuel(
            from,
            send.fuel,
            unpackedFrom
        );

        _moveUnpacked({
            from: send.sender,
            unpackedFrom: unpackedFrom,
            to: send.recipient,
            unpackedTo: unpackedTo,
            amount: send.amount,
            fuelBurn: fuelBurn,
            incrementNonce: true
        });
    }

    /**
     * @dev Burn the fuel of a `boostedSend`. Returns a `FuelBurn` struct containing information about the burn.
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal virtual returns (FuelBurn memory);

    /**
     * @dev Burn `amount` tokens from `account`.
     * The `account` must be opted-in and the `msg.sender` must be a trusted booster.
     *
     * NOTE: Booster extension
     */
    function boostedBurn(
        BoostedBurn memory message,
        // A signature, that is compared against the function payload and only accepted if signed by 'sender'
        Signature memory signature
    ) public {
        address from = message.account;
        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);

        // We verify the nonce separately, since it's stored next to the balance
        _verifyNonce(message.boosterPayload, unpacked.nonce);

        _verifyBoostWithoutNonce(
            message.account,
            hashBoostedBurn(message, msg.sender),
            message.boosterPayload,
            signature
        );

        FuelBurn memory fuelBurn = _burnBoostedBurnFuel(
            from,
            message.fuel,
            unpacked
        );

        _burnUnpacked({
            from: message.account,
            unpacked: unpacked,
            amount: message.amount,
            data: message.data,
            incrementNonce: true,
            fuelBurn: fuelBurn
        });
    }

    /**
     * @dev Burn the fuel of a `boostedSend`. Returns a `FuelBurn` struct containing information about the burn.
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal virtual returns (FuelBurn memory);

    function burnFuel(address from, TokenFuel memory fuel)
        external
        virtual
        override
    {}

    //---------------------------------------------------------------

    /**
     * @dev Get the allowance of `spender` for `holder`
     */
    function allowance(address holder, address spender)
        public
        override(IBoostableERC20, IERC20)
        view
        returns (uint256)
    {
        return _allowances[holder][spender];
    }

    /**
     * @dev Increase the allowance of `spender` by `value` for msg.sender
     */
    function approve(address spender, uint256 value)
        public
        override(IBoostableERC20, IERC20)
        returns (bool)
    {
        address holder = msg.sender;
        _assertSenderRecipient(holder, spender);
        _approve(holder, spender, value);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, spender);
        _approve(
            msg.sender,
            spender,
            _allowances[msg.sender][spender].add(addedValue)
        );
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        _assertSenderRecipient(msg.sender, spender);
        _approve(
            msg.sender,
            spender,
            _allowances[msg.sender][spender].sub(subtractedValue, "ERC20-18")
        );
        return true;
    }

    /**
     * @dev Transfer `amount` from `holder` to `recipient`.
     *
     * `msg.sender` requires an allowance >= `amount` of `holder`.
     */
    function transferFrom(
        address holder,
        address recipient,
        uint256 amount
    ) public override(IBoostableERC20, IERC20) returns (bool) {
        _assertSenderRecipient(holder, recipient);

        address spender = msg.sender;

        // Create pending transfer if the token holder is opted-in and the permaboost is active
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(holder);
        if (optInStatus.isOptedIn && optInStatus.permaBoostActive) {
            // Ignore allowances if holder is opted-in
            require(holder == spender, "ERC20-7");

            _createPendingTransfer({
                opType: OP_TYPE_SEND,
                spender: spender,
                from: holder,
                to: recipient,
                amount: amount,
                data: "",
                optInStatus: optInStatus
            });

            return true;
        }

        // Not opted-in, but we still need to check approval of the given spender

        _approve(
            holder,
            spender,
            _allowances[holder][spender].sub(amount, "ERC20-4")
        );

        _move({from: holder, to: recipient, amount: amount});

        return true;
    }

    /**
     * @dev Burn tokens
     * @param from address token holder address
     * @param amount uint256 amount of tokens to burn
     * @param data bytes extra information provided by the token holder
     * @param incrementNonce whether to increment the nonce or not - only true for boosted burns
     */
    function _burn(
        address from,
        uint256 amount,
        bytes memory data,
        bool incrementNonce
    ) internal virtual {
        require(from != address(0), "ERC20-8");

        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        _burnUnpacked({
            from: from,
            unpacked: unpacked,
            amount: amount,
            data: data,
            incrementNonce: incrementNonce,
            fuelBurn: fuelBurn
        });
    }

    function _burnUnpacked(
        address from,
        UnpackedData memory unpacked,
        uint256 amount,
        bytes memory data,
        bool incrementNonce,
        FuelBurn memory fuelBurn
    ) internal {
        // _beforeBurn allows deriving contracts to run additional logic and affect the amount
        // that is actually getting burned. E.g. when burning PRPS, a portion of it might be taken
        // from the `hodlBalance`. Thus the returned `burnAmount` overrides `amount` and will be
        // subtracted from the actual `balance`.

        uint96 actualBurnAmount = _beforeBurn({
            from: from,
            unpacked: unpacked,
            transferAmount: uint96(amount),
            occupiedAmount: 0,
            createdAt: uint32(block.timestamp),
            fuelBurn: fuelBurn,
            finalizing: false
        });

        // Update to new balance

        if (incrementNonce) {
            // The nonce uses 64 bits, so a overflow is pretty much impossible
            // via increments of 1.
            unpacked.nonce++;
        }

        if (actualBurnAmount > 0) {
            require(unpacked.balance >= actualBurnAmount, "ERC20-9");
            unpacked.balance -= actualBurnAmount;
        }

        // Update packed data by writing to storage
        _packedData[from] = _packUnpackedData(unpacked);

        // Total supply can be updated in batches elsewhere, shaving off another >5k gas.
        // _totalSupply = _totalSupply.sub(amount);

        // The `Burned` event is emitted with the total amount that got burned.
        // Furthermore, the fuel used is encoded in the upper bits.
        uint256 amountAndFuel;

        // Set first 96 bits to amount
        amountAndFuel |= uint96(amount);

        // Set next 3 bits to fuel type
        uint8 fuelType = uint8(fuelBurn.fuelType);
        amountAndFuel |= uint256(fuelType) << 96;

        // Set next 96 bits to fuel amount
        amountAndFuel |= uint256(fuelBurn.amount) << (96 + 3);

        emit Burned(amountAndFuel, data);

        // We emit a transfer event with the actual burn amount excluding burned `hodlBalance`.
        emit Transfer(from, address(0), actualBurnAmount);
    }

    /**
     * @dev Allow deriving contracts to prepare a burn. By default it behaves like an identity function
     * and just returns the amount passed in.
     */
    function _beforeBurn(
        address from,
        UnpackedData memory unpacked,
        uint96 transferAmount,
        uint96 occupiedAmount,
        uint32 createdAt,
        FuelBurn memory fuelBurn,
        bool finalizing
    ) internal virtual returns (uint96) {
        return transferAmount;
    }

    function _move(
        address from,
        address to,
        uint256 amount
    ) internal {
        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        _moveUnpacked({
            from: from,
            unpackedFrom: unpackedFrom,
            to: to,
            unpackedTo: unpackedTo,
            amount: amount,
            incrementNonce: false,
            fuelBurn: fuelBurn
        });
    }

    function _moveUnpacked(
        address from,
        UnpackedData memory unpackedFrom,
        address to,
        UnpackedData memory unpackedTo,
        uint256 amount,
        bool incrementNonce,
        FuelBurn memory fuelBurn
    ) internal {
        require(from != to, "ERC20-19");

        // Increment nonce of sender if it's a boosted send
        if (incrementNonce) {
            // The nonce uses 64 bits, so a overflow is pretty much impossible
            // via increments of 1.
            unpackedFrom.nonce++;
        }

        // Check if sender has enough tokens
        uint96 transferAmount = uint96(amount);
        require(unpackedFrom.balance >= transferAmount, "ERC20-10");

        // Subtract transfer amount from sender balance
        unpackedFrom.balance -= transferAmount;

        // Check that recipient balance doesn't overflow
        uint96 updatedRecipientBalance = unpackedTo.balance + transferAmount;
        require(updatedRecipientBalance >= unpackedTo.balance, "ERC20-12");
        unpackedTo.balance = updatedRecipientBalance;

        _packedData[from] = _packUnpackedData(unpackedFrom);
        _packedData[to] = _packUnpackedData(unpackedTo);

        // The transfer amount does not include any used fuel
        emit Transfer(from, to, transferAmount);
    }

    /**
     * @dev See {ERC20-_approve}.
     */
    function _approve(
        address holder,
        address spender,
        uint256 value
    ) internal {
        _allowances[holder][spender] = value;
        emit Approval(holder, spender, value);
    }

    function _assertSenderRecipient(address sender, address recipient)
        private
        pure
    {
        require(sender != address(0) && recipient != address(0), "ERC20-13");
    }

    /**
     * @dev Checks whether msg.sender is a deploy-time known contract or not.
     */
    function _callerIsDeployTimeKnownContract()
        internal
        virtual
        view
        returns (bool)
    {
        if (msg.sender == _hodlAddress) {
            return true;
        }

        if (msg.sender == _externalAddress1) {
            return true;
        }

        if (msg.sender == _externalAddress2) {
            return true;
        }

        if (msg.sender == _externalAddress3) {
            return true;
        }

        return false;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    /**
     * @dev Create a pending transfer
     */
    function _createPendingTransfer(
        uint8 opType,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data,
        IOptIn.OptInStatus memory optInStatus
    ) private {
        OpHandle memory opHandle = _createNewOpHandle(
            optInStatus,
            from,
            opType
        );

        PendingTransfer memory pendingTransfer = _createPendingTransferInternal(
            opHandle,
            spender,
            from,
            to,
            amount,
            data
        );

        _pendingTransfers[_getOpKey(from, opHandle.opId)] = pendingTransfer;

        // Emit PendingOp event
        emit PendingOp(from, opHandle.opId, opHandle.opType);
    }

    /**
     * @dev Create a pending transfer by moving the funds of `spender` to this contract.
     * Deriving contracts may override this function.
     */
    function _createPendingTransferInternal(
        OpHandle memory opHandle,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) internal virtual returns (PendingTransfer memory) {
        // Move funds into this contract

        // Reverts if `from` has less than `amount` tokens.
        _move({from: from, to: address(this), amount: amount});

        // Create op
        PendingTransfer memory pendingTransfer = PendingTransfer({
            transferAmount: uint96(amount),
            spender: spender,
            occupiedAmount: 0,
            to: to,
            data: data
        });

        return pendingTransfer;
    }

    /**
     * @dev Finalize a pending op
     */
    function finalizePendingOp(address user, OpHandle memory opHandle) public {
        uint8 opType = opHandle.opType;

        // Assert that the caller (msg.sender) is allowed to finalize the given op
        uint32 createdAt = uint32(_assertCanFinalize(user, opHandle));

        // Reverts if opId doesn't exist
        PendingTransfer storage pendingTransfer = _safeGetPendingTransfer(
            user,
            opHandle.opId
        );

        // Cleanup
        // NOTE: We do not delete the pending transfer struct, because it only makes it
        // more expensive since we already hit the gas refund limit.
        //
        // delete _pendingTransfers[_getOpKey(user, opHandle.opId)];
        //
        // The difference is ~13k gas.
        //
        // Deleting the op handle is enough to invalidate an opId forever:
        _deleteOpHandle(user, opHandle);

        // Call op type specific finalize
        if (opType == OP_TYPE_SEND) {
            _finalizeTransferOp(pendingTransfer, user, createdAt);
        } else if (opType == OP_TYPE_BURN) {
            _finalizePendingBurn(pendingTransfer, user, createdAt);
        } else {
            revert("ERC20-15");
        }

        // Emit event
        emit FinalizedOp(user, opHandle.opId, opType);
    }

    /**
     * @dev Finalize a pending transfer
     */
    function _finalizeTransferOp(
        PendingTransfer storage pendingTransfer,
        address from,
        uint32 createdAt
    ) private {
        address to = pendingTransfer.to;

        uint96 transferAmount = pendingTransfer.transferAmount;

        address _this = address(this);
        UnpackedData memory unpackedThis = _unpackPackedData(
            _packedData[_this]
        );
        UnpackedData memory unpackedTo = _unpackPackedData(_packedData[to]);

        // Check that sender balance does not overflow
        require(unpackedThis.balance >= transferAmount, "ERC20-2");
        unpackedThis.balance -= transferAmount;

        // Check that recipient doesn't overflow
        uint96 updatedBalanceRecipient = unpackedTo.balance + transferAmount;
        require(updatedBalanceRecipient >= unpackedTo.balance, "ERC20-2");

        unpackedTo.balance = updatedBalanceRecipient;

        _packedData[_this] = _packUnpackedData(unpackedThis);
        _packedData[to] = _packUnpackedData(unpackedTo);

        // Transfer event is emitted with original sender
        emit Transfer(from, to, transferAmount);
    }

    /**
     * @dev Finalize a pending burn
     */
    function _finalizePendingBurn(
        PendingTransfer storage pendingTransfer,
        address from,
        uint32 createdAt
    ) private {
        uint96 transferAmount = pendingTransfer.transferAmount;

        // We pass the packedData of `from` to `_beforeBurn`, because it PRPS needs to update
        // the `hodlBalance` which is NOT on the contract's own packedData.
        UnpackedData memory unpackedFrom = _unpackPackedData(_packedData[from]);

        // Empty fuel burn
        FuelBurn memory fuelBurn;

        uint96 burnAmountExcludingLockedPrps = _beforeBurn({
            from: from,
            unpacked: unpackedFrom,
            transferAmount: transferAmount,
            occupiedAmount: pendingTransfer.occupiedAmount,
            createdAt: createdAt,
            fuelBurn: fuelBurn,
            finalizing: true
        });

        // Update to new balance
        // NOTE: We change the balance of this contract, because that's where
        // the pending PRPS went to.
        address _this = address(this);
        UnpackedData memory unpackedOfContract = _unpackPackedData(
            _packedData[_this]
        );
        require(
            unpackedOfContract.balance >= burnAmountExcludingLockedPrps,
            "ERC20-2"
        );

        unpackedOfContract.balance -= burnAmountExcludingLockedPrps;
        _packedData[_this] = _packUnpackedData(unpackedOfContract);
        _packedData[from] = _packUnpackedData(unpackedFrom);

        // Furthermore, total supply can be updated elsewhere, shaving off another >5k gas.
        // _totalSupply = _totalSupply.sub(amount);

        // Emit events using the same `transferAmount` instead of what `_beforeBurn`
        // returned which is only used for updating the balance correctly.
        emit Burned(transferAmount, pendingTransfer.data);
        emit Transfer(from, address(0), transferAmount);
    }

    /**
     * @dev Revert a pending operation.
     *
     * Only the opted-in booster can revert a transaction if it provides a signed and still valid booster message
     * from the original sender.
     */
    function revertPendingOp(
        address user,
        OpHandle memory opHandle,
        bytes memory boosterMessage,
        Signature memory signature
    ) public {
        // Prepare revert, including permission check and prevents reentrancy for same opHandle.
        _prepareOpRevert({
            user: user,
            opHandle: opHandle,
            boosterMessage: boosterMessage,
            signature: signature
        });

        // Now perform the actual revert of the pending op
        _revertPendingOp(user, opHandle.opType, opHandle.opId);
    }

    /**
     * @dev Revert a pending transfer
     */
    function _revertPendingOp(
        address user,
        uint8 opType,
        uint64 opId
    ) private {
        PendingTransfer storage pendingTransfer = _safeGetPendingTransfer(
            user,
            opId
        );

        uint96 transferAmount = pendingTransfer.transferAmount;
        uint96 occupiedAmount = pendingTransfer.occupiedAmount;

        // Move funds from this contract back to the original sender. Transfers and burns
        // are reverted the same way. We only transfer back the `transferAmount` - that is the amount
        // that actually got moved into this contract. The occupied amount is released during `onRevertPendingOp`
        // by the deriving contract.
        _move({from: address(this), to: user, amount: transferAmount});

        // Call hook to allow deriving contracts to perform additional cleanup
        _onRevertPendingOp(user, opType, opId, transferAmount, occupiedAmount);

        // NOTE: we do not clean up the ops mapping, because we already hit the
        // gas refund limit.
        // delete _pendingTransfers[_getOpKey(user, opHandle.opId)];

        // Emit event
        emit RevertedOp(user, opId, opType);
    }

    /**
     * @dev Hook that is called during revert of a pending transfer.
     * Allows deriving contracts to perform additional cleanup.
     */
    function _onRevertPendingOp(
        address user,
        uint8 opType,
        uint64 opId,
        uint96 transferAmount,
        uint96 occupiedAmount
    ) internal virtual {}

    /**
     * @dev Safely get a pending transfer. Reverts if it doesn't exist.
     */
    function _safeGetPendingTransfer(address user, uint64 opId)
        private
        view
        returns (PendingTransfer storage)
    {
        PendingTransfer storage pendingTransfer = _pendingTransfers[_getOpKey(
            user,
            opId
        )];

        require(pendingTransfer.spender != address(0), "ERC20-16");

        return pendingTransfer;
    }
}
IHodl.sol 66 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

interface IHodl {
    /**
     * @dev Lock the given amount of PRPS for the specified period (or infinitely)
     * for DUBI.
     */
    function hodl(
        uint24 id,
        uint96 amountPrps,
        uint16 duration,
        address dubiBeneficiary,
        address prpsBeneficiary
    ) external;

    /**
     * @dev Release a hodl of `prpsBeneficiary` with the given `creator` and `id`.
     */
    function release(
        uint24 id,
        address prpsBeneficiary,
        address creator
    ) external;

    /**
     * @dev Withdraw can be used to withdraw DUBI from infinitely locked PRPS.
     * The amount of DUBI withdrawn depends on the time passed since the last withdrawal.
     */
    function withdraw(
        uint24 id,
        address prpsBeneficiary,
        address creator
    ) external;

    /**
     * @dev Burn `amount` of `from`'s locked and/or pending PRPS.
     *
     * This function is supposed to be only called by the PRPS contract.
     *
     * Returns the amount of DUBI that needs to be minted.
     */
    function burnLockedPrps(
        address from,
        uint96 amount,
        uint32 dubiMintTimestamp,
        bool burnPendingLockedPrps
    ) external returns (uint96);

    /**
     * @dev Set `amount` of `from`'s locked PRPS to pending.
     *
     * This function is supposed to be only called by the PRPS contract.
     *
     * Returns the amount of locked PRPS that could be set to pending.
     */
    function setLockedPrpsToPending(address from, uint96 amount) external;

    /**
     * @dev Revert `amount` of `from`'s pending locked PRPS to not pending.
     *
     * This function is supposed to be only called by the PRPS contract and returns
     */
    function revertLockedPrpsSetToPending(address account, uint96 amount)
        external;
}
IOptIn.sol 33 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

struct Signature {
    bytes32 r;
    bytes32 s;
    uint8 v;
}

interface IOptIn {
    struct OptInStatus {
        bool isOptedIn;
        bool permaBoostActive;
        address optedInTo;
        uint32 optOutPeriod;
    }

    function getOptInStatusPair(address accountA, address accountB)
        external
        view
        returns (OptInStatus memory, OptInStatus memory);

    function getOptInStatus(address account)
        external
        view
        returns (OptInStatus memory);

    function isOptedInBy(address _sender, address _account)
        external
        view
        returns (bool, uint256);
}
Purpose.sol 695 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ERC20.sol";
import "./Dubi.sol";
import "./IHodl.sol";
import "./MintMath.sol";

contract Purpose is ERC20 {
    // The DUBI contract, required for auto-minting DUBI on burn.
    Dubi private immutable _dubi;

    // The HODL contract, required for burning locked PRPS.
    IHodl private immutable _hodl;

    modifier onlyHodl() {
        require(msg.sender == _hodlAddress, "PRPS-1");
        _;
    }

    constructor(
        uint256 initialSupply,
        address optIn,
        address dubi,
        address hodl,
        address externalAddress1,
        address externalAddress2,
        address externalAddress3
    )
        public
        ERC20(
            "Purpose",
            "PRPS",
            optIn,
            hodl,
            externalAddress1,
            externalAddress2,
            externalAddress3
        )
    {
        _dubi = Dubi(dubi);
        _hodl = IHodl(hodl);

        _mintInitialSupply(msg.sender, initialSupply);
    }

    /**
     * @dev Returns the address of the {HODL} contract used for burning locked PRPS.
     */
    function hodl() external view returns (address) {
        return address(_hodl);
    }

    /**
     * @dev Returns the hodl balance of the given `tokenHolder`
     */
    function hodlBalanceOf(address tokenHolder) public view returns (uint256) {
        // The hodl balance follows after the first 96 bits in the packed data.
        return uint96(_packedData[tokenHolder] >> 96);
    }

    /**
     * @dev Transfer `amount` PRPS from `from` to the Hodl contract.
     *
     * This can only be called by the Hodl contract.
     */
    function hodlTransfer(address from, uint96 amount) external onlyHodl {
        _move(from, address(_hodl), amount);
    }

    /**
     * @dev Increase the hodl balance of `account` by `hodlAmount`. This is
     * only used as part of the migration.
     */
    function migrateHodlBalance(address account, uint96 hodlAmount)
        external
        onlyHodl
    {
        UnpackedData memory unpacked = _unpackPackedData(_packedData[account]);

        unpacked.hodlBalance += hodlAmount;
        _packedData[account] = _packUnpackedData(unpacked);
    }

    /**
     * @dev Increase the hodl balance of `to` by moving `amount` PRPS from `from`'s balance.
     *
     * This can only be called by the Hodl contract.
     */
    function increaseHodlBalance(
        address from,
        address to,
        uint96 amount
    ) external onlyHodl {
        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );
        UnpackedData memory unpackedDataTo;

        // We only need to unpack twice if from != to
        if (from != to) {
            unpackedDataTo = _unpackPackedData(_packedData[to]);
        } else {
            unpackedDataTo = unpackedDataFrom;
        }

        // `from` must have enough balance
        require(unpackedDataFrom.balance >= amount, "PRPS-3");

        // Subtract balance from `from`
        unpackedDataFrom.balance -= amount;
        // Add to `hodlBalance` from `to`
        unpackedDataTo.hodlBalance += amount;

        // We only need to pack twice if from != to
        if (from != to) {
            _packedData[to] = _packUnpackedData(unpackedDataTo);
        }

        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Decrease the hodl balance of `from` by `hodlAmount` and increase
     * the regular balance by `refundAmount.
     *
     * `refundAmount` might be less than `hodlAmount`.
     *
     * E.g. when burning fuel in locked PRPS
     *
     * This can only be called by the Hodl contract.
     */
    function decreaseHodlBalance(
        address from,
        uint96 hodlAmount,
        uint96 refundAmount
    ) external onlyHodl {
        require(hodlAmount >= refundAmount, "PRPS-4");

        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );

        // `from` must have enough balance
        require(unpackedDataFrom.hodlBalance >= hodlAmount, "PRPS-5");

        // Subtract amount from hodl balance
        unpackedDataFrom.hodlBalance -= hodlAmount;

        if (refundAmount > 0) {
            // Add amount to balance
            unpackedDataFrom.balance += refundAmount;
        }

        // Write to storage
        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Revert the hodl balance change caused by `from` on `to`.
     *
     * E.g. when reverting a pending hodl.
     *
     * This can only be called by the Hodl contract.
     */
    function revertHodlBalance(
        address from,
        address to,
        uint96 amount
    ) external onlyHodl {
        UnpackedData memory unpackedDataFrom = _unpackPackedData(
            _packedData[from]
        );
        UnpackedData memory unpackedDataTo;

        // We only need to unpack twice if from != to
        if (from != to) {
            unpackedDataTo = _unpackPackedData(_packedData[to]);
        } else {
            unpackedDataTo = unpackedDataFrom;
        }

        // `to` must have enough hodl balance
        require(unpackedDataTo.hodlBalance >= amount, "PRPS-5");

        // Subtract hodl balance from `to`
        unpackedDataTo.hodlBalance -= amount;
        // Add to `balance` from `from`
        unpackedDataFrom.balance += amount;

        // We only need to pack twice if from != to
        if (from != to) {
            _packedData[to] = _packUnpackedData(unpackedDataTo);
        }

        _packedData[from] = _packUnpackedData(unpackedDataFrom);
    }

    /**
     * @dev Mint DUBI when burning PRPS
     * @param from address token holder address
     * @param transferAmount amount of tokens to burn
     * @param occupiedAmount amount of tokens that are occupied
     * @param createdAt equal to block.timestamp if not finalizing a pending op, otherwise
     * it corresponds to op.createdAt
     * @param finalizing boolean indicating whether this is a finalizing transaction or not. Changes
     * how the `amount` is interpreted.
     *
     * When burning PRPS, we first try to burn unlocked PRPS.
     * If burning an amount that exceeds the unlocked PRPS of `from`, we attempt to burn the
     * difference from locked PRPS.
     *
     * If the desired `amount` cannot be filled by taking locked and unlocked PRPS into account,
     * this function reverts.
     *
     * Burning locked PRPS means reducing the `hodlBalance` while burning unlocked PRPS means reducing
     * the regular `balance`.
     *
     * This function returns the actual unlocked PRPS that needs to be removed from `balance`.
     *
     */
    function _beforeBurn(
        address from,
        UnpackedData memory unpacked,
        uint96 transferAmount,
        uint96 occupiedAmount,
        uint32 createdAt,
        FuelBurn memory fuelBurn,
        bool finalizing
    ) internal override returns (uint96) {
        uint96 totalDubiToMint;
        uint96 lockedPrpsToBurn;
        uint96 burnableUnlockedPrps;

        // Depending on whether this is a finalizing burn or not,
        // the amount of locked/unlocked PRPS is determined differently.
        if (finalizing) {
            // For a finalizing burn, we use the occupied amount, since we already know how much
            // locked PRPS we are going to burn. This amount represents the `pendingLockedPrps`
            // on the hodl items.
            lockedPrpsToBurn = occupiedAmount;

            // Since `transferAmount` is the total amount of PRPS getting burned, we need to subtract
            // the `occupiedAmount` to get the actual amount of unlocked PRPS.

            // Sanity check
            assert(transferAmount >= occupiedAmount);
            transferAmount -= occupiedAmount;

            // Set the unlocked PRPS to burn to the updated `transferAmount`
            burnableUnlockedPrps = transferAmount;
        } else {
            // For a direct burn, we start off with the full amounts, since we don't know the exact
            // amounts initially.

            lockedPrpsToBurn = transferAmount;
            burnableUnlockedPrps = unpacked.balance;
        }

        // 1) Try to burn unlocked PRPS
        if (burnableUnlockedPrps > 0) {
            // Nice, we can burn unlocked PRPS

            // Catch underflow i.e. don't burn more than we need to
            if (burnableUnlockedPrps > transferAmount) {
                burnableUnlockedPrps = transferAmount;
            }

            // Calculate DUBI to mint based on unlocked PRPS we can burn
            totalDubiToMint = MintMath.calculateDubiToMintMax(
                burnableUnlockedPrps
            );

            // Subtract the amount of burned unlocked PRPS from the locked PRPS we
            // need to burn if this is NOT a finalizing burn, because in that case we
            // already have the exact amount locked PRPS we want to burn.
            if (!finalizing) {
                lockedPrpsToBurn -= burnableUnlockedPrps;
            }
        }

        // 2) Burn locked PRPS if there's not enough unlocked PRPS

        // Burn an additional amount of locked PRPS equal to the fuel if any
        if (fuelBurn.fuelType == FuelType.LOCKED_PRPS) {
            // The `burnFromLockedPrps` call will fail, if not enough PRPS can be burned.
            lockedPrpsToBurn += fuelBurn.amount;
        }

        if (lockedPrpsToBurn > 0) {
            uint96 dubiToMintFromLockedPrps = _burnFromLockedPrps({
                from: from,
                unpacked: unpacked,
                lockedPrpsToBurn: lockedPrpsToBurn,
                createdAt: createdAt,
                finalizing: finalizing
            });

            // We check 'greater than or equal' because it's possible to mint 0 new DUBI
            // e.g. when called right after a hodl where not enough time passed to generate new DUBI.
            uint96 dubiToMint = totalDubiToMint + dubiToMintFromLockedPrps;
            require(dubiToMint >= totalDubiToMint, "PRPS-6");

            totalDubiToMint = dubiToMint;
        } else {
            // Sanity check for finalizes that don't touch locked PRPS
            assert(occupiedAmount == 0);
        }

        // Burn minted DUBI equal to the fuel if any
        if (fuelBurn.fuelType == FuelType.AUTO_MINTED_DUBI) {
            require(totalDubiToMint >= fuelBurn.amount, "PRPS-7");
            totalDubiToMint -= fuelBurn.amount;
        }

        // Mint DUBI taking differences between burned locked/unlocked into account
        if (totalDubiToMint > 0) {
            _dubi.purposeMint(from, totalDubiToMint);
        }

        return burnableUnlockedPrps;
    }

    function _burnFromLockedPrps(
        address from,
        UnpackedData memory unpacked,
        uint96 lockedPrpsToBurn,
        uint32 createdAt,
        bool finalizing
    ) private returns (uint96) {
        // Reverts if the exact amount needed cannot be burned
        uint96 dubiToMintFromLockedPrps = _hodl.burnLockedPrps({
            from: from,
            amount: lockedPrpsToBurn,
            dubiMintTimestamp: createdAt,
            burnPendingLockedPrps: finalizing
        });

        require(unpacked.hodlBalance >= lockedPrpsToBurn, "PRPS-8");

        unpacked.hodlBalance -= lockedPrpsToBurn;

        return dubiToMintFromLockedPrps;
    }

    function _callerIsDeployTimeKnownContract()
        internal
        override
        view
        returns (bool)
    {
        if (msg.sender == address(_dubi)) {
            return true;
        }

        return super._callerIsDeployTimeKnownContract();
    }

    //---------------------------------------------------------------
    // Fuel
    //---------------------------------------------------------------

    /**
     * @dev Burns `fuel` from `from`. Can only be called by one of the deploy-time known contracts.
     */
    function burnFuel(address from, TokenFuel memory fuel) public override {
        require(_callerIsDeployTimeKnownContract(), "PRPS-2");
        _burnFuel(from, fuel);
    }

    function _burnFuel(address from, TokenFuel memory fuel) private {
        require(fuel.amount <= MAX_BOOSTER_FUEL, "PRPS-10");
        require(from != address(0) && from != msg.sender, "PRPS-11");

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_UNLOCKED_PRPS) {
            // Burn fuel from unlocked PRPS
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.balance >= fuel.amount, "PRPS-7");
            unpacked.balance -= fuel.amount;
            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        if (fuel.tokenAlias == TOKEN_FUEL_ALIAS_LOCKED_PRPS) {
            // Burn fuel from locked PRPS
            UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
            require(unpacked.hodlBalance >= fuel.amount, "PRPS-7");
            unpacked.hodlBalance -= fuel.amount;

            // We pass a mint timestamp, but that doesn't mean that DUBI is minted.
            // The returned DUBI that should be minted is ignored.
            // Reverts if not enough locked PRPS can be burned.
            _hodl.burnLockedPrps({
                from: from,
                amount: fuel.amount,
                dubiMintTimestamp: uint32(block.timestamp),
                burnPendingLockedPrps: false
            });

            _packedData[from] = _packUnpackedData(unpacked);
            return;
        }

        revert("PRPS-12");
    }

    /**
     *@dev Burn the fuel of a `boostedSend`
     */
    function _burnBoostedSendFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.unlockedPrps > 0) {
            require(fuel.unlockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.balance >= fuel.unlockedPrps, "PRPS-7");
            unpacked.balance -= fuel.unlockedPrps;

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            require(fuel.lockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            // We pass a mint timestamp, but that doesn't mean that DUBI is minted.
            // The returned DUBI that should be minted is ignored.
            // Reverts if not enough locked PRPS can be burned.
            _hodl.burnLockedPrps({
                from: from,
                amount: fuel.lockedPrps,
                dubiMintTimestamp: uint32(block.timestamp),
                burnPendingLockedPrps: false
            });

            require(unpacked.hodlBalance >= fuel.lockedPrps, "PRPS-7");
            unpacked.hodlBalance -= fuel.lockedPrps;

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;
            return fuelBurn;
        }

        // If the fuel is DUBI, then we have to reach out to the DUBI contract.
        if (fuel.dubi > 0) {
            // Reverts if the requested amount cannot be burned
            _dubi.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_DUBI,
                    amount: fuel.dubi
                })
            );

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;
            return fuelBurn;
        }

        return fuelBurn;
    }

    /**
     *@dev Burn the fuel of a `boostedBurn`
     */
    function _burnBoostedBurnFuel(
        address from,
        BoosterFuel memory fuel,
        UnpackedData memory unpacked
    ) internal override returns (FuelBurn memory) {
        FuelBurn memory fuelBurn;

        if (fuel.unlockedPrps > 0) {
            require(fuel.unlockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.balance >= fuel.unlockedPrps, "PRPS-7");
            unpacked.balance -= fuel.unlockedPrps;

            fuelBurn.amount = fuel.unlockedPrps;
            fuelBurn.fuelType = FuelType.UNLOCKED_PRPS;
            return fuelBurn;
        }

        if (fuel.lockedPrps > 0) {
            require(fuel.lockedPrps <= MAX_BOOSTER_FUEL, "PRPS-10");

            require(unpacked.hodlBalance >= fuel.lockedPrps, "PRPS-7");
            // Fuel is taken from hodl balance in _beforeBurn
            // unpacked.hodlBalance -= fuel.lockedPrps;

            fuelBurn.amount = fuel.lockedPrps;
            fuelBurn.fuelType = FuelType.LOCKED_PRPS;

            return fuelBurn;
        }

        if (fuel.intrinsicFuel > 0) {
            require(fuel.intrinsicFuel <= MAX_BOOSTER_FUEL, "PRPS-10");

            fuelBurn.amount = fuel.intrinsicFuel;
            fuelBurn.fuelType = FuelType.AUTO_MINTED_DUBI;

            return fuelBurn;
        }

        // If the fuel is DUBI, then we have to reach out to the DUBI contract.
        if (fuel.dubi > 0) {
            // Reverts if the requested amount cannot be burned
            _dubi.burnFuel(
                from,
                TokenFuel({
                    tokenAlias: TOKEN_FUEL_ALIAS_DUBI,
                    amount: fuel.dubi
                })
            );

            fuelBurn.amount = fuel.dubi;
            fuelBurn.fuelType = FuelType.DUBI;
            return fuelBurn;
        }

        // No fuel at all
        return fuelBurn;
    }

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    function _getHasherContracts()
        internal
        override
        returns (address[] memory)
    {
        address[] memory hashers = new address[](5);
        hashers[0] = address(this);
        hashers[1] = address(_dubi);
        hashers[2] = _hodlAddress;
        hashers[3] = _externalAddress1;
        hashers[4] = _externalAddress2;

        return hashers;
    }

    /**
     * @dev Create a pending transfer by moving the funds of `spender` to this contract.
     * Special behavior applies to pending burns to account for locked PRPS.
     */
    function _createPendingTransferInternal(
        OpHandle memory opHandle,
        address spender,
        address from,
        address to,
        uint256 amount,
        bytes memory data
    ) internal override returns (PendingTransfer memory) {
        if (opHandle.opType != OP_TYPE_BURN) {
            return
                // Nothing special to do for non-burns so just call parent implementation
                super._createPendingTransferInternal(
                    opHandle,
                    spender,
                    from,
                    to,
                    amount,
                    data
                );
        }

        // When burning, we first use unlocked PRPS and match the remaining amount with locked PRPS from the Hodl contract.

        // Sanity check
        assert(amount < 2**96);
        uint96 transferAmount = uint96(amount);
        uint96 lockedPrpsAmount = transferAmount;

        UnpackedData memory unpacked = _unpackPackedData(_packedData[from]);
        // First try to move as much unlocked PRPS as possible to the PRPS address
        uint96 unlockedPrpsToMove = transferAmount;
        if (unlockedPrpsToMove > unpacked.balance) {
            unlockedPrpsToMove = unpacked.balance;
        }

        // Update the locked PRPS we have to use
        lockedPrpsAmount -= unlockedPrpsToMove;

        if (unlockedPrpsToMove > 0) {
            _move({from: from, to: address(this), amount: unlockedPrpsToMove});
        }

        // If we still need locked PRPS, call into the Hodl contract.
        // This will also take pending hodls into account, if `from` has
        // some.
        if (lockedPrpsAmount > 0) {
            // Reverts if not the exact amount can be set to pending
            _hodl.setLockedPrpsToPending(from, lockedPrpsAmount);
        }

        // Create pending transfer
        return
            PendingTransfer({
                spender: spender,
                transferAmount: transferAmount,
                to: to,
                occupiedAmount: lockedPrpsAmount,
                data: data
            });
    }

    /**
     * @dev Hook that is called during revert of a pending op.
     * Reverts any changes to locked PRPS when 'opType' is burn.
     */
    function _onRevertPendingOp(
        address user,
        uint8 opType,
        uint64 opId,
        uint96 transferAmount,
        uint96 occupiedAmount
    ) internal override {
        if (opType != OP_TYPE_BURN) {
            return;
        }

        // Extract the pending locked PRPS from the amount.
        if (occupiedAmount > 0) {
            _hodl.revertLockedPrpsSetToPending(user, occupiedAmount);
        }
    }

    //---------------------------------------------------------------
    // Shared pending ops for Hodl
    //---------------------------------------------------------------

    /**
     * @dev Creates a new opHandle with the given type for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function createNewOpHandleShared(
        IOptIn.OptInStatus memory optInStatus,
        address user,
        uint8 opType
    ) public onlyHodl returns (OpHandle memory) {
        return _createNewOpHandle(optInStatus, user, opType);
    }

    /**
     * @dev Delete the op handle with the given `opId` from `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function deleteOpHandleShared(address user, OpHandle memory opHandle)
        public
        onlyHodl
        returns (bool)
    {
        _deleteOpHandle(user, opHandle);
        return true;
    }

    /**
     * @dev Get the next op id for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function assertFinalizeFIFOShared(address user, uint64 opId)
        public
        onlyHodl
        returns (bool)
    {
        _assertFinalizeFIFO(user, opId);
        return true;
    }

    /**
     * @dev Get the next op id for `user`. Hodl and Prps share the same
     * opCounter to enforce a consistent order in which pending ops are finalized/reverted
     * across contracts. This function can only be called by Hodl.
     */
    function assertRevertLIFOShared(address user, uint64 opId)
        public
        onlyHodl
        returns (bool)
    {
        _assertRevertLIFO(user, opId);
        return true;
    }
}
MintMath.sol 121 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

// NOTE: we ignore leap-seconds etc.
library MintMath {
    // The maximum number of seconds per month (365 * 24 * 60 * 60 / 12)
    uint32 public constant SECONDS_PER_MONTH = 2628000;
    // The maximum number of days PRPS can be finitely locked for
    uint16 public constant MAX_FINITE_LOCK_DURATION_DAYS = 365;
    // The maximum number of seconds PRPS can be finitely locked for
    uint32 public constant MAX_FINITE_LOCK_DURATION_SECONDS = uint32(
        MAX_FINITE_LOCK_DURATION_DAYS
    ) *
        24 *
        60 *
        60;

    /**
     * @dev Calculates the DUBI to mint based on the given amount of PRPS and duration in days.
     * NOTE: We trust the caller to ensure that the duration between 1 and 365.
     */
    function calculateDubiToMintByDays(
        uint256 amountPrps,
        uint16 durationInDays
    ) internal pure returns (uint96) {
        uint32 durationInSeconds = uint32(durationInDays) * 24 * 60 * 60;
        return calculateDubiToMintBySeconds(amountPrps, durationInSeconds);
    }

    /**
     * @dev Calculates the DUBI to mint based on the given amount of PRPS and duration in seconds.
     */
    function calculateDubiToMintBySeconds(
        uint256 amountPrps,
        uint32 durationInSeconds
    ) internal pure returns (uint96) {
        // NOTE: We do not use safe math for efficiency reasons

        uint256 _percentage = percentage(
            durationInSeconds,
            MAX_FINITE_LOCK_DURATION_SECONDS,
            18 // precision in WEI, 10^18
        ) * 4; // A full lock grants 4%, so multiply by 4.

        // Multiply PRPS by the percentage and then divide by the precision (=10^8)
        // from the previous step
        uint256 _dubiToMint = (amountPrps * _percentage) / (1 ether * 100); // multiply by 100, because we deal with percentages

        // Assert that the calculated DUBI never overflows uint96
        assert(_dubiToMint < 2**96);

        return uint96(_dubiToMint);
    }

    function calculateDubiToMintMax(uint96 amount)
        internal
        pure
        returns (uint96)
    {
        return
            calculateDubiToMintBySeconds(
                amount,
                MAX_FINITE_LOCK_DURATION_SECONDS
            );
    }

    function calculateMintDuration(uint32 _now, uint32 lastWithdrawal)
        internal
        pure
        returns (uint32)
    {
        require(lastWithdrawal > 0 && lastWithdrawal <= _now, "MINT-1");

        // NOTE: we don't use any safe math here for efficiency reasons. The assert above
        // is already a pretty good guarantee that nothing goes wrong. Also, all numbers involved
        // are very well smaller than uint256 in the first place.
        uint256 _elapsedTotal = _now - lastWithdrawal;
        uint256 _proRatedYears = _elapsedTotal / SECONDS_PER_MONTH / 12;
        uint256 _elapsedInYear = _elapsedTotal %
            MAX_FINITE_LOCK_DURATION_SECONDS;

        //
        // Examples (using months instead of seconds):
        // calculation formula: (monthsSinceWithdrawal % 12) + (_proRatedYears * 12)

        // 1) Burn after 11 months since last withdrawal (number of years = 11 / 12 + 1 = 1)
        // => (11 % 12) + (years * 12) => 23 months worth of DUBI
        // => 23 months

        // 1) Burn after 4 months since last withdrawal (number of years = 4 / 12 + 1 = 1)
        // => (4 % 12) + (years * 12) => 16 months worth of DUBI
        // => 16 months

        // 2) Burn 0 months after withdrawal after 4 months (number of years = 0 / 12 + 1 = 1):
        // => (0 % 12) + (years * 12) => 12 months worth of DUBI (+ 4 months worth of withdrawn DUBI)
        // => 16 months

        // 3) Burn after 36 months since last withdrawal (number of years = 36 / 12 + 1 = 4)
        // => (36 % 12) + (years * 12) => 48 months worth of DUBI
        // => 48 months

        // 4) Burn 1 month after withdrawal after 35 months (number of years = 1 / 12 + 1 = 1):
        // => (1 % 12) + (years * 12) => 12 month worth of DUBI (+ 35 months worth of withdrawn DUBI)
        // => 47 months
        uint32 _mintDuration = uint32(
            _elapsedInYear + _proRatedYears * MAX_FINITE_LOCK_DURATION_SECONDS
        );

        return _mintDuration;
    }

    function percentage(
        uint256 numerator,
        uint256 denominator,
        uint256 precision
    ) internal pure returns (uint256) {
        return
            ((numerator * (uint256(10)**(precision + 1))) / denominator + 5) /
            uint256(10);
    }
}
Boostable.sol 32 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./ProtectedBoostable.sol";

/**
 * @dev Purpose Boostable primitives using the EIP712 standard
 */
abstract contract Boostable is ProtectedBoostable {
    // "Purpose", "Dubi" and "Hodl" are all under the "Purpose" umbrella
    constructor(address optIn)
        public
        ProtectedBoostable(
            optIn,
            keccak256(
                abi.encode(
                    EIP712_DOMAIN_TYPEHASH,
                    keccak256("Purpose"),
                    keccak256("1"),
                    _getChainId(),
                    address(this)
                )
            )
        )
    {}

    // Fuel alias constants - used when fuel is burned from external contract calls
    uint8 internal constant TOKEN_FUEL_ALIAS_UNLOCKED_PRPS = 0;
    uint8 internal constant TOKEN_FUEL_ALIAS_LOCKED_PRPS = 1;
    uint8 internal constant TOKEN_FUEL_ALIAS_DUBI = 2;
}
BoostableLib.sol 121 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

struct BoosterFuel {
    uint96 dubi;
    uint96 unlockedPrps;
    uint96 lockedPrps;
    uint96 intrinsicFuel;
}

struct BoosterPayload {
    address booster;
    uint64 timestamp;
    uint64 nonce;
    // Fallback for 'personal_sign' when e.g. using hardware wallets that don't support
    // EIP712 signing (yet).
    bool isLegacySignature;
}

// Library for Boostable hash functions that are completely inlined.
library BoostableLib {
    bytes32 private constant BOOSTER_PAYLOAD_TYPEHASH = keccak256(
        "BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTER_FUEL_TYPEHASH = keccak256(
        "BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)"
    );

    /**
     * @dev Returns the hash of the packed DOMAIN_SEPARATOR and `messageHash` and is used for verifying
     * a signature.
     */
    function hashWithDomainSeparator(
        bytes32 domainSeparator,
        bytes32 messageHash
    ) internal pure returns (bytes32) {
        return
            keccak256(
                abi.encodePacked("\x19\x01", domainSeparator, messageHash)
            );
    }

    /**
     * @dev Returns the hash of `payload` using the provided booster (i.e. `msg.sender`).
     */
    function hashBoosterPayload(BoosterPayload memory payload, address booster)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_PAYLOAD_TYPEHASH,
                    booster,
                    payload.timestamp,
                    payload.nonce,
                    payload.isLegacySignature
                )
            );
    }

    function hashBoosterFuel(BoosterFuel memory fuel)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_FUEL_TYPEHASH,
                    fuel.dubi,
                    fuel.unlockedPrps,
                    fuel.lockedPrps,
                    fuel.intrinsicFuel
                )
            );
    }

    /**
     * @dev Returns the tag found in the given `boosterMessage`.
     */
    function _readBoosterTag(bytes memory boosterMessage)
        internal
        pure
        returns (uint8)
    {
        // The tag is either the 32th byte or the 64th byte depending on whether
        // the booster message contains dynamic bytes or not.
        //
        // If it contains a dynamic byte array, then the first word points to the first
        // data location.
        //
        // Therefore, we read the 32th byte and check if it's >= 32 and if so,
        // simply read the (32 + first word)th byte to get the tag.
        //
        // This imposes a limit on the number of tags we can support (<32), but
        // given that it is very unlikely for so many tags to exist it is fine.
        //
        // Read the 32th byte to get the tag, because it is a uint8 padded to 32 bytes.
        // i.e.
        // -----------------------------------------------------------------v
        // 0x0000000000000000000000000000000000000000000000000000000000000001
        //   ...
        //
        uint8 tag = uint8(boosterMessage[31]);
        if (tag >= 32) {
            // Read the (32 + tag) byte. E.g. if tag is 32, then we read the 64th:
            // --------------------------------------------------------------------
            // 0x0000000000000000000000000000000000000000000000000000000000000020 |
            //   0000000000000000000000000000000000000000000000000000000000000001 <
            //   ...
            //
            tag = uint8(boosterMessage[31 + tag]);
        }

        return tag;
    }
}
BoostableERC20.sol 137 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./Boostable.sol";
import "./BoostableLib.sol";

/**
 * @dev EIP712 boostable primitives related to ERC20 for the Purpose domain
 */
abstract contract BoostableERC20 is Boostable {
    /**
     * @dev A struct representing the payload of the ERC20 `boostedSend` function.
     */
    struct BoostedSend {
        uint8 tag;
        address sender;
        address recipient;
        uint256 amount;
        bytes data;
        BoosterFuel fuel;
        BoosterPayload boosterPayload;
    }

    /**
     * @dev A struct representing the payload of the ERC20 `boostedBurn` function.
     */
    struct BoostedBurn {
        uint8 tag;
        address account;
        uint256 amount;
        bytes data;
        BoosterFuel fuel;
        BoosterPayload boosterPayload;
    }

    uint8 internal constant BOOST_TAG_SEND = 0;
    uint8 internal constant BOOST_TAG_BURN = 1;

    bytes32 internal constant BOOSTED_SEND_TYPEHASH = keccak256(
        "BoostedSend(uint8 tag,address sender,address recipient,uint256 amount,bytes data,BoosterFuel fuel,BoosterPayload boosterPayload)BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTED_BURN_TYPEHASH = keccak256(
        "BoostedBurn(uint8 tag,address account,uint256 amount,bytes data,BoosterFuel fuel,BoosterPayload boosterPayload)BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    constructor(address optIn) public Boostable(optIn) {}

    /**
     * @dev Returns the hash of `boostedSend`.
     */
    function hashBoostedSend(BoostedSend memory send, address booster)
        internal
        view
        returns (bytes32)
    {
        return
            BoostableLib.hashWithDomainSeparator(
                _DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        BOOSTED_SEND_TYPEHASH,
                        BOOST_TAG_SEND,
                        send.sender,
                        send.recipient,
                        send.amount,
                        keccak256(send.data),
                        BoostableLib.hashBoosterFuel(send.fuel),
                        BoostableLib.hashBoosterPayload(
                            send.boosterPayload,
                            booster
                        )
                    )
                )
            );
    }

    /**
     * @dev Returns the hash of `boostedBurn`.
     */
    function hashBoostedBurn(BoostedBurn memory burn, address booster)
        internal
        view
        returns (bytes32)
    {
        return
            BoostableLib.hashWithDomainSeparator(
                _DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        BOOSTED_BURN_TYPEHASH,
                        BOOST_TAG_BURN,
                        burn.account,
                        burn.amount,
                        keccak256(burn.data),
                        BoostableLib.hashBoosterFuel(burn.fuel),
                        BoostableLib.hashBoosterPayload(
                            burn.boosterPayload,
                            booster
                        )
                    )
                )
            );
    }

    /**
     * @dev Tries to interpret the given boosterMessage and
     * return it's hash plus creation timestamp.
     */
    function decodeAndHashBoosterMessage(
        address targetBooster,
        bytes memory boosterMessage
    ) external override view returns (bytes32, uint64) {
        require(boosterMessage.length > 0, "PB-7");

        uint8 tag = _readBoosterTag(boosterMessage);
        if (tag == BOOST_TAG_SEND) {
            BoostedSend memory send = abi.decode(boosterMessage, (BoostedSend));
            return (
                hashBoostedSend(send, targetBooster),
                send.boosterPayload.timestamp
            );
        }

        if (tag == BOOST_TAG_BURN) {
            BoostedBurn memory burn = abi.decode(boosterMessage, (BoostedBurn));
            return (
                hashBoostedBurn(burn, targetBooster),
                burn.boosterPayload.timestamp
            );
        }

        // Unknown tag, so just return an empty result
        return ("", 0);
    }
}
EIP712Boostable.sol 185 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "./IOptIn.sol";
import "./BoostableLib.sol";
import "./IBoostableERC20.sol";

/**
 * @dev Boostable base contract
 *
 * All deriving contracts are expected to implement EIP712 for the message signing.
 *
 */
abstract contract EIP712Boostable {
    using ECDSA for bytes32;

    // solhint-disable-next-line var-name-mixedcase
    IOptIn internal immutable _OPT_IN;
    // solhint-disable-next-line var-name-mixedcase
    bytes32 internal immutable _DOMAIN_SEPARATOR;

    bytes32 internal constant EIP712_DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

    bytes32 private constant BOOSTER_PAYLOAD_TYPEHASH = keccak256(
        "BoosterPayload(address booster,uint64 timestamp,uint64 nonce,bool isLegacySignature)"
    );

    bytes32 internal constant BOOSTER_FUEL_TYPEHASH = keccak256(
        "BoosterFuel(uint96 dubi,uint96 unlockedPrps,uint96 lockedPrps,uint96 intrinsicFuel)"
    );

    // The boost fuel is capped to 10 of the respective token that will be used for payment.
    uint96 internal constant MAX_BOOSTER_FUEL = 10 ether;

    // A magic booster permission prefix
    bytes6 private constant MAGIC_BOOSTER_PERMISSION_PREFIX = "BOOST-";

    constructor(address optIn, bytes32 domainSeparator) public {
        _OPT_IN = IOptIn(optIn);
        _DOMAIN_SEPARATOR = domainSeparator;
    }

    // A mapping of mappings to keep track of used nonces by address to
    // protect against replays. Each 'Boostable' contract maintains it's own
    // state for nonces.
    mapping(address => uint64) private _nonces;

    //---------------------------------------------------------------

    function getNonce(address account) external virtual view returns (uint64) {
        return _nonces[account];
    }

    function getOptInStatus(address account)
        internal
        view
        returns (IOptIn.OptInStatus memory)
    {
        return _OPT_IN.getOptInStatus(account);
    }

    /**
     * @dev Called by every 'boosted'-function to ensure that `msg.sender` (i.e. a booster) is
     * allowed to perform the call for `from` (the origin) by verifying that `messageHash`
     * has been signed by `from`. Additionally, `from` provides a nonce to prevent
     * replays. Boosts cannot be verified out of order.
     *
     * @param from the address that the boost is made for
     * @param messageHash the reconstructed message hash based on the function input
     * @param payload the booster payload
     * @param signature the signature of `from`
     */
    function verifyBoost(
        address from,
        bytes32 messageHash,
        BoosterPayload memory payload,
        Signature memory signature
    ) internal {
        uint64 currentNonce = _nonces[from];
        require(currentNonce == payload.nonce - 1, "AB-1");

        _nonces[from] = currentNonce + 1;

        _verifyBoostWithoutNonce(from, messageHash, payload, signature);
    }

    /**
     * @dev Verify a boost without verifying the nonce.
     */
    function _verifyBoostWithoutNonce(
        address from,
        bytes32 messageHash,
        BoosterPayload memory payload,
        Signature memory signature
    ) internal view {
        // The sender must be the booster specified in the payload
        require(msg.sender == payload.booster, "AB-2");

        (bool isOptedInToSender, uint256 optOutPeriod) = _OPT_IN.isOptedInBy(
            msg.sender,
            from
        );

        // `from` must be opted-in to booster
        require(isOptedInToSender, "AB-3");

        // The given timestamp must not be greater than `block.timestamp + 1 hour`
        // and at most `optOutPeriod(booster)` seconds old.
        uint64 _now = uint64(block.timestamp);
        uint64 _optOutPeriod = uint64(optOutPeriod);

        bool notTooFarInFuture = payload.timestamp <= _now + 1 hours;
        bool belowMaxAge = true;

        // Calculate the absolute difference. Because of the small tolerance, `payload.timestamp`
        // may be greater than `_now`:
        if (payload.timestamp <= _now) {
            belowMaxAge = _now - payload.timestamp <= _optOutPeriod;
        }

        // Signature must not be expired
        require(notTooFarInFuture && belowMaxAge, "AB-4");

        // NOTE: Currently, hardware wallets (e.g. Ledger, Trezor) do not support EIP712 signing (specifically `signTypedData_v4`).
        // However, a user can still sign the EIP712 hash with the caveat that it's signed using `personal_sign` which prepends
        // the prefix '"\x19Ethereum Signed Message:\n" + len(message)'.
        //
        // To still support that, we add the prefix to the hash if `isLegacySignature` is true.
        if (payload.isLegacySignature) {
            messageHash = messageHash.toEthSignedMessageHash();
        }

        // Valid, if the recovered address from `messageHash` with the given `signature` matches `from`.

        address signer = ecrecover(
            messageHash,
            signature.v,
            signature.r,
            signature.s
        );

        if (!payload.isLegacySignature && signer != from) {
            // As a last resort we try anyway, in case the caller simply forgot the `isLegacySignature` flag.
            signer = ecrecover(
                messageHash.toEthSignedMessageHash(),
                signature.v,
                signature.r,
                signature.s
            );
        }

        require(from == signer, "AB-5");
    }

    /**
     * @dev Returns the hash of `payload` using the provided booster (i.e. `msg.sender`).
     */
    function hashBoosterPayload(BoosterPayload memory payload, address booster)
        internal
        pure
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    BOOSTER_PAYLOAD_TYPEHASH,
                    booster,
                    payload.timestamp,
                    payload.nonce
                )
            );
    }

    function _getChainId() internal pure returns (uint256) {
        uint256 chainId;
        assembly {
            chainId := chainid()
        }
        return chainId;
    }
}
IBoostableERC20.sol 67 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

// Token agnostic fuel struct that is passed around when the fuel is burned by a different (token) contract.
// The contract has to explicitely support the desired token that should be burned.
struct TokenFuel {
    // A token alias that must be understood by the target contract
    uint8 tokenAlias;
    uint96 amount;
}

/**
 * @dev Extends the interface of the ERC20 standard as defined in the EIP with
 * `boostedTransferFrom` to perform transfers without having to rely on an allowance.
 */
interface IBoostableERC20 {
    // ERC20
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    // Extension

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient`.
     *
     * If the caller is known by the callee, then the implementation should skip approval checks.
     * Also accepts a data payload, similar to ERC721's `safeTransferFrom` to pass arbitrary data.
     *
     */
    function boostedTransferFrom(
        address sender,
        address recipient,
        uint256 amount,
        bytes calldata data
    ) external returns (bool);

    /**
     * @dev Burns `fuel` from `from`.
     */
    function burnFuel(address from, TokenFuel memory fuel) external;
}
ProtectedBoostable.sol 328 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./EIP712Boostable.sol";
import "./IOptIn.sol";
import "./ProtectedBoostableLib.sol";

abstract contract ProtectedBoostable is EIP712Boostable {
    //---------------------------------------------------------------
    // State for non-boosted operations while opted-in and the OPT_IN permaboost is active
    //---------------------------------------------------------------

    uint256 private constant MAX_PENDING_OPS = 25;

    // A mapping of account to an opCounter.
    mapping(address => OpCounter) internal _opCounters;

    // A mapping of account to an array containing all it's pending ops.
    mapping(address => OpHandle[]) internal _pendingOpsByAddress;

    // A mapping of keccak256(address,opId) to a struct holding metadata like the associated user account and creation timestamp.
    mapping(bytes32 => OpMetadata) internal _opMetadata;

    // Event that is emitted whenever a pending op is created
    // NOTE: returning an OpHandle in the event flattens it into an array for some reason
    // i.e. emit PendingOp(0x123.., OpHandle(1, 0)) => { from: 0x123, opHandle: ['1', '0']}
    event PendingOp(address from, uint64 opId, uint8 opType);
    // Event that is emitted whenever a pending op is finalized
    event FinalizedOp(address from, uint64 opId, uint8 opType);
    // Event that is emitted whenever a pending op is reverted
    event RevertedOp(address from, uint64 opId, uint8 opType);

    constructor(address optIn, bytes32 domainSeparator)
        public
        EIP712Boostable(optIn, domainSeparator)
    {}

    //---------------------------------------------------------------
    // Pending ops
    //---------------------------------------------------------------

    /**
     * @dev Returns the metadata of an op. Returns a zero struct if it doesn't exist.
     */
    function getOpMetadata(address user, uint64 opId)
        public
        virtual
        view
        returns (OpMetadata memory)
    {
        return _opMetadata[_getOpKey(user, opId)];
    }

    /**
     * @dev Returns the metadata of an op. Returns a zero struct if it doesn't exist.
     */
    function getOpCounter(address user)
        public
        virtual
        view
        returns (OpCounter memory)
    {
        return _opCounters[user];
    }

    /**
     * @dev Returns the metadata of an op. Reverts if it doesn't exist or
     * the opType mismatches.
     */
    function safeGetOpMetadata(address user, OpHandle memory opHandle)
        public
        virtual
        view
        returns (OpMetadata memory)
    {
        OpMetadata storage metadata = _opMetadata[_getOpKey(
            user,
            opHandle.opId
        )];

        // If 'createdAt' is zero, then it's non-existent for us
        require(metadata.createdAt > 0, "PB-1");
        require(metadata.opType == opHandle.opType, "PB-2");

        return metadata;
    }

    /**
     * @dev Get the next op id for `user`
     */
    function _getNextOpId(address user) internal returns (uint64) {
        OpCounter storage counter = _opCounters[user];
        // NOTE: we always increase by 1, so it cannot overflow as long as this
        // is the only place increasing the counter.
        uint64 nextOpId = counter.value + 1;

        // This also updates the nextFinalize/Revert values
        if (counter.nextFinalize == 0) {
            // Only gets updated if currently pointing to "nothing", because FIFO
            counter.nextFinalize = nextOpId;
        }

        // nextRevert is always updated to the new opId, because LIFO
        counter.nextRevert = nextOpId;
        counter.value = nextOpId;

        // NOTE: It is safe to downcast to uint64 since it's practically impossible to overflow.
        return nextOpId;
    }

    /**
     * @dev Creates a new opHandle with the given type for `user`.
     */
    function _createNewOpHandle(
        IOptIn.OptInStatus memory optInStatus,
        address user,
        uint8 opType
    ) internal virtual returns (OpHandle memory) {
        uint64 nextOpId = _getNextOpId(user);
        OpHandle memory opHandle = OpHandle({opId: nextOpId, opType: opType});

        // NOTE: we have a hard limit of 25 pending OPs and revert if that
        // limit is exceeded.
        require(_pendingOpsByAddress[user].length < MAX_PENDING_OPS, "PB-3");

        address booster = optInStatus.optedInTo;

        _pendingOpsByAddress[user].push(opHandle);
        _opMetadata[_getOpKey(user, nextOpId)] = OpMetadata({
            createdAt: uint64(block.timestamp),
            booster: booster,
            opType: opType
        });

        return opHandle;
    }

    /**
     * @dev Delete the given `opHandle` from `user`.
     */
    function _deleteOpHandle(address user, OpHandle memory opHandle)
        internal
        virtual
    {
        OpHandle[] storage _opHandles = _pendingOpsByAddress[user];
        OpCounter storage opCounter = _opCounters[user];

        ProtectedBoostableLib.deleteOpHandle(
            user,
            opHandle,
            _opHandles,
            opCounter,
            _opMetadata
        );
    }

    /**
     * @dev Assert that the caller is allowed to finalize a pending op.
     *
     * Returns the user and createdAt timestamp of the op on success in order to
     * save some gas by minimizing redundant look-ups.
     */
    function _assertCanFinalize(address user, OpHandle memory opHandle)
        internal
        returns (uint64)
    {
        OpMetadata memory metadata = safeGetOpMetadata(user, opHandle);

        uint64 createdAt = metadata.createdAt;

        // First check if the user is still opted-in. If not, then anyone
        // can finalize since it is no longer associated with the original booster.
        IOptIn.OptInStatus memory optInStatus = getOptInStatus(user);
        if (!optInStatus.isOptedIn) {
            return createdAt;
        }

        // Revert if not FIFO order
        _assertFinalizeFIFO(user, opHandle.opId);

        return ProtectedBoostableLib.assertCanFinalize(metadata, optInStatus);
    }

    /**
     * @dev Asserts that the caller (msg.sender) is allowed to revert a pending operation.
     * The caller must be opted-in by user and provide a valid signature from the user
     * that hasn't expired yet.
     */
    function _assertCanRevert(
        address user,
        OpHandle memory opHandle,
        uint64 opTimestamp,
        bytes memory boosterMessage,
        Signature memory signature
    ) internal {
        // Revert if not LIFO order
        _assertRevertLIFO(user, opHandle.opId);

        IOptIn.OptInStatus memory optInStatus = getOptInStatus(user);

        require(
            optInStatus.isOptedIn && msg.sender == optInStatus.optedInTo,
            "PB-6"
        );

        // In order to verify the boosterMessage, we need the hash and timestamp of when it
        // was signed. To interpret the boosterMessage, consult all available hasher contracts and
        // take the first non-zero result.
        address[] memory hasherContracts = _getHasherContracts();

        // Call external library function, which performs the actual assertion. The reason
        // why it is not inlined, is that the need to reduce bytecode size.
        ProtectedBoostableLib.verifySignatureForRevert(
            user,
            opTimestamp,
            optInStatus,
            boosterMessage,
            hasherContracts,
            signature
        );
    }

    function _getHasherContracts() internal virtual returns (address[] memory);

    /**
     * @dev Asserts that the given opId is the next to be finalized for `user`.
     */
    function _assertFinalizeFIFO(address user, uint64 opId) internal virtual {
        OpCounter storage counter = _opCounters[user];
        require(counter.nextFinalize == opId, "PB-9");
    }

    /**
     * @dev Asserts that the given opId is the next to be reverted for `user`.
     */
    function _assertRevertLIFO(address user, uint64 opId) internal virtual {
        OpCounter storage counter = _opCounters[user];
        require(counter.nextRevert == opId, "PB-10");
    }

    /**
     * @dev Prepare an op revert.
     * - Asserts that the caller is allowed to revert the given op
     * - Deletes the op handle to minimize risks of reentrancy
     */
    function _prepareOpRevert(
        address user,
        OpHandle memory opHandle,
        bytes memory boosterMessage,
        Signature memory signature
    ) internal {
        OpMetadata memory metadata = safeGetOpMetadata(user, opHandle);

        _assertCanRevert(
            user,
            opHandle,
            metadata.createdAt,
            boosterMessage,
            signature
        );

        // Delete opHandle, which prevents reentrancy since `safeGetOpMetadata`
        // will fail afterwards.
        _deleteOpHandle(user, opHandle);
    }

    /**
     * @dev Returns the hash of (user, opId) which is used as a look-up
     * key in the `_opMetadata` mapping.
     */
    function _getOpKey(address user, uint64 opId)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(user, opId));
    }

    /**
     * @dev Deriving contracts can override this function to accept a boosterMessage for a given booster and
     * interpret it into a hash and timestamp.
     */
    function decodeAndHashBoosterMessage(
        address targetBooster,
        bytes memory boosterMessage
    ) external virtual view returns (bytes32, uint64) {}

    /**
     * @dev Returns the tag found in the given `boosterMesasge`.
     */
    function _readBoosterTag(bytes memory boosterMessage)
        internal
        pure
        returns (uint8)
    {
        // The tag is either the 32th byte or the 64th byte depending on whether
        // the booster message contains dynamic bytes or not.
        //
        // If it contains a dynamic byte array, then the first word points to the first
        // data location.
        //
        // Therefore, we read the 32th byte and check if it's >= 32 and if so,
        // simply read the (32 + first word)th byte to get the tag.
        //
        // This imposes a limit on the number of tags we can support (<32), but
        // given that it is very unlikely for so many tags to exist it is fine.
        //
        // Read the 32th byte to get the tag, because it is a uint8 padded to 32 bytes.
        // i.e.
        // -----------------------------------------------------------------v
        // 0x0000000000000000000000000000000000000000000000000000000000000001
        //   ...
        //
        uint8 tag = uint8(boosterMessage[31]);
        if (tag >= 32) {
            // Read the (32 + tag) byte. E.g. if tag is 32, then we read the 64th:
            // --------------------------------------------------------------------
            // 0x0000000000000000000000000000000000000000000000000000000000000020 |
            //   0000000000000000000000000000000000000000000000000000000000000001 <
            //   ...
            //
            tag = uint8(boosterMessage[31 + tag]);
        }

        return tag;
    }
}
ProtectedBoostableLib.sol 230 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "./IOptIn.sol";

struct OpHandle {
    uint8 opType;
    uint64 opId;
}

struct OpMetadata {
    uint8 opType; // the operation type
    uint64 createdAt; // the creation timestamp of an op
    address booster; // the booster at the time of when the op has been created
}

struct OpCounter {
    // The current value of the counter
    uint64 value;
    // Contains the opId that is to be finalized next - i.e. FIFO order
    uint64 nextFinalize;
    // Contains the opId that is to be reverted next - i.e. LIFO order
    uint64 nextRevert;
}

// Library containing public functions for pending ops - those will never be inlined
// to reduce the bytecode size of individual contracts.
library ProtectedBoostableLib {
    using ECDSA for bytes32;

    function deleteOpHandle(
        address user,
        OpHandle memory opHandle,
        OpHandle[] storage opHandles,
        OpCounter storage opCounter,
        mapping(bytes32 => OpMetadata) storage opMetadata
    ) public {
        uint256 length = opHandles.length;
        assert(length > 0);

        uint64 minOpId; // becomes next LIFO
        uint64 maxOpId; // becomes next FIFO

        // Pending ops are capped to MAX_PENDING_OPS. We always perform
        // MIN(length, MAX_PENDING_OPS) look-ups to do a "swap-and-pop" and
        // for updating the opCounter LIFO/FIFO pointers.
        for (uint256 i = 0; i < length; i++) {
            uint64 currOpId = opHandles[i].opId;
            if (currOpId == opHandle.opId) {
                // Overwrite item at i with last
                opHandles[i] = opHandles[length - 1];

                // Continue, to ignore this opId when updating
                // minOpId and maxOpId.
                continue;
            }

            // Update minOpId
            if (minOpId == 0 || currOpId < minOpId) {
                minOpId = currOpId;
            }

            // Update maxOpId
            if (currOpId > maxOpId) {
                maxOpId = currOpId;
            }
        }

        // Might be 0 when everything got finalized/reverted
        opCounter.nextFinalize = minOpId;
        // Might be 0 when everything got finalized/reverted
        opCounter.nextRevert = maxOpId;

        // Remove the last item
        opHandles.pop();

        // Remove metadata
        delete opMetadata[_getOpKey(user, opHandle.opId)];
    }

    function assertCanFinalize(
        OpMetadata memory metadata,
        IOptIn.OptInStatus memory optInStatus
    ) public view returns (uint64) {
        // Now there are three valid scenarios remaining:
        //
        // - msg.sender is the original booster
        // - op is expired
        // - getBoosterAddress returns a different booster than the original booster
        //
        // In the second and third case, anyone can call finalize.
        address originalBooster = metadata.booster;

        if (originalBooster == msg.sender) {
            return metadata.createdAt; // First case
        }

        address currentBooster = optInStatus.optedInTo;
        uint256 optOutPeriod = optInStatus.optOutPeriod;

        bool isExpired = block.timestamp >= metadata.createdAt + optOutPeriod;
        if (isExpired) {
            return metadata.createdAt; // Second case
        }

        if (currentBooster != originalBooster) {
            return metadata.createdAt; // Third case
        }

        revert("PB-4");
    }

    function verifySignatureForRevert(
        address user,
        uint64 opTimestamp,
        IOptIn.OptInStatus memory optInStatus,
        bytes memory boosterMessage,
        address[] memory hasherContracts,
        Signature memory signature
    ) public {
        require(hasherContracts.length > 0, "PB-12");

        // Result of hasher contract call
        uint64 signedAt;
        bytes32 boosterHash;
        bool signatureVerified;

        for (uint256 i = 0; i < hasherContracts.length; i++) {
            // Call into the hasher contract and take the first non-zero result.
            // The contract must implement the following function:
            //
            // decodeAndHashBoosterMessage(
            //     address targetBooster,
            //     bytes memory boosterMessage
            // )
            //
            // If it doesn't, then the call will fail (success=false) and we try the next one.
            // If it succeeds (success = true), then we try to decode the result.
            // solhint-disable-next-line avoid-low-level-calls
            (bool success, bytes memory result) = address(hasherContracts[i])
                .call(
                // keccak256("decodeAndHashBoosterMessage(address,bytes)")
                abi.encodeWithSelector(
                    0xaf6eec54,
                    msg.sender, /* msg.sender becomes the target booster */
                    boosterMessage
                )
            );

            if (!success) {
                continue;
            }

            // The result is exactly 2 words long = 512 bits = 64 bytes
            // 32 bytes for the expected message hash
            // 8 bytes (padded to 32 bytes) for the expected timestamp
            if (result.length != 64) {
                continue;
            }

            // NOTE: A contract with malintent could return any hash that we would
            // try to recover against. But there is no harm done in doing so since
            // the user must have signed it.
            //
            // However, it might return an unrelated timestamp, that the user hasn't
            // signed - so it could prolong the expiry of a signature which is a valid
            // concern whose risk we minimize by using also the op timestamp which guarantees
            // that a signature eventually expires.

            // Decode and recover signer
            (boosterHash, signedAt) = abi.decode(result, (bytes32, uint64));
            address signer = ecrecover(
                boosterHash,
                signature.v,
                signature.r,
                signature.s
            );

            if (user != signer) {
                // NOTE: Currently, hardware wallets (e.g. Ledger, Trezor) do not support EIP712 signing (specifically `signTypedData_v4`).
                // However, a user can still sign the EIP712 hash with the caveat that it's signed using `personal_sign` which prepends
                // the prefix '"\x19Ethereum Signed Message:\n" + len(message)'.
                //
                // To still support that, we also add the prefix and try to use the recovered address instead:
                signer = ecrecover(
                    boosterHash.toEthSignedMessageHash(),
                    signature.v,
                    signature.r,
                    signature.s
                );
            }

            // If we recovered `user` from the signature, then we have a valid signature.
            if (user == signer) {
                signatureVerified = true;
                break;
            }

            // Keep trying
        }

        // Revert if signature couldn't be verified with any of the returned hashes
        require(signatureVerified, "PB-8");

        // Lastly, the current time must not be older than:
        // MIN(opTimestamp, signedAt) + optOutPeriod * 3
        uint64 _now = uint64(block.timestamp);
        // The maximum age is equal to whichever is lowest:
        //      opTimestamp + optOutPeriod * 3
        //      signedAt + optOutPeriod * 3
        uint64 maximumAge;
        if (opTimestamp > signedAt) {
            maximumAge = signedAt + uint64(optInStatus.optOutPeriod * 3);
        } else {
            maximumAge = opTimestamp + uint64(optInStatus.optOutPeriod * 3);
        }

        require(_now <= maximumAge, "PB-11");
    }

    function _getOpKey(address user, uint64 opId)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(user, opId));
    }
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.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 GSN 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 payable) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}
SafeMath.sol 159 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}
Ownable.sol 68 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

import "../GSN/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.
 */
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 () internal {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        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 {
        emit OwnershipTransferred(_owner, address(0));
        _owner = 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");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}
ECDSA.sol 83 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Check the signature length
        if (signature.length != 65) {
            revert("ECDSA: invalid signature length");
        }

        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // ecrecover takes the signature parameters, and the only way to get them
        // currently is to use assembly.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }

        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            revert("ECDSA: invalid signature 's' value");
        }

        if (v != 27 && v != 28) {
            revert("ECDSA: invalid signature 'v' value");
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * replicates the behavior of the
     * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
     * JSON-RPC method.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }
}
IERC20.sol 77 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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 `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, 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 `sender` to `recipient` 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 sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @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);
}
IERC1820Registry.sol 111 lines
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;

/**
 * @dev Interface of the global ERC1820 Registry, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register
 * implementers for interfaces in this registry, as well as query support.
 *
 * Implementers may be shared by multiple accounts, and can also implement more
 * than a single interface for each account. Contracts can implement interfaces
 * for themselves, but externally-owned accounts (EOA) must delegate this to a
 * contract.
 *
 * {IERC165} interfaces can also be queried via the registry.
 *
 * For an in-depth explanation and source code analysis, see the EIP text.
 */
interface IERC1820Registry {
    /**
     * @dev Sets `newManager` as the manager for `account`. A manager of an
     * account is able to set interface implementers for it.
     *
     * By default, each account is its own manager. Passing a value of `0x0` in
     * `newManager` will reset the manager to this initial state.
     *
     * Emits a {ManagerChanged} event.
     *
     * Requirements:
     *
     * - the caller must be the current manager for `account`.
     */
    function setManager(address account, address newManager) external;

    /**
     * @dev Returns the manager for `account`.
     *
     * See {setManager}.
     */
    function getManager(address account) external view returns (address);

    /**
     * @dev Sets the `implementer` contract as ``account``'s implementer for
     * `interfaceHash`.
     *
     * `account` being the zero address is an alias for the caller's address.
     * The zero address can also be used in `implementer` to remove an old one.
     *
     * See {interfaceHash} to learn how these are created.
     *
     * Emits an {InterfaceImplementerSet} event.
     *
     * Requirements:
     *
     * - the caller must be the current manager for `account`.
     * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not
     * end in 28 zeroes).
     * - `implementer` must implement {IERC1820Implementer} and return true when
     * queried for support, unless `implementer` is the caller. See
     * {IERC1820Implementer-canImplementInterfaceForAddress}.
     */
    function setInterfaceImplementer(address account, bytes32 interfaceHash, address implementer) external;

    /**
     * @dev Returns the implementer of `interfaceHash` for `account`. If no such
     * implementer is registered, returns the zero address.
     *
     * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28
     * zeroes), `account` will be queried for support of it.
     *
     * `account` being the zero address is an alias for the caller's address.
     */
    function getInterfaceImplementer(address account, bytes32 interfaceHash) external view returns (address);

    /**
     * @dev Returns the interface hash for an `interfaceName`, as defined in the
     * corresponding
     * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP].
     */
    function interfaceHash(string calldata interfaceName) external pure returns (bytes32);

    /**
     *  @notice Updates the cache with whether the contract implements an ERC165 interface or not.
     *  @param account Address of the contract for which to update the cache.
     *  @param interfaceId ERC165 interface for which to update the cache.
     */
    function updateERC165Cache(address account, bytes4 interfaceId) external;

    /**
     *  @notice Checks whether a contract implements an ERC165 interface or not.
     *  If the result is not cached a direct lookup on the contract address is performed.
     *  If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling
     *  {updateERC165Cache} with the contract address.
     *  @param account Address of the contract to check.
     *  @param interfaceId ERC165 interface to check.
     *  @return True if `account` implements `interfaceId`, false otherwise.
     */
    function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);

    /**
     *  @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.
     *  @param account Address of the contract to check.
     *  @param interfaceId ERC165 interface to check.
     *  @return True if `account` implements `interfaceId`, false otherwise.
     */
    function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);

    event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);

    event ManagerChanged(address indexed account, address indexed newManager);
}

Read Contract

allowance 0xdd62ed3e → uint256
balanceOf 0x70a08231 → uint256
decimals 0x313ce567 → uint8
decodeAndHashBoosterMessage 0xaf6eec54 → bytes32, uint64
getNonce 0x2d0335ab → uint64
getOpCounter 0x9cc0b8b3 → tuple
getOpMetadata 0x252b2d20 → tuple
name 0x06fdde03 → string
owner 0x8da5cb5b → address
safeGetOpMetadata 0xa39c6c41 → tuple
symbol 0x95d89b41 → string
totalSupply 0x18160ddd → uint256
unpackedDataOf 0x2ec4ac0a → tuple

Write Contract 19 functions

These functions modify contract state and require a wallet transaction to execute.

approve 0x095ea7b3
address spender
uint256 value
returns: bool
boostedBurn 0x325d45b5
tuple message
tuple signature
boostedBurnBatch 0x545ba191
tuple[] burns
tuple[] signatures
boostedSend 0xb7dafd9f
tuple send
tuple signature
boostedSendBatch 0x0bd69570
tuple[] sends
tuple[] signatures
boostedTransferFrom 0x09ee40a0
address sender
address recipient
uint256 amount
bytes data
returns: bool
burn 0xfe9d9303
uint256 amount
bytes data
burnFuel 0xa80242e1
address from
tuple fuel
decreaseAllowance 0xa457c2d7
address spender
uint256 subtractedValue
returns: bool
finalizePendingOp 0xbe085a4f
address user
tuple opHandle
hodlMint 0x4598980b
address to
uint256 amount
increaseAllowance 0x39509351
address spender
uint256 addedValue
returns: bool
mint 0x40c10f19
address to
uint256 amount
purposeMint 0xb55ff884
address to
uint256 amount
renounceOwnership 0x715018a6
No parameters
revertPendingOp 0xdfb5bed7
address user
tuple opHandle
bytes boosterMessage
tuple signature
transfer 0xa9059cbb
address recipient
uint256 amount
returns: bool
transferFrom 0x23b872dd
address holder
address recipient
uint256 amount
returns: bool
transferOwnership 0xf2fde38b
address newOwner

Recent Transactions

No transactions found for this address