Address Contract Verified
Address
0x0A8D311B99DdAA9EBb45FD606Eb0A1533004f26b
Balance
0.361000 ETH
Nonce
2
Code Size
10935 bytes
Creator
0xB031159C...d512 at tx 0xf0eab82f...1fc8c8
Indexed Transactions
0
Contract Bytecode
10935 bytes
0x6080604052600436106102c95760003560e01c80636c0360eb11610175578063a78c2965116100dc578063bac91c6a11610095578063d2d1cd4f1161006f578063d2d1cd4f146108ec578063e985e9c514610902578063f12615d61461093d578063f2fde38b1461095357600080fd5b8063bac91c6a1461088c578063c6b4102d146108ac578063c87b56dd146108cc57600080fd5b8063a78c296514610561578063aad2b723146107f9578063acd379cc14610819578063adc4f2bf14610839578063ae4875971461084c578063b88d4fde1461086c57600080fd5b806378e0b7601161012e57806378e0b760146106b35780638d7ba8ba146106d35780638da5cb5b1461078e57806395d89b41146107ae578063a04eecae146107c3578063a22cb465146107d957600080fd5b80636c0360eb146106085780636e33a9fb1461061d5780636ff5dfd41461063d578063700bfe001461065357806370a082311461066657806377f7a5cd1461068657600080fd5b80632ed73d681161023457806342842e0e116101ed5780635b7633d0116101c75780635b7633d0146105ae5780635c975abb146105ce5780635c9c01d1146105615780636352211e146105e857600080fd5b806342842e0e146105415780634db8f13c14610561578063567e34ba1461058157600080fd5b80632ed73d6814610494578063333171bb146104c1578063354cfe43146104d657806339a0c6f9146104f65780633a5a2995146105165780633ccfd60b1461052c57600080fd5b806318160ddd1161028657806318160ddd146103de5780631cdfffa4146103f45780631e7553821461041457806323b872dd1461043457806329ec57a1146104545780632b76f8ea1461047457600080fd5b806301ffc9a7146102ce57806306fdde031461030357806307de128e14610325578063081812fc14610362578063095ea7b314610398578063152d92da146103ba575b600080fd5b3480156102da57600080fd5b506102ee6102e9366004612188565b610973565b60405190151581526020015b60405180910390f35b34801561030f57600080fd5b506103186109c5565b6040516102fa91906121d5565b34801561033157600080fd5b50600f5461034a9061010090046001600160a01b031681565b6040516001600160a01b0390911681526020016102fa565b34801561036e57600080fd5b5061034a61037d366004612208565b6004602052600090815260409020546001600160a01b031681565b3480156103a457600080fd5b506103b86103b3366004612238565b610a53565b005b3480156103c657600080fd5b506103d060095481565b6040519081526020016102fa565b3480156103ea57600080fd5b506103d060075481565b34801561040057600080fd5b506103b861040f366004612208565b610b3a565b34801561042057600080fd5b506103b861042f3660046122ab565b610b69565b34801561044057600080fd5b506103b861044f3660046122fe565b610d27565b34801561046057600080fd5b506103b861046f366004612208565b610edc565b34801561048057600080fd5b506103b861048f366004612208565b610f0b565b3480156104a057600080fd5b506103d06104af366004612208565b60126020526000908152604090205481565b3480156104cd57600080fd5b506103b8610f3a565b3480156104e257600080fd5b506103d06104f136600461233a565b610f78565b34801561050257600080fd5b506103b8610511366004612387565b610fdb565b34801561052257600080fd5b506103d0600b5481565b34801561053857600080fd5b506103b8611017565b34801561054d57600080fd5b506103b861055c3660046122fe565b611059565b34801561056d57600080fd5b506103d061057c3660046123c9565b61114c565b34801561058d57600080fd5b506103d061059c366004612420565b60136020526000908152604090205481565b3480156105ba57600080fd5b5060105461034a906001600160a01b031681565b3480156105da57600080fd5b50600f546102ee9060ff1681565b3480156105f457600080fd5b5061034a610603366004612208565b6111b7565b34801561061457600080fd5b5061031861120e565b34801561062957600080fd5b506103b8610638366004612208565b61121b565b34801561064957600080fd5b506103d0600c5481565b6103b861066136600461243b565b61124a565b34801561067257600080fd5b506103d0610681366004612420565b611489565b34801561069257600080fd5b506103d06106a1366004612208565b60116020526000908152604090205481565b3480156106bf57600080fd5b506103b86106ce3660046122ab565b6114ec565b3480156106df57600080fd5b5061073f6106ee366004612208565b6000908152601160209081526040918290205460e081901c9363ffffffff60c083901c81169460a084901c821694608085901c831694606081901c8416949281901c8416939181901c821692911690565b6040805163ffffffff998a16815297891660208901529588169587019590955292861660608601529085166080850152841660a0840152831660c083015290911660e0820152610100016102fa565b34801561079a57600080fd5b5060065461034a906001600160a01b031681565b3480156107ba57600080fd5b50610318611632565b3480156107cf57600080fd5b506103d0600a5481565b3480156107e557600080fd5b506103b86107f43660046124a5565b61163f565b34801561080557600080fd5b506103b8610814366004612420565b6116ab565b34801561082557600080fd5b506103b86108343660046124e1565b6116f7565b6103b861084736600461252d565b611836565b34801561085857600080fd5b506103b8610867366004612595565b611a6b565b34801561087857600080fd5b506103b86108873660046125f6565b611b66565b34801561089857600080fd5b506103b86108a7366004612208565b611c4e565b3480156108b857600080fd5b506103b86108c73660046122ab565b611c7d565b3480156108d857600080fd5b506103186108e7366004612208565b611e44565b3480156108f857600080fd5b506103d060085481565b34801561090e57600080fd5b506102ee61091d366004612648565b600560209081526000928352604080842090915290825290205460ff1681565b34801561094957600080fd5b506103d0600d5481565b34801561095f57600080fd5b506103b861096e366004612420565b611ef3565b60006301ffc9a760e01b6001600160e01b0319831614806109a457506380ac58cd60e01b6001600160e01b03198316145b806109bf5750635b5e139f60e01b6001600160e01b03198316145b92915050565b600080546109d29061267b565b80601f01602080910402602001604051908101604052809291908181526020018280546109fe9061267b565b8015610a4b5780601f10610a2057610100808354040283529160200191610a4b565b820191906000526020600020905b815481529060010190602001808311610a2e57829003601f168201915b505050505081565b6000818152600260205260409020546001600160a01b031633811480610a9c57506001600160a01b038116600090815260056020908152604080832033845290915290205460ff165b610ade5760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b60448201526064015b60405180910390fd5b60008281526004602052604080822080546001600160a01b0319166001600160a01b0387811691821790925591518593918516917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a4505050565b6006546001600160a01b03163314610b645760405162461bcd60e51b8152600401610ad5906126b5565b600a55565b82610bb83330604051602001610b8990636275727960e01b815260040190565b604051602081830303815290604052610ba1906126db565b33600090815260136020526040902054899061114c565b14610bd55760405162461bcd60e51b8152600401610ad590612712565b610be0838383611f69565b610bfc5760405162461bcd60e51b8152600401610ad59061273f565b6000610c07856111b7565b90506001600160a01b038116610c4c5760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610ad5565b6001600160a01b038116600081815260036020908152604080832080546000190190557f262bb27bbdd95c1cdc8e16957e36e38579ea44f7f6413dd7a9c75939def06b2c8054600101905588835260028252808320805461dead6001600160a01b03199182168117909255600490935281842080549093169092555188939192600080516020612a6283398151915291a4604051339086907f6d1f829e44cf0d38014e70d7b78b7a4251f29b55b319b387f7dd98359685c81d90600090a3505033600090815260136020526040902080546001019055505050565b6000818152600260205260409020546001600160a01b03848116911614610d7d5760405162461bcd60e51b815260206004820152600a60248201526957524f4e475f46524f4d60b01b6044820152606401610ad5565b6001600160a01b038216610dc75760405162461bcd60e51b81526020600482015260116024820152701253959053125117d49150d25412515395607a1b6044820152606401610ad5565b336001600160a01b0384161480610e0157506001600160a01b038316600090815260056020908152604080832033845290915290205460ff165b80610e2257506000818152600460205260409020546001600160a01b031633145b610e5f5760405162461bcd60e51b815260206004820152600e60248201526d1393d517d055551213d49256915160921b6044820152606401610ad5565b6001600160a01b0380841660008181526003602090815260408083208054600019019055938616808352848320805460010190558583526002825284832080546001600160a01b0319908116831790915560049092528483208054909216909155925184939291600080516020612a6283398151915291a4505050565b6006546001600160a01b03163314610f065760405162461bcd60e51b8152600401610ad5906126b5565b600c55565b6006546001600160a01b03163314610f355760405162461bcd60e51b8152600401610ad5906126b5565b600955565b6006546001600160a01b03163314610f645760405162461bcd60e51b8152600401610ad5906126b5565b600f805460ff19811660ff90911615179055565b6040516bffffffffffffffffffffffff19606086811b8216602084015285901b1660348201526001600160e01b031983166048820152604c8101829052600090606c01604051602081830303815290604052805190602001209050949350505050565b6006546001600160a01b031633146110055760405162461bcd60e51b8152600401610ad5906126b5565b600e6110128284836127d3565b505050565b6006546001600160a01b031633146110415760405162461bcd60e51b8152600401610ad5906126b5565b600080818283473386f18015611055578182f35b8182fd5b611064838383610d27565b6001600160a01b0382163b158061110d5750604051630a85bd0160e11b8082523360048301526001600160a01b03858116602484015260448301849052608060648401526000608484015290919084169063150b7a029060a4016020604051808303816000875af11580156110dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111019190612893565b6001600160e01b031916145b6110125760405162461bcd60e51b815260206004820152601060248201526f155394d0519157d49150d2541251539560821b6044820152606401610ad5565b6040516bffffffffffffffffffffffff19606087811b8216602084015286901b1660348201526001600160e01b031984166048820152604c8101839052606c8101829052600090608c0160405160208183030381529060405280519060200120905095945050505050565b6000818152600260205260409020546001600160a01b0316806112095760405162461bcd60e51b815260206004820152600a6024820152691393d517d3525395115160b21b6044820152606401610ad5565b919050565b600e80546109d29061267b565b6006546001600160a01b031633146112455760405162461bcd60e51b8152600401610ad5906126b5565b600b55565b853380611256836111b7565b6001600160a01b03161461127c5760405162461bcd60e51b8152600401610ad5906128b0565b87600954146112cd5760405162461bcd60e51b815260206004820152601c60248201527f4d7573742062652074686520476f62626c657220476f62626c657221000000006044820152606401610ad5565b600d5434146113155760405162461bcd60e51b81526020600482015260146024820152734e6f7420656e6f756768204554482073656e742160601b6044820152606401610ad5565b8461136d333060405160200161133e906c33b7b1313632a3b7b1313632b960991b8152600d0190565b604051602081830303815290604052611356906126db565b336000908152601360205260409020548b9061114c565b1461138a5760405162461bcd60e51b8152600401610ad590612712565b611395858585611f69565b6113b15760405162461bcd60e51b8152600401610ad59061273f565b60006113bc886111b7565b6001600160a01b0381166000818152600360209081526040808320805460001901905533808452818420805460010190558d84526002835281842080546001600160a01b0319908116831790915560049093528184208054909316909255519394508b93909291600080516020612a6283398151915291a486888a7f5fa0b7a4af71eaeec6c8b4e527f1c7d57b68825c59afce15cf5f7677fa4ae9cb60405160405180910390a4505050600993909355505033600090815260136020526040902080546001019055505050565b60006001600160a01b0382166114d05760405162461bcd60e51b815260206004820152600c60248201526b5a45524f5f4144445245535360a01b6044820152606401610ad5565b506001600160a01b031660009081526003602052604090205490565b8333806114f8836111b7565b6001600160a01b03161461151e5760405162461bcd60e51b8152600401610ad5906128b0565b8461156e333060405160200161154190666d69746f73697360c81b815260070190565b604051602081830303815290604052611559906126db565b33600090815260136020526040902054610f78565b1461158b5760405162461bcd60e51b8152600401610ad590612712565b611596858585611f69565b6115b25760405162461bcd60e51b8152600401610ad59061273f565b60006008546107d06115c491906128f3565b90506115d03382611fc3565b6040513390829089907fc115e05e23155867c7e139d51a6e7b80d8e9d029655b6f810119c030b973921b90600090a450506008805460019081019091556007805482019055336000908152601360205260409020805490910190555050505050565b600180546109d29061267b565b3360008181526005602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b6006546001600160a01b031633146116d55760405162461bcd60e51b8152600401610ad5906126b5565b601080546001600160a01b0319166001600160a01b0392909216919091179055565b6107d0600754600161170991906128f3565b11156117575760405162461bcd60e51b815260206004820152601a60248201527f47656e65736973206d617820737570706c7920726561636865640000000000006044820152606401610ad5565b600f5460ff161561179f5760405162461bcd60e51b8152602060048201526012602482015271135d5cdd081b9bdd081899481c185d5cd95960721b6044820152606401610ad5565b826117bf333060405160200161154190631b5a5b9d60e21b815260040190565b146117dc5760405162461bcd60e51b8152600401610ad590612712565b6117e7838383611f69565b6118035760405162461bcd60e51b8152600401610ad59061273f565b61180f33600754611fc3565b50506007805460019081019091553360009081526013602052604090208054909101905550565b843380611842836111b7565b6001600160a01b0316146118685760405162461bcd60e51b8152600401610ad5906128b0565b8461188f3330604051602001611541906a616374696f6e416c69766560a81b8152600b0190565b146118ac5760405162461bcd60e51b8152600401610ad590612712565b6118b7858585611f69565b6118d35760405162461bcd60e51b8152600401610ad59061273f565b60008860028111156118e7576118e761290b565b03611952578560ff16600a546118fd9190612921565b341461191b5760405162461bcd60e51b8152600401610ad590612940565b604051339060ff88169089907f34451d2406ea78fab417966fed7c007ca3143382b25cee653588abb9c8d9f00890600090a4611a39565b60018860028111156119665761196661290b565b036119d1578560ff16600b5461197c9190612921565b341461199a5760405162461bcd60e51b8152600401610ad590612940565b604051339060ff88169089907f9515093a0215bb0e2e2244374ea65a5c55b4e40f728f2255977d3aa2881e8c5090600090a4611a39565b60028860028111156119e5576119e561290b565b03611a3957600c543414611a0b5760405162461bcd60e51b8152600401610ad590612940565b604051339088907f2057376b06556f4cca13bb6be220c9eeae3ca2531e0e2dfd8f21126e56b8b61790600090a35b505050600093845250506012602090815260408084208054340190553384526013909152909120805460010190555050565b843380611a77836111b7565b6001600160a01b031614611a9d5760405162461bcd60e51b8152600401610ad5906128b0565b84611ac8333060405160200161133e906e636f6e66696775726554726169747360881b8152600f0190565b14611ae55760405162461bcd60e51b8152600401610ad590612712565b611af0858585611f69565b611b0c5760405162461bcd60e51b8152600401610ad59061273f565b60008781526011602052604080822088905551879189917f8f25dfe3ab2aa69e8c75a62082e281bad5a6837df174231b2e79db6cd67372aa9190a35050336000908152601360205260409020805460010190555050505050565b611b71858585610d27565b6001600160a01b0384163b1580611c085750604051630a85bd0160e11b808252906001600160a01b0386169063150b7a0290611bb99033908a9089908990899060040161296d565b6020604051808303816000875af1158015611bd8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611bfc9190612893565b6001600160e01b031916145b611c475760405162461bcd60e51b815260206004820152601060248201526f155394d0519157d49150d2541251539560821b6044820152606401610ad5565b5050505050565b6006546001600160a01b03163314611c785760405162461bcd60e51b8152600401610ad5906126b5565b600d55565b833380611c89836111b7565b6001600160a01b031614611caf5760405162461bcd60e51b8152600401610ad5906128b0565b84611cd63330604051602001611541906a1d5b9b1bd8dad51c985a5d60aa1b8152600b0190565b14611cf35760405162461bcd60e51b8152600401610ad590612712565b611cfe858585611f69565b611d1a5760405162461bcd60e51b8152600401610ad59061273f565b6000600f60019054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d6f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d9391906129c1565b600f546040516335313c2160e11b815233600482015291925061010090046001600160a01b031690636a62784290602401600060405180830381600087803b158015611dde57600080fd5b505af1158015611df2573d6000803e3d6000fd5b505060405133925083915089907fb9fd29c6d3a708dc686fbd2bb7d536911b863fda0a12c8e450aa88104108691390600090a45050336000908152601360205260409020805460010190555050505050565b60606007548210611e975760405162461bcd60e51b815260206004820152601960248201527f5468697320746f6b656e20646f6573206e6f74206578697374000000000000006044820152606401610ad5565b6000600e8054611ea69061267b565b905011611ec257604051806020016040528060008152506109bf565b600e611ecd836120bc565b604051602001611ede9291906129da565b60405160208183030381529060405292915050565b6006546001600160a01b03163314611f1d5760405162461bcd60e51b8152600401610ad5906126b5565b600680546001600160a01b0319166001600160a01b03831690811790915560405133907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a350565b600080611fa78484611fa0886020527b19457468657265756d205369676e6564204d6573736167653a0a3332600052603c60042090565b9190612100565b6010546001600160a01b039081169116149150505b9392505050565b6001600160a01b03821661200d5760405162461bcd60e51b81526020600482015260116024820152701253959053125117d49150d25412515395607a1b6044820152606401610ad5565b6000818152600260205260409020546001600160a01b0316156120635760405162461bcd60e51b815260206004820152600e60248201526d1053149150511657d3525395115160921b6044820152606401610ad5565b6001600160a01b038216600081815260036020908152604080832080546001019055848352600290915280822080546001600160a01b031916841790555183929190600080516020612a62833981519152908290a45050565b606060a06040510180604052602081039150506000815280825b600183039250600a81066030018353600a9004806120d65750819003601f19909101908152919050565b600060418203611fbc576040516040846040377f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0606051116121655784600052604084013560001a602052602060406080600060015afa5060006060523d6060035191505b6040529392505050565b6001600160e01b03198116811461218557600080fd5b50565b60006020828403121561219a57600080fd5b8135611fbc8161216f565b60005b838110156121c05781810151838201526020016121a8565b838111156121cf576000848401525b50505050565b60208152600082518060208401526121f48160408501602087016121a5565b601f01601f19169190910160400192915050565b60006020828403121561221a57600080fd5b5035919050565b80356001600160a01b038116811461120957600080fd5b6000806040838503121561224b57600080fd5b61225483612221565b946020939093013593505050565b60008083601f84011261227457600080fd5b50813567ffffffffffffffff81111561228c57600080fd5b6020830191508360208285010111156122a457600080fd5b9250929050565b600080600080606085870312156122c157600080fd5b8435935060208501359250604085013567ffffffffffffffff8111156122e657600080fd5b6122f287828801612262565b95989497509550505050565b60008060006060848603121561231357600080fd5b61231c84612221565b925061232a60208501612221565b9150604084013590509250925092565b6000806000806080858703121561235057600080fd5b61235985612221565b935061236760208601612221565b925060408501356123778161216f565b9396929550929360600135925050565b6000806020838503121561239a57600080fd5b823567ffffffffffffffff8111156123b157600080fd5b6123bd85828601612262565b90969095509350505050565b600080600080600060a086880312156123e157600080fd5b6123ea86612221565b94506123f860208701612221565b935060408601356124088161216f565b94979396509394606081013594506080013592915050565b60006020828403121561243257600080fd5b611fbc82612221565b60008060008060008060a0878903121561245457600080fd5b86359550602087013594506040870135935060608701359250608087013567ffffffffffffffff81111561248757600080fd5b61249389828a01612262565b979a9699509497509295939492505050565b600080604083850312156124b857600080fd5b6124c183612221565b9150602083013580151581146124d657600080fd5b809150509250929050565b6000806000604084860312156124f657600080fd5b83359250602084013567ffffffffffffffff81111561251457600080fd5b61252086828701612262565b9497909650939450505050565b60008060008060008060a0878903121561254657600080fd5b86356003811061255557600080fd5b955060208701359450604087013560ff8116811461257257600080fd5b935060608701359250608087013567ffffffffffffffff81111561248757600080fd5b6000806000806000608086880312156125ad57600080fd5b853594506020860135935060408601359250606086013567ffffffffffffffff8111156125d957600080fd5b6125e588828901612262565b969995985093965092949392505050565b60008060008060006080868803121561260e57600080fd5b61261786612221565b945061262560208701612221565b935060408601359250606086013567ffffffffffffffff8111156125d957600080fd5b6000806040838503121561265b57600080fd5b61266483612221565b915061267260208401612221565b90509250929050565b600181811c9082168061268f57607f821691505b6020821081036126af57634e487b7160e01b600052602260045260246000fd5b50919050565b6020808252600c908201526b15539055551213d49256915160a21b604082015260600190565b805160208201516001600160e01b0319808216929190600483101561270a5780818460040360031b1b83161693505b505050919050565b60208082526013908201527257726f6e67206d65737361676520686173682160681b604082015260600190565b60208082526016908201527524b73b30b634b21030b2323932b9b99039b4b3b732b960511b604082015260600190565b634e487b7160e01b600052604160045260246000fd5b601f82111561101257600081815260208120601f850160051c810160208610156127ac5750805b601f850160051c820191505b818110156127cb578281556001016127b8565b505050505050565b67ffffffffffffffff8311156127eb576127eb61276f565b6127ff836127f9835461267b565b83612785565b6000601f841160018114612833576000851561281b5750838201355b600019600387901b1c1916600186901b178355611c47565b600083815260209020601f19861690835b828110156128645786850135825560209485019460019092019101612844565b50868210156128815760001960f88860031b161c19848701351681555b505060018560011b0183555050505050565b6000602082840312156128a557600080fd5b8151611fbc8161216f565b60208082526013908201527226bab9ba103132903a37b5b2b71037bbb732b960691b604082015260600190565b634e487b7160e01b600052601160045260246000fd5b60008219821115612906576129066128dd565b500190565b634e487b7160e01b600052602160045260246000fd5b600081600019048311821515161561293b5761293b6128dd565b500290565b602080825260139082015272139bdd08195b9bdd59da081155120814d95b9d606a1b604082015260600190565b6001600160a01b038681168252851660208201526040810184905260806060820181905281018290526000828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b6000602082840312156129d357600080fd5b5051919050565b60008084546129e88161267b565b60018281168015612a005760018114612a1557612a44565b60ff1984168752821515830287019450612a44565b8860005260208060002060005b85811015612a3b5781548a820152908401908201612a22565b50505082870194505b505050508351612a588183602088016121a5565b0194935050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220a70f8a086a9ef20b7c707e9b3f89f48c61d615dedeb263013c00bd0ef1e78ba864736f6c634300080f0033
Verified Source Code Full Match
Compiler: v0.8.15+commit.e14f2714
EVM: london
Optimization: Yes (200 runs)
Owned.sol 44 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
ERC721.sol 231 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) internal _ownerOf;
mapping(address => uint256) internal _balanceOf;
function ownerOf(uint256 id) public view virtual returns (address owner) {
require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
}
function balanceOf(address owner) public view virtual returns (uint256) {
require(owner != address(0), "ZERO_ADDRESS");
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 id) public virtual {
address owner = _ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(
address from,
address to,
uint256 id
) public virtual {
require(from == _ownerOf[id], "WRONG_FROM");
require(to != address(0), "INVALID_RECIPIENT");
require(
msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint256 id
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 id) internal virtual {
require(to != address(0), "INVALID_RECIPIENT");
require(_ownerOf[id] == address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.
unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = _ownerOf[id];
require(owner != address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.
unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*//////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _safeMint(
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
ETHGobblers.sol 519 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "solady/utils/ECDSA.sol";
import "solady/utils/LibString.sol";
import "solmate/auth/Owned.sol";
import "solmate/tokens/ERC721.sol";
import "./GobDrops.sol";
/// @title ETH Gobblers
/// @author EtDu
/// @notice Gobble... Gobble... Gobble...
contract ETHGobblers is ERC721, Owned {
using ECDSA for bytes32;
using LibString for uint256;
/*------------------------------------------------------*/
/* VARIABLES / CONSTANTS
/*------------------------------------------------------*/
uint256 public totalSupply = 0;
// Genesis max supply, newer generations can be minted via mitosis
uint256 constant genesisMaxSupply = 2000;
// Supply of new ETH Gobblers from mitosis
uint256 public mitosisSupply = 0;
// Current Gobbler Gobbler token ID. Has the power to gobble one ETH Gobbler before declaring another Gobbler Gobbler.
uint256 public currentGobblerGobbler;
// ETH Gobbler action pricing
uint256 public feedPrice = 0.001 ether;
uint256 public groomPrice = 0.01 ether;
uint256 public sleepPrice = 0.1 ether;
uint256 public gobbleGobblerPrice = 1 ether;
string public baseURI;
bool public paused;
GobDrops public gobDrops;
// ETHGobblers signer of naughty/nice list verifications
address public signerAddress;
address constant burnAddress = 0x000000000000000000000000000000000000dEaD;
// Gobbler ID to equipped traits
// Traits can be swapped/updated, separate from base artistic traits
// Base traits are kept track of off-chain
// Each trait type value represents a trait tokenID from GobDrops
// 7 trait IDs (uint32) are packed into a single uint256 variable to save on storage costs
// Value of 2^32 - 1 means no traits
mapping(uint256 => uint256) public equippedTraits;
// Total amount of ETH Gobbled per Gobbler ID (in wei)
mapping(uint256 => uint256) public ETHGobbled;
// Current nonce per signer, prevents signature replay attacks
// Backend should query it for creating signatures
mapping(address => uint256) public signatureNonce;
enum Action {
Feed, Groom, Sleep
}
/*------------------------------------------------------*/
/* EVENTS
/*------------------------------------------------------*/
// All actions are event based, the backend handles health logic based on emitted events
event Feed(
uint256 indexed tokenID,
uint8 indexed amount,
address indexed owner
);
event Groom(
uint256 indexed tokenID,
uint8 indexed amount,
address indexed owner
);
event Sleep(
uint256 indexed tokenID,
address indexed owner
);
event Bury(
uint256 indexed tokenID,
address indexed owner
);
event Mitosis(
uint256 indexed parentTokenID,
uint256 indexed newTokenID,
address indexed owner
);
event ConfigureTraits(
uint256 indexed tokenID,
uint256 indexed traitIDs
);
event TraitUnlocked(
uint256 indexed parentGobblerID,
uint256 indexed newTraitTokenID,
address indexed owner
);
event GobblerGobbled(
uint256 indexed gobblerGobblerID,
uint256 indexed victimID,
uint256 indexed newGobblerGobblerID
);
/*------------------------------------------------------*/
/* CONSTRUCTOR
/*------------------------------------------------------*/
constructor(address signer) ERC721("ETH GOBBLERS", "GOOEY") Owned(msg.sender){
signerAddress = signer;
gobDrops = new GobDrops(msg.sender);
}
modifier onlyTokenOwner(uint256 tokenID, address holder) {
require(ownerOf(tokenID) == holder, "Must be token owner");
_;
}
/*------------------------------------------------------*/
/* USER ACTIONS
/*------------------------------------------------------*/
/// @notice Mint a gobbler. Must be on the Omakasea Naughty or Nice list to participate
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function mint(
bytes32 messageHash,
bytes calldata signature
) external {
// Free mint
require(totalSupply + 1 <= genesisMaxSupply, "Genesis max supply reached");
// must not be paused
require(!paused, "Must not be paused");
// Naughty/nice list checks
// The message should contain the msg sender, this contract address, function name sig and sig nonce
require(
hashMessage(
msg.sender,
address(this),
bytes4(abi.encodePacked("mint")),
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
_mint(msg.sender, totalSupply);
unchecked {
totalSupply++;
signatureNonce[msg.sender]++;
}
}
/// @notice Feed, Groom or Sleep - any action invoked while the gobbler is alive
/// @param action The action to invoke
/// @param tokenID The Gobbler tokenID to use
/// @param amount The of times the action should be invoked
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function actionAlive(
Action action,
uint256 tokenID,
uint8 amount,
bytes32 messageHash,
bytes calldata signature
) external payable onlyTokenOwner(tokenID, msg.sender) {
// Checks required, valid message hash and signature only produced if health is above 0%
// This smart contract has no notion of health, which is entirely managed off chain
// The message should contain the msg sender, this contract address, function name sig and sig nonce
require(
hashMessage(
msg.sender,
address(this),
bytes4(abi.encodePacked("actionAlive")),
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
if (action == Action.Feed) {
require(msg.value == feedPrice * amount, "Not enough ETH Sent");
emit Feed(tokenID, amount, msg.sender);
} else if (action == Action.Groom) {
require(msg.value == groomPrice * amount, "Not enough ETH Sent");
emit Groom(tokenID, amount, msg.sender);
} else if (action == Action.Sleep) {
require(msg.value == sleepPrice, "Not enough ETH Sent");
emit Sleep(tokenID, msg.sender);
}
unchecked {
ETHGobbled[tokenID] += msg.value;
signatureNonce[msg.sender]++;
}
}
/// @notice Bury a gobbler, sending it to the burn address, eliminating it from supply permanently. Only possible if health is at 0
/// @param tokenID The Gobbler tokenID to use
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function bury(
uint256 tokenID,
bytes32 messageHash,
bytes calldata signature
) external {
// Checks required, valid message hash and signature only produced if health is 0%
// This smart contract has no notion of health, which is entirely managed off chain
// The message should contain the msg sender, this contract address, function name sig, the tokenID and sig nonce
require(
hashMessageBury(
msg.sender,
address(this),
bytes4(abi.encodePacked("bury")),
tokenID,
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
address currentOwner = ownerOf(tokenID);
/*-----------ERC721-----------*/
// custom burn logic, sends to DEAD address
require(currentOwner != address(0), "NOT_MINTED");
unchecked {
_balanceOf[currentOwner]--;
_balanceOf[burnAddress]++;
}
_ownerOf[tokenID] = burnAddress;
delete getApproved[tokenID];
emit Transfer(currentOwner, burnAddress, tokenID);
/*-----------ERC721-----------*/
emit Bury(tokenID, msg.sender);
unchecked {
signatureNonce[msg.sender]++;
}
}
/// @notice Current gobbler divides into another one
/// @param tokenID The Gobbler tokenID to use
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
/// @dev should not be invoked until all 2000 genesis are minted
function mitosis(
uint256 tokenID,
bytes32 messageHash,
bytes calldata signature
) external onlyTokenOwner(tokenID, msg.sender) {
// Checks required, valid message hash and signature only produced if certain actions have been called a number of times
// Action counts are tracked by emitted events
// The message should contain the msg sender, this contract address, function name and sig nonce
require(
hashMessage(
msg.sender,
address(this),
bytes4(abi.encodePacked("mitosis")),
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
// token IDs for mitosis gobblers start at ID 2000
uint newTokenID = genesisMaxSupply + mitosisSupply;
_mint(msg.sender, newTokenID);
emit Mitosis(
tokenID,
newTokenID,
msg.sender
);
unchecked {
mitosisSupply++;
totalSupply++;
signatureNonce[msg.sender]++;
}
}
/// @notice Configure NFT traits for the gobbler
/// @param tokenID The Gobbler tokenID to use
/// @param traitIDs The token IDs of traits to equip (packed into one uint256)
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function configureTraits(
uint256 tokenID,
uint256 traitIDs,
bytes32 messageHash,
bytes calldata signature
) external onlyTokenOwner(tokenID, msg.sender) {
// checks required, cannot casually call this function from etherscan
// The message should contain the msg sender, this contract address, function name, trait IDs and sig nonce
require(
hashMessageConfigureTraits(
msg.sender,
address(this),
bytes4(abi.encodePacked("configureTraits")),
traitIDs,
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
equippedTraits[tokenID] = traitIDs;
emit ConfigureTraits(
tokenID,
traitIDs
);
unchecked {
signatureNonce[msg.sender]++;
}
}
/// @notice Unlock a new NFT trait
/// @param tokenID The Gobbler tokenID to use
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function unlockTrait(
uint256 tokenID,
bytes32 messageHash,
bytes calldata signature
) external onlyTokenOwner(tokenID, msg.sender) {
// checks required, valid signature and message hash only produced if certain actions have been called a number of times
// The message should contain the msg sender, this contract address, function name sig, and sig nonce
require(
hashMessage(
msg.sender,
address(this),
bytes4(abi.encodePacked("unlockTrait")),
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
uint newTraitTokenID = gobDrops.totalSupply();
gobDrops.mint(msg.sender);
emit TraitUnlocked(
tokenID,
newTraitTokenID,
msg.sender
);
unchecked {
signatureNonce[msg.sender]++;
}
}
/// @notice Gobble (steal) another gobbler. Must be the Gobbler Gobbler
/// @param gobblerGobblerTokenID The token ID of the current Gobbler Gobbler
/// @param victimTokenID The token ID of the Gobbler to be gobbled
/// @param newGobblerGobbler The token ID of the new Gobbler Gobbler
/// @param messageHash Hash of message created by the backend
/// @param signature Signature of message hash signed by the ETH Gobblers admin address
function gobbleGobbler(
uint256 gobblerGobblerTokenID,
uint256 victimTokenID,
uint256 newGobblerGobbler,
bytes32 messageHash,
bytes calldata signature
) external payable onlyTokenOwner(gobblerGobblerTokenID, msg.sender) {
require(currentGobblerGobbler == gobblerGobblerTokenID, "Must be the Gobbler Gobbler!");
require(msg.value == gobbleGobblerPrice, "Not enough ETH sent!");
require(
hashMessageGobbleGobbler(
msg.sender,
address(this),
bytes4(abi.encodePacked("gobbleGobbler")),
newGobblerGobbler,
signatureNonce[msg.sender]
) == messageHash, "Wrong message hash!"
);
require(verifyAddressSigner(messageHash, signature), "Invalid address signer");
address currentOwnerOfVictim = ownerOf(victimTokenID);
/*-----------ERC721-----------*/
unchecked {
_balanceOf[currentOwnerOfVictim]--;
_balanceOf[msg.sender]++;
}
_ownerOf[victimTokenID] = msg.sender;
delete getApproved[victimTokenID];
emit Transfer(currentOwnerOfVictim, msg.sender, victimTokenID);
/*-----------ERC721-----------*/
emit GobblerGobbled(
gobblerGobblerTokenID,
victimTokenID,
newGobblerGobbler
);
currentGobblerGobbler = newGobblerGobbler;
unchecked {
signatureNonce[msg.sender]++;
}
}
/*------------------------------------------------------*/
/* ADMIN
/*------------------------------------------------------*/
function changeFeedPrice(uint256 price) external onlyOwner {
feedPrice = price;
}
function changeGroomPrice(uint256 price) external onlyOwner {
groomPrice = price;
}
function changeSleepPrice(uint256 price) external onlyOwner {
sleepPrice = price;
}
function changeGobbleGobblerPrice(uint256 price) external onlyOwner {
gobbleGobblerPrice = price;
}
function setGobblerGobbler(uint256 tokenID) external onlyOwner {
currentGobblerGobbler = tokenID;
}
function changeBaseURI(string calldata newBaseURI) external onlyOwner {
baseURI = newBaseURI;
}
function changeSigner(address signer) external onlyOwner {
signerAddress = signer;
}
function flipPaused() external onlyOwner {
paused = !paused;
}
/*------------------------------------------------------*/
/* READ ONLY
/*------------------------------------------------------*/
function getTraitConfiguration(uint256 tokenID) external view returns (
uint32 wings,
uint32 sidekick,
uint32 food,
uint32 accessory,
uint32 weather,
uint32 cushion,
uint32 inflight,
uint32 freeSlot
) {
uint256 currentTraits = equippedTraits[tokenID];
assembly {
wings := and(shr(0xE0, currentTraits), 0xffffffff)
sidekick := and(shr(0xC0, currentTraits), 0xffffffff)
food := and(shr(0xA0, currentTraits), 0xffffffff)
accessory := and(shr(0x80, currentTraits), 0xffffffff)
weather := and(shr(0x60, currentTraits), 0xffffffff)
cushion := and(shr(0x40, currentTraits), 0xffffffff)
inflight := and(shr(0x20, currentTraits), 0xffffffff)
freeSlot := and(currentTraits, 0xffffffff)
}
}
function tokenURI(uint tokenID) public view override returns (string memory) {
require(tokenID < totalSupply, "This token does not exist");
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenID.toString())) : "";
}
function verifyAddressSigner(bytes32 messageHash, bytes calldata signature) private view returns (bool) {
address recovery = messageHash.toEthSignedMessageHash().recover(signature);
return signerAddress == recovery;
}
function hashMessage(address sender, address thisContract, bytes4 functionNameSig, uint256 nonce) public pure returns (bytes32) {
return keccak256(abi.encodePacked(sender, thisContract, functionNameSig, nonce));
}
// special hash message for trait configuration function
function hashMessageConfigureTraits(address sender, address thisContract, bytes4 functionNameSig, uint256 traitIDs, uint256 nonce) public pure returns (bytes32) {
return keccak256(abi.encodePacked(sender, thisContract, functionNameSig, traitIDs, nonce));
}
// special hash message for bury function
function hashMessageBury(address sender, address thisContract, bytes4 functionNameSig, uint256 tokenID, uint256 nonce) public pure returns (bytes32) {
return keccak256(abi.encodePacked(sender, thisContract, functionNameSig, tokenID, nonce));
}
// special hash message for gobble gobbler function
function hashMessageGobbleGobbler(address sender, address thisContract, bytes4 functionNameSig, uint256 newGobblerGobbler, uint256 nonce) public pure returns (bytes32) {
return keccak256(abi.encodePacked(sender, thisContract, functionNameSig, newGobblerGobbler, nonce));
}
/*------------------------------------------------------*/
/* WITHDRAW
/*------------------------------------------------------*/
function withdraw() external onlyOwner {
assembly {
let result := call(0, caller(), selfbalance(), 0, 0, 0, 0)
switch result
case 0 { revert(0, 0) }
default { return(0, 0) }
}
}
}
GobDrops.sol 57 lines
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
import "solmate/tokens/ERC721.sol";
import "solmate/auth/Owned.sol";
import "solady/utils/LibString.sol";
/// @title GobDrops
/// @author EtDu
contract GobDrops is ERC721, Owned {
using LibString for uint256;
uint256 public totalSupply = 0;
string public baseURI;
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin, "Must be admin!");
_;
}
constructor(address adminAddress) ERC721("GOB DROPS", "DROP") Owned(msg.sender) {
admin = adminAddress;
}
/// @notice Mint new trait. Only callable by ETH Gobblers contract on trait unlock.
/// @param user User address to claim the NFT trait.
function mint(address user) external onlyOwner {
_mint(user, totalSupply);
unchecked {
totalSupply++;
}
}
function tokenURI(uint tokenID) public view override returns (string memory) {
require(tokenID < totalSupply, "This token does not exist");
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenID.toString())) : "";
}
function changeBaseURI(string calldata newBaseURI) external onlyAdmin {
baseURI = newBaseURI;
}
function changeAdmin(address newAdmin) external onlyAdmin {
admin = newAdmin;
}
function withdraw() external onlyAdmin {
assembly {
let result := call(0, caller(), selfbalance(), 0, 0, 0, 0)
switch result
case 0 { revert(0, 0) }
default { return(0, 0) }
}
}
}
ECDSA.sol 206 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Gas optimized ECDSA wrapper.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ECDSA.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ECDSA.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol)
library ECDSA {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The number which `s` must not exceed in order for
/// the signature to be non-malleable.
bytes32 private constant _MALLEABILITY_THRESHOLD =
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RECOVERY OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the `signature`.
///
/// This function does NOT accept EIP-2098 short form signatures.
/// Use `recover(bytes32 hash, bytes32 r, bytes32 vs)` for EIP-2098
/// short form signatures instead.
///
/// WARNING!
/// The `result` will be the zero address upon recovery failure.
/// As such, it is extremely important to ensure that the address which
/// the `result` is compared against is never zero.
function recover(bytes32 hash, bytes calldata signature)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
if eq(signature.length, 65) {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// Directly copy `r` and `s` from the calldata.
calldatacopy(0x40, signature.offset, 0x40)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(mload(0x60), _MALLEABILITY_THRESHOLD)) {
mstore(0x00, hash)
// Compute `v` and store it in the scratch space.
mstore(0x20, byte(0, calldataload(add(signature.offset, 0x40))))
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x40, // Start of output.
0x20 // Size of output.
)
)
// Restore the zero slot.
mstore(0x60, 0)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(sub(0x60, returndatasize()))
}
// Restore the free memory pointer.
mstore(0x40, m)
}
}
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the EIP-2098 short form signature defined by `r` and `vs`.
///
/// This function only accepts EIP-2098 short form signatures.
/// See: https://eips.ethereum.org/EIPS/eip-2098
///
/// To be honest, I do not recommend using EIP-2098 signatures
/// for simplicity, performance, and security reasons. Most if not
/// all clients support traditional non EIP-2098 signatures by default.
/// As such, this method is intentionally not fully inlined.
/// It is merely included for completeness.
///
/// WARNING!
/// The `result` will be the zero address upon recovery failure.
/// As such, it is extremely important to ensure that the address which
/// the `result` is compared against is never zero.
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal view returns (address result) {
uint8 v;
bytes32 s;
/// @solidity memory-safe-assembly
assembly {
s := shr(1, shl(1, vs))
v := add(shr(255, vs), 27)
}
result = recover(hash, v, r, s);
}
/// @dev Recovers the signer's address from a message digest `hash`,
/// and the signature defined by `v`, `r`, `s`.
///
/// WARNING!
/// The `result` will be the zero address upon recovery failure.
/// As such, it is extremely important to ensure that the address which
/// the `result` is compared against is never zero.
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
view
returns (address result)
{
/// @solidity memory-safe-assembly
assembly {
// Copy the free memory pointer so that we can restore it later.
let m := mload(0x40)
// If `s` in lower half order, such that the signature is not malleable.
if iszero(gt(s, _MALLEABILITY_THRESHOLD)) {
mstore(0x00, hash)
mstore(0x20, v)
mstore(0x40, r)
mstore(0x60, s)
pop(
staticcall(
gas(), // Amount of gas left for the transaction.
0x01, // Address of `ecrecover`.
0x00, // Start of input.
0x80, // Size of input.
0x40, // Start of output.
0x20 // Size of output.
)
)
// Restore the zero slot.
mstore(0x60, 0)
// `returndatasize()` will be `0x20` upon success, and `0x00` otherwise.
result := mload(sub(0x60, returndatasize()))
}
// Restore the free memory pointer.
mstore(0x40, m)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HASHING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns an Ethereum Signed Message, created from a `hash`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
// Store into scratch space for keccak256.
mstore(0x20, hash)
mstore(0x00, "\x00\x00\x00\x00\x19Ethereum Signed Message:\n32")
// 0x40 - 0x04 = 0x3c
result := keccak256(0x04, 0x3c)
}
}
/// @dev Returns an Ethereum Signed Message, created from `s`.
/// This produces a hash corresponding to the one signed with the
/// [`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign)
/// JSON-RPC method as part of EIP-191.
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32 result) {
assembly {
// We need at most 128 bytes for Ethereum signed message header.
// The max length of the ASCII reprenstation of a uint256 is 78 bytes.
// The length of "\x19Ethereum Signed Message:\n" is 26 bytes (i.e. 0x1a).
// The next multiple of 32 above 78 + 26 is 128 (i.e. 0x80).
// Instead of allocating, we temporarily copy the 128 bytes before the
// start of `s` data to some variables.
let m3 := mload(sub(s, 0x60))
let m2 := mload(sub(s, 0x40))
let m1 := mload(sub(s, 0x20))
// The length of `s` is in bytes.
let sLength := mload(s)
let ptr := add(s, 0x20)
// `end` marks the end of the memory which we will compute the keccak256 of.
let end := add(ptr, sLength)
// Convert the length of the bytes to ASCII decimal representation
// and store it into the memory.
for { let temp := sLength } 1 {} {
ptr := sub(ptr, 1)
mstore8(ptr, add(48, mod(temp, 10)))
temp := div(temp, 10)
if iszero(temp) { break }
}
// Copy the header over to the memory.
mstore(sub(ptr, 0x20), "\x00\x00\x00\x00\x00\x00\x19Ethereum Signed Message:\n")
// Compute the keccak256 of the memory.
result := keccak256(sub(ptr, 0x1a), sub(end, sub(ptr, 0x1a)))
// Restore the previous memory.
mstore(s, sLength)
mstore(sub(s, 0x20), m1)
mstore(sub(s, 0x40), m2)
mstore(sub(s, 0x60), m3)
}
}
}
LibString.sol 1014 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Library for converting numbers into strings and other string operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
library LibString {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The `length` of the output is too small to contain all the hex digits.
error HexLengthInsufficient();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The constant returned when the `search` is not found in the string.
uint256 internal constant NOT_FOUND = type(uint256).max;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* DECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the base 10 decimal representation of `value`.
function toString(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
// The maximum value of a uint256 contains 78 digits (1 byte per digit), but
// we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned.
// We will need 1 word for the trailing zeros padding, 1 word for the length,
// and 3 words for a maximum of 78 digits. Total: 5 * 0x20 = 0xa0.
let m := add(mload(0x40), 0xa0)
// Update the free memory pointer to allocate.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end of the memory to calculate the length later.
let end := str
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := sub(str, 1)
// Write the character to the pointer.
// The ASCII index of the '0' character is 48.
mstore8(str, add(48, mod(temp, 10)))
// Keep dividing `temp` until zero.
temp := div(temp, 10)
if iszero(temp) { break }
}
let length := sub(end, str)
// Move the pointer 32 bytes leftwards to make room for the length.
str := sub(str, 0x20)
// Store the length.
mstore(str, length)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HEXADECIMAL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2 + 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value, length);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`,
/// left-padded to an input length of `length` bytes.
/// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte,
/// giving a total length of `length * 2` bytes.
/// Reverts if `length` is too small for the output to contain all the digits.
function toHexStringNoPrefix(uint256 value, uint256 length)
internal
pure
returns (string memory str)
{
/// @solidity memory-safe-assembly
assembly {
let start := mload(0x40)
// We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes
// for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length.
// We add 0x20 to the total and round down to a multiple of 0x20.
// (0x20 + 0x20 + 0x02 + 0x20) = 0x62.
let m := add(start, and(add(shl(1, length), 0x62), not(0x1f)))
// Allocate the memory.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end to calculate the length later.
let end := str
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
let temp := value
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for {} 1 {} {
str := sub(str, 2)
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
length := sub(length, 1)
if iszero(length) { break }
}
if temp {
// Store the function selector of `HexLengthInsufficient()`.
mstore(0x00, 0x2194895a)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Compute the string's length.
let strLength := sub(end, str)
// Move the pointer and write the length.
str := sub(str, 0x20)
mstore(str, strLength)
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2 + 2` bytes.
function toHexString(uint256 value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
/// As address are 20 bytes long, the output will left-padded to have
/// a length of `20 * 2` bytes.
function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
let start := mload(0x40)
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x40 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0.
let m := add(start, 0xa0)
// Allocate the memory.
mstore(0x40, m)
// Assign the `str` to the end.
str := sub(m, 0x20)
// Zeroize the slot after the string.
mstore(str, 0)
// Cache the end to calculate the length later.
let end := str
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let temp := value } 1 {} {
str := sub(str, 2)
mstore8(add(str, 1), mload(and(temp, 15)))
mstore8(str, mload(and(shr(4, temp), 15)))
temp := shr(8, temp)
if iszero(temp) { break }
}
// Compute the string's length.
let strLength := sub(end, str)
// Move the pointer and write the length.
str := sub(str, 0x20)
mstore(str, strLength)
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte,
/// and the alphabets are capitalized conditionally according to
/// https://eips.ethereum.org/EIPS/eip-55
function toHexStringChecksumed(address value) internal pure returns (string memory str) {
str = toHexString(value);
/// @solidity memory-safe-assembly
assembly {
let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...`
let o := add(str, 0x22)
let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... `
let t := shl(240, 136) // `0b10001000 << 240`
for { let i := 0 } 1 {} {
mstore(add(i, i), mul(t, byte(i, hashed)))
i := add(i, 1)
if eq(i, 20) { break }
}
mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask)))))
o := add(o, 0x20)
mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask)))))
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte.
function toHexString(address value) internal pure returns (string memory str) {
str = toHexStringNoPrefix(value);
/// @solidity memory-safe-assembly
assembly {
let strLength := add(mload(str), 2) // Compute the length.
mstore(str, 0x3078) // Write the "0x" prefix.
str := sub(str, 2) // Move the pointer.
mstore(str, strLength) // Write the length.
}
}
/// @dev Returns the hexadecimal representation of `value`.
/// The output is encoded using 2 hexadecimal digits per byte.
function toHexStringNoPrefix(address value) internal pure returns (string memory str) {
/// @solidity memory-safe-assembly
assembly {
str := mload(0x40)
// Allocate the memory.
// We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length,
// 0x02 bytes for the prefix, and 0x28 bytes for the digits.
// The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80.
mstore(0x40, add(str, 0x80))
// Store "0123456789abcdef" in scratch space.
mstore(0x0f, 0x30313233343536373839616263646566)
str := add(str, 2)
mstore(str, 40)
let o := add(str, 0x20)
mstore(add(o, 40), 0)
value := shl(96, value)
// We write the string from rightmost digit to leftmost digit.
// The following is essentially a do-while loop that also handles the zero case.
for { let i := 0 } 1 {} {
let p := add(o, add(i, i))
let temp := byte(i, value)
mstore8(add(p, 1), mload(and(temp, 15)))
mstore8(p, mload(shr(4, temp)))
i := add(i, 1)
if eq(i, 20) { break }
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* RUNE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the number of UTF characters in the string.
function runeCount(string memory s) internal pure returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
if mload(s) {
mstore(0x00, div(not(0), 255))
mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506)
let o := add(s, 0x20)
let end := add(o, mload(s))
for { result := 1 } 1 { result := add(result, 1) } {
o := add(o, byte(0, mload(shr(250, mload(o)))))
if iszero(lt(o, end)) { break }
}
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* BYTE STRING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance and bytecode compactness, all indices of the following operations
// are byte (ASCII) offsets, not UTF character offsets.
/// @dev Returns `subject` all occurrences of `search` replaced with `replacement`.
function replace(string memory subject, string memory search, string memory replacement)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
let searchLength := mload(search)
let replacementLength := mload(replacement)
subject := add(subject, 0x20)
search := add(search, 0x20)
replacement := add(replacement, 0x20)
result := add(mload(0x40), 0x20)
let subjectEnd := add(subject, subjectLength)
if iszero(gt(searchLength, subjectLength)) {
let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1)
let h := 0
if iszero(lt(searchLength, 32)) { h := keccak256(search, searchLength) }
let m := shl(3, sub(32, and(searchLength, 31)))
let s := mload(search)
for {} 1 {} {
let t := mload(subject)
// Whether the first `searchLength % 32` bytes of
// `subject` and `search` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(subject, searchLength), h)) {
mstore(result, t)
result := add(result, 1)
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Copy the `replacement` one word at a time.
for { let o := 0 } 1 {} {
mstore(add(result, o), mload(add(replacement, o)))
o := add(o, 0x20)
if iszero(lt(o, replacementLength)) { break }
}
result := add(result, replacementLength)
subject := add(subject, searchLength)
if searchLength {
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
mstore(result, t)
result := add(result, 1)
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
}
let resultRemainder := result
result := add(mload(0x40), 0x20)
let k := add(sub(resultRemainder, result), sub(subjectEnd, subject))
// Copy the rest of the string one word at a time.
for {} lt(subject, subjectEnd) {} {
mstore(resultRemainder, mload(subject))
resultRemainder := add(resultRemainder, 0x20)
subject := add(subject, 0x20)
}
result := sub(result, 0x20)
// Zeroize the slot after the string.
let last := add(add(result, 0x20), k)
mstore(last, 0)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, and(add(last, 31), not(31)))
// Store the length of the result.
mstore(result, k)
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from left to right, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function indexOf(string memory subject, string memory search, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for { let subjectLength := mload(subject) } 1 {} {
if iszero(mload(search)) {
// `result = min(from, subjectLength)`.
result := xor(from, mul(xor(from, subjectLength), lt(subjectLength, from)))
break
}
let searchLength := mload(search)
let subjectStart := add(subject, 0x20)
result := not(0) // Initialize to `NOT_FOUND`.
subject := add(subjectStart, from)
let subjectSearchEnd := add(sub(add(subjectStart, subjectLength), searchLength), 1)
let m := shl(3, sub(32, and(searchLength, 31)))
let s := mload(add(search, 0x20))
if iszero(lt(subject, subjectSearchEnd)) { break }
if iszero(lt(searchLength, 32)) {
for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
if eq(keccak256(subject, searchLength), h) {
result := sub(subject, subjectStart)
break
}
}
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
break
}
for {} 1 {} {
if iszero(shr(m, xor(mload(subject), s))) {
result := sub(subject, subjectStart)
break
}
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from left to right.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function indexOf(string memory subject, string memory search)
internal
pure
returns (uint256 result)
{
result = indexOf(subject, search, 0);
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from right to left, starting from `from`.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function lastIndexOf(string memory subject, string memory search, uint256 from)
internal
pure
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
let searchLength := mload(search)
let fromMax := sub(mload(subject), searchLength)
if iszero(gt(fromMax, from)) { from := fromMax }
if iszero(mload(search)) {
result := from
break
}
result := not(0) // Initialize to `NOT_FOUND`.
let subjectSearchEnd := sub(add(subject, 0x20), 1)
subject := add(add(subject, 0x20), from)
if iszero(gt(subject, subjectSearchEnd)) { break }
// As this function is not too often used,
// we shall simply use keccak256 for smaller bytecode size.
for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} {
if eq(keccak256(subject, searchLength), h) {
result := sub(subject, add(subjectSearchEnd, 1))
break
}
subject := sub(subject, 1)
if iszero(gt(subject, subjectSearchEnd)) { break }
}
break
}
}
}
/// @dev Returns the byte index of the first location of `search` in `subject`,
/// searching from right to left.
/// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found.
function lastIndexOf(string memory subject, string memory search)
internal
pure
returns (uint256 result)
{
result = lastIndexOf(subject, search, uint256(int256(-1)));
}
/// @dev Returns whether `subject` starts with `search`.
function startsWith(string memory subject, string memory search)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLength := mload(search)
// Just using keccak256 directly is actually cheaper.
// forgefmt: disable-next-item
result := and(
iszero(gt(searchLength, mload(subject))),
eq(
keccak256(add(subject, 0x20), searchLength),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns whether `subject` ends with `search`.
function endsWith(string memory subject, string memory search)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
let searchLength := mload(search)
let subjectLength := mload(subject)
// Whether `search` is not longer than `subject`.
let withinRange := iszero(gt(searchLength, subjectLength))
// Just using keccak256 directly is actually cheaper.
// forgefmt: disable-next-item
result := and(
withinRange,
eq(
keccak256(
// `subject + 0x20 + max(subjectLength - searchLength, 0)`.
add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))),
searchLength
),
keccak256(add(search, 0x20), searchLength)
)
)
}
}
/// @dev Returns `subject` repeated `times`.
function repeat(string memory subject, uint256 times)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
if iszero(or(iszero(times), iszero(subjectLength))) {
subject := add(subject, 0x20)
result := mload(0x40)
let output := add(result, 0x20)
for {} 1 {} {
// Copy the `subject` one word at a time.
for { let o := 0 } 1 {} {
mstore(add(output, o), mload(add(subject, o)))
o := add(o, 0x20)
if iszero(lt(o, subjectLength)) { break }
}
output := add(output, subjectLength)
times := sub(times, 1)
if iszero(times) { break }
}
// Zeroize the slot after the string.
mstore(output, 0)
// Store the length.
let resultLength := sub(output, add(result, 0x20))
mstore(result, resultLength)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, add(result, and(add(resultLength, 63), not(31))))
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive).
/// `start` and `end` are byte offsets.
function slice(string memory subject, uint256 start, uint256 end)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
if iszero(gt(subjectLength, end)) { end := subjectLength }
if iszero(gt(subjectLength, start)) { start := subjectLength }
if lt(start, end) {
result := mload(0x40)
let resultLength := sub(end, start)
mstore(result, resultLength)
subject := add(subject, start)
let w := not(31)
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(resultLength, 31), w) } 1 {} {
mstore(add(result, o), mload(add(subject, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
// Zeroize the slot after the string.
mstore(add(add(result, 0x20), resultLength), 0)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, add(result, and(add(resultLength, 63), w)))
}
}
}
/// @dev Returns a copy of `subject` sliced from `start` to the end of the string.
/// `start` is a byte offset.
function slice(string memory subject, uint256 start)
internal
pure
returns (string memory result)
{
result = slice(subject, start, uint256(int256(-1)));
}
/// @dev Returns all the indices of `search` in `subject`.
/// The indices are byte offsets.
function indicesOf(string memory subject, string memory search)
internal
pure
returns (uint256[] memory result)
{
/// @solidity memory-safe-assembly
assembly {
let subjectLength := mload(subject)
let searchLength := mload(search)
if iszero(gt(searchLength, subjectLength)) {
subject := add(subject, 0x20)
search := add(search, 0x20)
result := add(mload(0x40), 0x20)
let subjectStart := subject
let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1)
let h := 0
if iszero(lt(searchLength, 32)) { h := keccak256(search, searchLength) }
let m := shl(3, sub(32, and(searchLength, 31)))
let s := mload(search)
for {} 1 {} {
let t := mload(subject)
// Whether the first `searchLength % 32` bytes of
// `subject` and `search` matches.
if iszero(shr(m, xor(t, s))) {
if h {
if iszero(eq(keccak256(subject, searchLength), h)) {
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
// Append to `result`.
mstore(result, sub(subject, subjectStart))
result := add(result, 0x20)
// Advance `subject` by `searchLength`.
subject := add(subject, searchLength)
if searchLength {
if iszero(lt(subject, subjectSearchEnd)) { break }
continue
}
}
subject := add(subject, 1)
if iszero(lt(subject, subjectSearchEnd)) { break }
}
let resultEnd := result
// Assign `result` to the free memory pointer.
result := mload(0x40)
// Store the length of `result`.
mstore(result, shr(5, sub(resultEnd, add(result, 0x20))))
// Allocate memory for result.
// We allocate one more word, so this array can be recycled for {split}.
mstore(0x40, add(resultEnd, 0x20))
}
}
}
/// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string.
function split(string memory subject, string memory delimiter)
internal
pure
returns (string[] memory result)
{
uint256[] memory indices = indicesOf(subject, delimiter);
/// @solidity memory-safe-assembly
assembly {
let w := not(31)
let indexPtr := add(indices, 0x20)
let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1)))
mstore(add(indicesEnd, w), mload(subject))
mstore(indices, add(mload(indices), 1))
let prevIndex := 0
for {} 1 {} {
let index := mload(indexPtr)
mstore(indexPtr, 0x60)
if iszero(eq(index, prevIndex)) {
let element := mload(0x40)
let elementLength := sub(index, prevIndex)
mstore(element, elementLength)
// Copy the `subject` one word at a time, backwards.
for { let o := and(add(elementLength, 31), w) } 1 {} {
mstore(add(element, o), mload(add(add(subject, prevIndex), o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
// Zeroize the slot after the string.
mstore(add(add(element, 0x20), elementLength), 0)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, add(element, and(add(elementLength, 63), w)))
// Store the `element` into the array.
mstore(indexPtr, element)
}
prevIndex := add(index, mload(delimiter))
indexPtr := add(indexPtr, 0x20)
if iszero(lt(indexPtr, indicesEnd)) { break }
}
result := indices
if iszero(mload(delimiter)) {
result := add(indices, 0x20)
mstore(result, sub(mload(indices), 2))
}
}
}
/// @dev Returns a concatenated string of `a` and `b`.
/// Cheaper than `string.concat()` and does not de-align the free memory pointer.
function concat(string memory a, string memory b)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let w := not(31)
result := mload(0x40)
let aLength := mload(a)
// Copy `a` one word at a time, backwards.
for { let o := and(add(mload(a), 32), w) } 1 {} {
mstore(add(result, o), mload(add(a, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let bLength := mload(b)
let output := add(result, mload(a))
// Copy `b` one word at a time, backwards.
for { let o := and(add(bLength, 32), w) } 1 {} {
mstore(add(output, o), mload(add(b, o)))
o := add(o, w) // `sub(o, 0x20)`.
if iszero(o) { break }
}
let totalLength := add(aLength, bLength)
let last := add(add(result, 0x20), totalLength)
// Zeroize the slot after the string.
mstore(last, 0)
// Stores the length.
mstore(result, totalLength)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, and(add(last, 31), w))
}
}
/// @dev Returns a copy of the string in either lowercase or UPPERCASE.
function toCase(string memory subject, bool toUpper)
internal
pure
returns (string memory result)
{
/// @solidity memory-safe-assembly
assembly {
let length := mload(subject)
if length {
result := add(mload(0x40), 0x20)
subject := add(subject, 1)
let flags := shl(add(70, shl(5, toUpper)), 67108863)
let w := not(0)
for { let o := length } 1 {} {
o := add(o, w)
let b := and(0xff, mload(add(subject, o)))
mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20)))
if iszero(o) { break }
}
// Restore the result.
result := mload(0x40)
// Stores the string length.
mstore(result, length)
// Zeroize the slot after the string.
let last := add(add(result, 0x20), length)
mstore(last, 0)
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, and(add(last, 31), not(31)))
}
}
}
/// @dev Returns a lowercased copy of the string.
function lower(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, false);
}
/// @dev Returns an UPPERCASED copy of the string.
function upper(string memory subject) internal pure returns (string memory result) {
result = toCase(subject, true);
}
/// @dev Escapes the string to be used within HTML tags.
function escapeHTML(string memory s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
for {
let end := add(s, mload(s))
result := add(mload(0x40), 0x20)
// Store the bytes of the packed offsets and strides into the scratch space.
// `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6.
mstore(0x1f, 0x900094)
mstore(0x08, 0xc0000000a6ab)
// Store ""&'<>" into the scratch space.
mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b))
} iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
// Not in `["\"","'","&","<",">"]`.
if iszero(and(shl(c, 1), 0x500000c400000000)) {
mstore8(result, c)
result := add(result, 1)
continue
}
let t := shr(248, mload(c))
mstore(result, mload(and(t, 31)))
result := add(result, shr(5, t))
}
let last := result
// Zeroize the slot after the string.
mstore(last, 0)
// Restore the result to the start of the free memory.
result := mload(0x40)
// Store the length of the result.
mstore(result, sub(last, add(result, 0x20)))
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, and(add(last, 31), not(31)))
}
}
/// @dev Escapes the string to be used within double-quotes in a JSON.
function escapeJSON(string memory s) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
for {
let end := add(s, mload(s))
result := add(mload(0x40), 0x20)
// Store "\\u0000" in scratch space.
// Store "0123456789abcdef" in scratch space.
// Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`.
// into the scratch space.
mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672)
// Bitmask for detecting `["\"","\\"]`.
let e := or(shl(0x22, 1), shl(0x5c, 1))
} iszero(eq(s, end)) {} {
s := add(s, 1)
let c := and(mload(s), 0xff)
if iszero(lt(c, 0x20)) {
if iszero(and(shl(c, 1), e)) {
// Not in `["\"","\\"]`.
mstore8(result, c)
result := add(result, 1)
continue
}
mstore8(result, 0x5c) // "\\".
mstore8(add(result, 1), c)
result := add(result, 2)
continue
}
if iszero(and(shl(c, 1), 0x3700)) {
// Not in `["\b","\t","\n","\f","\d"]`.
mstore8(0x1d, mload(shr(4, c))) // Hex value.
mstore8(0x1e, mload(and(c, 15))) // Hex value.
mstore(result, mload(0x19)) // "\\u00XX".
result := add(result, 6)
continue
}
mstore8(result, 0x5c) // "\\".
mstore8(add(result, 1), mload(add(c, 8)))
result := add(result, 2)
}
let last := result
// Zeroize the slot after the string.
mstore(last, 0)
// Restore the result to the start of the free memory.
result := mload(0x40)
// Store the length of the result.
mstore(result, sub(last, add(result, 0x20)))
// Allocate memory for the length and the bytes,
// rounded up to a multiple of 32.
mstore(0x40, and(add(last, 31), not(31)))
}
}
/// @dev Returns whether `a` equals `b`.
function eq(string memory a, string memory b) internal pure returns (bool result) {
assembly {
result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b)))
}
}
/// @dev Packs a single string with its length into a single word.
/// Returns `bytes32(0)` if the length is zero or greater than 31.
function packOne(string memory a) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
// We don't need to zero right pad the string,
// since this is our own custom non-standard packing scheme.
result :=
mul(
// Load the length and the bytes.
mload(add(a, 0x1f)),
// `length != 0 && length < 32`. Abuses underflow.
// Assumes that the length is valid and within the block gas limit.
lt(sub(mload(a), 1), 0x1f)
)
}
}
/// @dev Unpacks a string packed using {packOne}.
/// Returns the empty string if `packed` is `bytes32(0)`.
/// If `packed` is not an output of {packOne}, the output behaviour is undefined.
function unpackOne(bytes32 packed) internal pure returns (string memory result) {
/// @solidity memory-safe-assembly
assembly {
// Grab the free memory pointer.
result := mload(0x40)
// Allocate 2 words (1 for the length, 1 for the bytes).
mstore(0x40, add(result, 0x40))
// Zeroize the length slot.
mstore(result, 0)
// Store the length and bytes.
mstore(add(result, 0x1f), packed)
// Right pad with zeroes.
mstore(add(add(result, 0x20), mload(result)), 0)
}
}
/// @dev Packs two strings with their lengths into a single word.
/// Returns `bytes32(0)` if combined length is zero or greater than 30.
function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) {
/// @solidity memory-safe-assembly
assembly {
let aLength := mload(a)
// We don't need to zero right pad the strings,
// since this is our own custom non-standard packing scheme.
result :=
mul(
// Load the length and the bytes of `a` and `b`.
or(
shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))),
mload(sub(add(b, 0x1e), aLength))
),
// `totalLength != 0 && totalLength < 31`. Abuses underflow.
// Assumes that the lengths are valid and within the block gas limit.
lt(sub(add(aLength, mload(b)), 1), 0x1e)
)
}
}
/// @dev Unpacks strings packed using {packTwo}.
/// Returns the empty strings if `packed` is `bytes32(0)`.
/// If `packed` is not an output of {packTwo}, the output behaviour is undefined.
function unpackTwo(bytes32 packed)
internal
pure
returns (string memory resultA, string memory resultB)
{
/// @solidity memory-safe-assembly
assembly {
// Grab the free memory pointer.
resultA := mload(0x40)
resultB := add(resultA, 0x40)
// Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words.
mstore(0x40, add(resultB, 0x40))
// Zeroize the length slots.
mstore(resultA, 0)
mstore(resultB, 0)
// Store the lengths and bytes.
mstore(add(resultA, 0x1f), packed)
mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA))))
// Right pad with zeroes.
mstore(add(add(resultA, 0x20), mload(resultA)), 0)
mstore(add(add(resultB, 0x20), mload(resultB)), 0)
}
}
/// @dev Directly returns `a` without copying.
function directReturn(string memory a) internal pure {
assembly {
// Assumes that the string does not start from the scratch space.
let retStart := sub(a, 0x20)
let retSize := add(mload(a), 0x40)
// Right pad with zeroes. Just in case the string is produced
// by a method that doesn't zero right pad.
mstore(add(retStart, retSize), 0)
// Store the return offset.
mstore(retStart, 0x20)
// End the transaction, returning the string.
return(retStart, retSize)
}
}
}
Read Contract
ETHGobbled 0x2ed73d68 → uint256
balanceOf 0x70a08231 → uint256
baseURI 0x6c0360eb → string
currentGobblerGobbler 0x152d92da → uint256
equippedTraits 0x77f7a5cd → uint256
feedPrice 0xa04eecae → uint256
getApproved 0x081812fc → address
getTraitConfiguration 0x8d7ba8ba → uint32, uint32, uint32, uint32, uint32, uint32, uint32, uint32
gobDrops 0x07de128e → address
gobbleGobblerPrice 0xf12615d6 → uint256
groomPrice 0x3a5a2995 → uint256
hashMessage 0x354cfe43 → bytes32
hashMessageBury 0x4db8f13c → bytes32
hashMessageConfigureTraits 0x5c9c01d1 → bytes32
hashMessageGobbleGobbler 0xa78c2965 → bytes32
isApprovedForAll 0xe985e9c5 → bool
mitosisSupply 0xd2d1cd4f → uint256
name 0x06fdde03 → string
owner 0x8da5cb5b → address
ownerOf 0x6352211e → address
paused 0x5c975abb → bool
signatureNonce 0x567e34ba → uint256
signerAddress 0x5b7633d0 → address
sleepPrice 0x6ff5dfd4 → uint256
supportsInterface 0x01ffc9a7 → bool
symbol 0x95d89b41 → string
tokenURI 0xc87b56dd → string
totalSupply 0x18160ddd → uint256
Write Contract 22 functions
These functions modify contract state and require a wallet transaction to execute.
actionAlive 0xadc4f2bf
uint8 action
uint256 tokenID
uint8 amount
bytes32 messageHash
bytes signature
approve 0x095ea7b3
address spender
uint256 id
bury 0x1e755382
uint256 tokenID
bytes32 messageHash
bytes signature
changeBaseURI 0x39a0c6f9
string newBaseURI
changeFeedPrice 0x1cdfffa4
uint256 price
changeGobbleGobblerPrice 0xbac91c6a
uint256 price
changeGroomPrice 0x6e33a9fb
uint256 price
changeSigner 0xaad2b723
address signer
changeSleepPrice 0x29ec57a1
uint256 price
configureTraits 0xae487597
uint256 tokenID
uint256 traitIDs
bytes32 messageHash
bytes signature
flipPaused 0x333171bb
No parameters
gobbleGobbler 0x700bfe00
uint256 gobblerGobblerTokenID
uint256 victimTokenID
uint256 newGobblerGobbler
bytes32 messageHash
bytes signature
mint 0xacd379cc
bytes32 messageHash
bytes signature
mitosis 0x78e0b760
uint256 tokenID
bytes32 messageHash
bytes signature
safeTransferFrom 0x42842e0e
address from
address to
uint256 id
safeTransferFrom 0xb88d4fde
address from
address to
uint256 id
bytes data
setApprovalForAll 0xa22cb465
address operator
bool approved
setGobblerGobbler 0x2b76f8ea
uint256 tokenID
transferFrom 0x23b872dd
address from
address to
uint256 id
transferOwnership 0xf2fde38b
address newOwner
unlockTrait 0xc6b4102d
uint256 tokenID
bytes32 messageHash
bytes signature
withdraw 0x3ccfd60b
No parameters
Recent Transactions
No transactions found for this address