Address Contract Verified
Address
0x5bF30F8487f99e6F3ca3B6BE8e151978958e5CD8
Balance
0.594978 ETH
Nonce
2
Code Size
13742 bytes
Creator
0xa964D6E8...0840 at tx 0x83bfc7fb...cbe417
Indexed Transactions
0
Contract Bytecode
13742 bytes
0x6080604052600436106102205760003560e01c80633644e5151161012e578063956c8327116100ab578063d3c9727c1161006f578063d3c9727c146106fa578063d505accf1461071a578063d7a2e4c91461073a578063dd62ed3e1461075a578063fceacc831461079257600080fd5b8063956c83271461066557806395d89b411461067c578063a035b1fe14610691578063a9059cbb146106a6578063c55dae63146106c657600080fd5b80634cfb998a116100f25780634cfb998a146105a45780635accf4a5146105b75780635fcbd285146105d757806370a082311461060b5780637ecebe001461063857600080fd5b80633644e5151461051357806340993b261461052857806343d726d61461053b57806347ccca02146105505780634cba3eca1461058457600080fd5b80631d989f83116101bc5780632a6f869d116101805780632a6f869d146104445780632e1a7d4d146104575780632eb4a7ab14610479578063313ce567146104ad57806335ec39d9146104f357600080fd5b80631d989f8314610390578063203ae565146103a3578063217c1d68146103b857806323b872dd14610404578063286605711461042457600080fd5b8063037d785b14610225578063052d7c001461025f57806306fdde0314610283578063095ea7b3146102a55780630a94914e146102d5578063150b7a02146102f5578063168585e51461033a57806318160ddd1461035a5780631ae74cdd14610370575b600080fd5b34801561023157600080fd5b50610245610240366004612981565b6107b4565b604080519283526020830191909152015b60405180910390f35b34801561026b57600080fd5b5061027560065481565b604051908152602001610256565b34801561028f57600080fd5b50610298610915565b604051610256919061299a565b3480156102b157600080fd5b506102c56102c0366004612a00565b6109a3565b6040519015158152602001610256565b3480156102e157600080fd5b506102756102f0366004612a2c565b610a10565b34801561030157600080fd5b50610321610310366004612a58565b630a85bd0160e11b95945050505050565b6040516001600160e01b03199091168152602001610256565b34801561034657600080fd5b50610275610355366004612981565b610aa3565b34801561036657600080fd5b5061027560025481565b34801561037c57600080fd5b5061024561038b366004612af6565b610aee565b61027561039e366004612b28565b610d08565b3480156103af57600080fd5b5061027561116f565b3480156103c457600080fd5b506103ec7f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba084081565b6040516001600160a01b039091168152602001610256565b34801561041057600080fd5b506102c561041f366004612b6b565b61117e565b34801561043057600080fd5b5061027561043f366004612c0c565b61125e565b610275610452366004612c5f565b6113b3565b34801561046357600080fd5b50610477610472366004612981565b6113e8565b005b34801561048557600080fd5b506102757f000000000000000000000000000000000000000000000000000000000000000081565b3480156104b957600080fd5b506104e17f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff9091168152602001610256565b3480156104ff57600080fd5b5061027561050e366004612caf565b611604565b34801561051f57600080fd5b50610275611779565b610275610536366004612a2c565b6117cf565b34801561054757600080fd5b50610477611968565b34801561055c57600080fd5b506103ec7f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf681565b34801561059057600080fd5b5061024561059f366004612d48565b611b5f565b6102756105b2366004612db7565b611b9b565b3480156105c357600080fd5b506102756105d2366004612e87565b611bcd565b3480156105e357600080fd5b506103ec7f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e468681565b34801561061757600080fd5b50610275610626366004612f33565b60036020526000908152604090205481565b34801561064457600080fd5b50610275610653366004612f33565b60056020526000908152604090205481565b34801561067157600080fd5b5061027562093a8081565b34801561068857600080fd5b50610298611bf9565b34801561069d57600080fd5b50610275611c06565b3480156106b257600080fd5b506102c56106c1366004612a00565b611d15565b3480156106d257600080fd5b506103ec7f000000000000000000000000000000000000000000000000000000000000000081565b34801561070657600080fd5b50610275610715366004612a2c565b611d7b565b34801561072657600080fd5b50610477610735366004612f5f565b611ea9565b34801561074657600080fd5b50610275610755366004612981565b6120e7565b34801561076657600080fd5b50610275610775366004612fd0565b600460209081526000928352604080842090915290825290205481565b34801561079e57600080fd5b5030600090815260036020526040902054610275565b60008060007f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e46866001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610817573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061083b9190613009565b90506000818561084961116f565b6108539190613038565b61085d9190613065565b9050600082866108793060009081526003602052604090205490565b6108839190613038565b61088d9190613065565b905060008361089d886001613079565b306000908152600360205260409020546108b79190613038565b6108c19190613065565b90506108d5670de0b6b3a76400008361308c565b158015906108ee57506103e86108eb83836130a0565b11155b8015610900575066038d7ea4c6800084115b15610909578091505b50909590945092505050565b60008054610922906130b3565b80601f016020809104026020016040519081016040528092919081815260200182805461094e906130b3565b801561099b5780601f106109705761010080835404028352916020019161099b565b820191906000526020600020905b81548152906001019060200180831161097e57829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906109fe9086815260200190565b60405180910390a35060015b92915050565b60008115610a79576000610a2261116f565b610a2c8487613038565b610a369190613065565b90506000610a503060009081526003602052604090205490565b610a5a8587613038565b610a649190613065565b9050610a708282612141565b92505050610a9c565b620186a0610a8f610a8a8587613038565b612157565b610a9991906130a0565b90505b9392505050565b6000610a0a610ab4836103e8613038565b610abc61116f565b84610ad33060009081526003602052604090205490565b610add91906130a0565b610ae9906103de613038565b61223f565b600080821580610afe5750428310155b610b235760405162461bcd60e51b8152600401610b1a906130ed565b60405180910390fd5b610b2c866107b4565b909250905084821015610b815760405162461bcd60e51b815260206004820152601f60248201527f536c6970706167653a206261736520746f6b656e20616d6f756e74206f7574006044820152606401610b1a565b83811015610bd15760405162461bcd60e51b815260206004820152601e60248201527f536c6970706167653a206672616374696f6e616c20746f6b656e206f757400006044820152606401610b1a565b610bdc303383612265565b50604051632770a7eb60e21b81526001600160a01b037f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e46861690639dc29fac90610c2b9033908a9060040161310e565b600060405180830381600087803b158015610c4557600080fd5b505af1158015610c59573d6000803e3d6000fd5b50507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169150610c9c905057610c9733836122ea565b610cd0565b610cd06001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163384612340565b8581837fe26e44b49401e2c8d21cfcb6628ae520c5a95ebd3b087893b427af4fa3d4e0d960405160405180910390a494509492505050565b6000811580610d175750428210155b610d335760405162461bcd60e51b8152600401610b1a906130ed565b600087118015610d435750600086115b610d8f5760405162461bcd60e51b815260206004820152601a60248201527f496e70757420746f6b656e20616d6f756e74206973207a65726f0000000000006044820152606401610b1a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610dc5573415610dc9565b8634145b610de55760405162461bcd60e51b8152600401610b1a90613127565b60007f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e46866001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610e45573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e699190613009565b90508015610edb576000610e7b611c06565b9050858110158015610e8d5750848111155b610ed95760405162461bcd60e51b815260206004820152601d60248201527f536c6970706167653a207072696365206f7574206f6620626f756e64730000006044820152606401610b1a565b505b610ee6888883610a10565b915085821015610f385760405162461bcd60e51b815260206004820152601d60248201527f536c6970706167653a206c7020746f6b656e20616d6f756e74206f75740000006044820152606401610b1a565b610f43333089612265565b506040516340c10f1960e01b81526001600160a01b037f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e468616906340c10f1990610f92903390869060040161310e565b600060405180830381600087803b158015610fac57600080fd5b505af1158015610fc0573d6000803e3d6000fd5b50505050806000036110d1577f00000000000000000000000050e6d7cb9f55e31d65f3c4ba7514e235706e46866001600160a01b03166340c10f197f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba08406001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611059573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061107d9190613154565b620186a06040518363ffffffff1660e01b815260040161109e92919061310e565b600060405180830381600087803b1580156110b857600080fd5b505af11580156110cc573d6000803e3d6000fd5b505050505b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615611135576111356001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308b6123be565b8187897f2a31efc7e9b3f67e8cd108d5980ce3d6ac332ef092f12c5f1d748a7dfdf48f0660405160405180910390a4509695505050505050565b6000611179612448565b905090565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146111da576111b583826130a0565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906112029084906130a0565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716906000805160206135598339815191529061124b9087815260200190565b60405180910390a3506001949350505050565b6000611272670de0b6b3a764000084613038565b905061127e338261250b565b81156112ba5760006103e8611294836003613038565b61129e9190613065565b90506112ab333083612265565b506112b68183613079565b9150505b60005b8381101561136b577f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf66001600160a01b03166342842e0e303388888681811061130857611308613171565b905060200201356040518463ffffffff1660e01b815260040161132d93929190613187565b600060405180830381600087803b15801561134757600080fd5b505af115801561135b573d6000803e3d6000fd5b5050600190920191506112bd9050565b50838360405161137c9291906131ab565b604051908190038120907f49c6b1e1927bec3d67ff457e5fd52befa360549040211069e7c52c817ed23b4a90600090a29392505050565b60006113d16113ca670de0b6b3a764000086613038565b84846117cf565b90506113df8585600061125e565b50949350505050565b336001600160a01b03167f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba08406001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611450573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114749190613154565b6001600160a01b0316146114c05760405162461bcd60e51b81526020600482015260136024820152722bb4ba34323930bb9d103737ba1037bbb732b960691b6044820152606401610b1a565b60065460000361150b5760405162461bcd60e51b815260206004820152601660248201527515da5d1a191c985dc81b9bdd081a5b9a5d1a585d195960521b6044820152606401610b1a565b6006544210156115545760405162461bcd60e51b8152602060048201526014602482015273139bdd081dda5d1a191c985dd8589b19481e595d60621b6044820152606401610b1a565b604051632142170760e11b81526001600160a01b037f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf616906342842e0e906115a490309033908690600401613187565b600060405180830381600087803b1580156115be57600080fd5b505af11580156115d2573d6000803e3d6000fd5b50506040518392507f5b6b431d4476a211bb7d41c20d1aab9ae2321deee0d20be3d9fc9b1093fa6e3d9150600090a250565b60006006546000146116475760405162461bcd60e51b815260206004820152600c60248201526b15dc985c0e8818db1bdcd95960a21b6044820152606401610b1a565b61165387878787612575565b61165f878785856126a2565b611671670de0b6b3a764000087613038565b905061167d33826127c7565b60005b8681101561172e577f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf66001600160a01b03166342842e0e33308b8b868181106116cb576116cb613171565b905060200201356040518463ffffffff1660e01b81526004016116f093929190613187565b600060405180830381600087803b15801561170a57600080fd5b505af115801561171e573d6000803e3d6000fd5b5050600190920191506116809050565b50868660405161173f9291906131ab565b604051908190038120907f54d2ea0f504a95a03aa80f593be4914a1ebece60c3315b75040de03aa5df5c3a90600090a29695505050505050565b60007f000000000000000000000000000000000000000000000000000000000000000146146117aa57611179612819565b507f5d7c11470919c06ebbf986b560e0d29296f97f61327cedbff2791732f96cd1ae90565b60008115806117de5750428210155b6117fa5760405162461bcd60e51b8152600401610b1a906130ed565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615611830573415611834565b8234145b6118505760405162461bcd60e51b8152600401610b1a90613127565b61185984610aa3565b9050828111156118a15760405162461bcd60e51b815260206004820152601360248201527229b634b83830b3b29d1030b6b7bab73a1034b760691b6044820152606401610b1a565b6118ac303386612265565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166118ff5760006118e782856130a0565b905080156118f9576118f933826122ea565b50611934565b6119346001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163330846123be565b604051849082907f76911b5d8081a7d290dd15cdb0e39e9513ac7e8d1cce3275a7cf1380889abacc90600090a39392505050565b336001600160a01b03167f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba08406001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119f49190613154565b6001600160a01b031614611a3d5760405162461bcd60e51b815260206004820152601060248201526f21b637b9b29d103737ba1037bbb732b960811b6044820152606401610b1a565b611a4a62093a8042613079565b60065560405163282ff0a960e11b81526001600160a01b037f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba0840169063505fe15290611afd907f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf6907f0000000000000000000000000000000000000000000000000000000000000000907f000000000000000000000000000000000000000000000000000000000000000090600401613187565b600060405180830381600087803b158015611b1757600080fd5b505af1158015611b2b573d6000803e3d6000fd5b50506006546040519092507fbf67515a38ee520223d32c1266d52101c30d936ed1f3e436c8caeb0a43cb06bf9150600090a2565b600080611b7f8888611b79670de0b6b3a764000088613038565b89610aee565b9092509050611b8f85858561125e565b50965096945050505050565b600080611bac8c8c88888888611604565b9050611bbc8d828c8c8c8c610d08565b9d9c50505050505050505050505050565b600080611bde8a8a88888888611604565b9050611beb818989611d7b565b9a9950505050505050505050565b60018054610922906130b3565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615611cca577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611c96573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cba91906131d4565b611cc59060246131f1565b611ccd565b60125b60ff169050611ce83060009081526003602052604090205490565b611cf382600a6132ee565b611cfb612448565b611d059190613038565b611d0f9190613065565b91505090565b33600090815260036020526040812080548391908390611d369084906130a0565b90915550506001600160a01b03831660008181526003602052604090819020805485019055513390600080516020613559833981519152906109fe9086815260200190565b6000811580611d8a5750428210155b611da65760405162461bcd60e51b8152600401610b1a906130ed565b611daf846120e7565b905082811015611df85760405162461bcd60e51b815260206004820152601460248201527314db1a5c1c1859d94e88185b5bdd5b9d081bdd5d60621b6044820152606401610b1a565b611e03333086612265565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316611e4157611e3c33826122ea565b611e75565b611e756001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163383612340565b604051819085907f8ed05978f1a2453a08590ad9c67430e04d0b816de1318b575d1c28b7965ff5d590600090a39392505050565b42841015611ef35760405162461bcd60e51b815260206004820152601760248201527614115493525517d11150511312539157d1561412549151604a1b6044820152606401610b1a565b60006001611eff611779565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa15801561200b573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906120415750876001600160a01b0316816001600160a01b0316145b61207e5760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610b1a565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b6000806120f6836103de613038565b90508061210f3060009081526003602052604090205490565b61211b906103e8613038565b6121259190613079565b61212d61116f565b6121379083613038565b610a9c9190613065565b60008183106121505781610a9c565b5090919050565b60008160000361216957506000919050565b60006001612176846128b3565b901c6001901b9050600181848161218f5761218f61304f565b048201901c905060018184816121a7576121a761304f565b048201901c905060018184816121bf576121bf61304f565b048201901c905060018184816121d7576121d761304f565b048201901c905060018184816121ef576121ef61304f565b048201901c905060018184816122075761220761304f565b048201901c9050600181848161221f5761221f61304f565b048201901c9050610a9c818285816122395761223961304f565b04612141565b600082600019048411830215820261225657600080fd5b50910281810615159190040190565b6001600160a01b03831660009081526003602052604081208054839190839061228f9084906130a0565b90915550506001600160a01b0380841660008181526003602052604090819020805486019055519091861690600080516020613559833981519152906122d89086815260200190565b60405180910390a35060019392505050565b600080600080600085875af190508061233b5760405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b6044820152606401610b1a565b505050565b600060405163a9059cbb60e01b8152836004820152826024820152602060006044836000895af13d15601f3d11600160005114161716915050806123b85760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b6044820152606401610b1a565b50505050565b60006040516323b872dd60e01b81528460048201528360248201528260448201526020600060648360008a5af13d15601f3d11600160005114161716915050806124415760405162461bcd60e51b81526020600482015260146024820152731514905394d1915497d19493d357d1905253115160621b6044820152606401610b1a565b5050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615612501576040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156124dd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111799190613009565b61117934476130a0565b6001600160a01b038216600090815260036020526040812080548392906125339084906130a0565b90915550506002805482900390556040518181526000906001600160a01b03841690600080516020613559833981519152906020015b60405180910390a35050565b7f0000000000000000000000000000000000000000000000000000000000000000156123b85760005b838110156124415760006126538484848181106125bd576125bd613171565b90506020028101906125cf91906132fa565b7f000000000000000000000000000000000000000000000000000000000000000089898781811061260257612602613171565b9050602002013560405160200161261b91815260200190565b60408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120612947565b9050806126995760405162461bcd60e51b815260206004820152601460248201527324b73b30b634b21036b2b935b63290383937b7b360611b6044820152606401610b1a565b5060010161259e565b60007f000000000000000000000000a964d6e8d90e5cd12592a8ef2b1735dae9ba08406001600160a01b031663358602f76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612702573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127269190613154565b90506001600160a01b03811661273c57506123b8565b604051638a4fa05b60e01b81526001600160a01b03821690638a4fa05b90612790907f00000000000000000000000018385240632282bb38c2f6f279c9c2735da38cf69089908990899089906004016133b1565b60006040518083038186803b1580156127a857600080fd5b505afa1580156127bc573d6000803e3d6000fd5b505050505050505050565b80600260008282546127d99190613079565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481526000805160206135598339815191529101612569565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600060405161284b91906134b9565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600080608083901c156128c857608092831c92015b604083901c156128da57604092831c92015b602083901c156128ec57602092831c92015b601083901c156128fe57601092831c92015b600883901c1561291057600892831c92015b600483901c1561292257600492831c92015b600283901c1561293457600292831c92015b600183901c15610a0a5760010192915050565b60008315612979578360051b8501855b803580851160051b948552602094851852604060002093018181106129575750505b501492915050565b60006020828403121561299357600080fd5b5035919050565b600060208083528351808285015260005b818110156129c7578581018301518582016040015282016129ab565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160a01b03811681146129fd57600080fd5b50565b60008060408385031215612a1357600080fd5b8235612a1e816129e8565b946020939093013593505050565b600080600060608486031215612a4157600080fd5b505081359360208301359350604090920135919050565b600080600080600060808688031215612a7057600080fd5b8535612a7b816129e8565b94506020860135612a8b816129e8565b93506040860135925060608601356001600160401b0380821115612aae57600080fd5b818801915088601f830112612ac257600080fd5b813581811115612ad157600080fd5b896020828501011115612ae357600080fd5b9699959850939650602001949392505050565b60008060008060808587031215612b0c57600080fd5b5050823594602084013594506040840135936060013592509050565b60008060008060008060c08789031215612b4157600080fd5b505084359660208601359650604086013595606081013595506080810135945060a0013592509050565b600080600060608486031215612b8057600080fd5b8335612b8b816129e8565b92506020840135612b9b816129e8565b929592945050506040919091013590565b60008083601f840112612bbe57600080fd5b5081356001600160401b03811115612bd557600080fd5b6020830191508360208260051b8501011115612bf057600080fd5b9250929050565b80358015158114612c0757600080fd5b919050565b600080600060408486031215612c2157600080fd5b83356001600160401b03811115612c3757600080fd5b612c4386828701612bac565b9094509250612c56905060208501612bf7565b90509250925092565b60008060008060608587031215612c7557600080fd5b84356001600160401b03811115612c8b57600080fd5b612c9787828801612bac565b90989097506020870135966040013595509350505050565b60008060008060008060608789031215612cc857600080fd5b86356001600160401b0380821115612cdf57600080fd5b612ceb8a838b01612bac565b90985096506020890135915080821115612d0457600080fd5b612d108a838b01612bac565b90965094506040890135915080821115612d2957600080fd5b50612d3689828a01612bac565b979a9699509497509295939492505050565b60008060008060008060a08789031215612d6157600080fd5b86359550602087013594506040870135935060608701356001600160401b03811115612d8c57600080fd5b612d9889828a01612bac565b9094509250612dab905060808801612bf7565b90509295509295509295565b60008060008060008060008060008060006101008c8e031215612dd957600080fd5b8b359a506001600160401b038060208e01351115612df657600080fd5b612e068e60208f01358f01612bac565b909b50995060408d0135985060608d0135975060808d0135965060a08d0135955060c08d0135811015612e3857600080fd5b612e488e60c08f01358f01612bac565b909550935060e08d0135811015612e5e57600080fd5b50612e6f8d60e08e01358e01612bac565b81935080925050509295989b509295989b9093969950565b60008060008060008060008060a0898b031215612ea357600080fd5b88356001600160401b0380821115612eba57600080fd5b612ec68c838d01612bac565b909a50985060208b0135975060408b0135965060608b0135915080821115612eed57600080fd5b612ef98c838d01612bac565b909650945060808b0135915080821115612f1257600080fd5b50612f1f8b828c01612bac565b999c989b5096995094979396929594505050565b600060208284031215612f4557600080fd5b8135610a9c816129e8565b60ff811681146129fd57600080fd5b600080600080600080600060e0888a031215612f7a57600080fd5b8735612f85816129e8565b96506020880135612f95816129e8565b955060408801359450606088013593506080880135612fb381612f50565b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215612fe357600080fd5b8235612fee816129e8565b91506020830135612ffe816129e8565b809150509250929050565b60006020828403121561301b57600080fd5b5051919050565b634e487b7160e01b600052601160045260246000fd5b8082028115828204841417610a0a57610a0a613022565b634e487b7160e01b600052601260045260246000fd5b6000826130745761307461304f565b500490565b80820180821115610a0a57610a0a613022565b60008261309b5761309b61304f565b500690565b81810381811115610a0a57610a0a613022565b600181811c908216806130c757607f821691505b6020821081036130e757634e487b7160e01b600052602260045260246000fd5b50919050565b602080825260079082015266115e1c1a5c995960ca1b604082015260600190565b6001600160a01b03929092168252602082015260400190565b602080825260139082015272125b9d985b1a5908195d1a195c881a5b9c1d5d606a1b604082015260600190565b60006020828403121561316657600080fd5b8151610a9c816129e8565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b039384168152919092166020820152604081019190915260600190565b60006001600160fb1b038311156131c157600080fd5b8260051b80858437919091019392505050565b6000602082840312156131e657600080fd5b8151610a9c81612f50565b60ff8281168282160390811115610a0a57610a0a613022565b600181815b8085111561324557816000190482111561322b5761322b613022565b8085161561323857918102915b93841c939080029061320f565b509250929050565b60008261325c57506001610a0a565b8161326957506000610a0a565b816001811461327f5760028114613289576132a5565b6001915050610a0a565b60ff84111561329a5761329a613022565b50506001821b610a0a565b5060208310610133831016604e8410600b84101617156132c8575081810a610a0a565b6132d2838361320a565b80600019048211156132e6576132e6613022565b029392505050565b6000610a9c838361324d565b6000808335601e1984360301811261331157600080fd5b8301803591506001600160401b0382111561332b57600080fd5b6020019150600581901b3603821315612bf057600080fd5b6000808335601e1984360301811261335a57600080fd5b83016020810192503590506001600160401b0381111561337957600080fd5b803603821315612bf057600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6001600160a01b0386168152606060208083018290528282018690526000919060806001600160fb1b038811156133e757600080fd5b8760051b808a838801378501858103820160408088019190915282820188905260a0600589901b83018101919083018a6000805b8c8110156134a457868603609f190184528235368f9003607e19018112613440578283fd5b8e01803587526134528a820182613343565b8a8c8a01526134648b8a018284613388565b915050868201358789015261347b8c830183613343565b92508882038d8a015261348f828483613388565b9850505093890193509188019160010161341b565b50939f9e505050505050505050505050505050565b600080835481600182811c9150808316806134d557607f831692505b602080841082036134f457634e487b7160e01b86526022600452602486fd5b818015613508576001811461351d5761354a565b60ff198616895284151585028901965061354a565b60008a81526020902060005b868110156135425781548b820152908501908301613529565b505084890196505b50949897505050505050505056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa26469706673582212204e4c529a5c875975e84c12b14108bed56db98103983410feb612740935b341c664736f6c63430008110033
Verified Source Code Full Match
Compiler: v0.8.17+commit.8df45f5f
EVM: london
Optimization: Yes (100 runs)
Pair.sol 607 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "solmate/tokens/ERC20.sol";
import "solmate/tokens/ERC721.sol";
import "solmate/utils/MerkleProofLib.sol";
import "solmate/utils/SafeTransferLib.sol";
import "solmate/utils/FixedPointMathLib.sol";
import "openzeppelin/utils/math/Math.sol";
import "reservoir-oracle/ReservoirOracle.sol";
import "./LpToken.sol";
import "./Caviar.sol";
import "./StolenNftFilterOracle.sol";
/// @title Pair
/// @author out.eth (@outdoteth)
/// @notice A pair of an NFT and a base token that can be used to create and trade fractionalized NFTs.
contract Pair is ERC20, ERC721TokenReceiver {
using SafeTransferLib for address;
using SafeTransferLib for ERC20;
uint256 public constant CLOSE_GRACE_PERIOD = 7 days;
uint256 private constant ONE = 1e18;
uint256 private constant MINIMUM_LIQUIDITY = 100_000;
address public immutable nft;
address public immutable baseToken; // address(0) for ETH
bytes32 public immutable merkleRoot;
LpToken public immutable lpToken;
Caviar public immutable caviar;
uint256 public closeTimestamp;
event Add(uint256 indexed baseTokenAmount, uint256 indexed fractionalTokenAmount, uint256 indexed lpTokenAmount);
event Remove(uint256 indexed baseTokenAmount, uint256 indexed fractionalTokenAmount, uint256 indexed lpTokenAmount);
event Buy(uint256 indexed inputAmount, uint256 indexed outputAmount);
event Sell(uint256 indexed inputAmount, uint256 indexed outputAmount);
event Wrap(uint256[] indexed tokenIds);
event Unwrap(uint256[] indexed tokenIds);
event Close(uint256 indexed closeTimestamp);
event Withdraw(uint256 indexed tokenId);
constructor(
address _nft,
address _baseToken,
bytes32 _merkleRoot,
string memory pairSymbol,
string memory nftName,
string memory nftSymbol
) ERC20(string.concat(nftName, " fractional token"), string.concat("f", nftSymbol), 18) {
nft = _nft;
baseToken = _baseToken; // use address(0) for native ETH
merkleRoot = _merkleRoot;
lpToken = new LpToken(pairSymbol);
caviar = Caviar(msg.sender);
}
// ************************ //
// Core AMM logic //
// *********************** //
/// @notice Adds liquidity to the pair.
/// @param baseTokenAmount The amount of base tokens to add.
/// @param fractionalTokenAmount The amount of fractional tokens to add.
/// @param minLpTokenAmount The minimum amount of LP tokens to mint.
/// @param minPrice The minimum price that the pool should currently be at.
/// @param maxPrice The maximum price that the pool should currently be at.
/// @param deadline The deadline before the trade expires.
/// @return lpTokenAmount The amount of LP tokens minted.
function add(
uint256 baseTokenAmount,
uint256 fractionalTokenAmount,
uint256 minLpTokenAmount,
uint256 minPrice,
uint256 maxPrice,
uint256 deadline
) public payable returns (uint256 lpTokenAmount) {
// *** Checks *** //
// check that the trade has not expired
require(deadline == 0 || deadline >= block.timestamp, "Expired");
// check the token amount inputs are not zero
require(baseTokenAmount > 0 && fractionalTokenAmount > 0, "Input token amount is zero");
// check that correct eth input was sent - if the baseToken equals address(0) then native ETH is used
require(baseToken == address(0) ? msg.value == baseTokenAmount : msg.value == 0, "Invalid ether input");
uint256 lpTokenSupply = lpToken.totalSupply();
// check that the price is within the bounds if there is liquidity in the pool
if (lpTokenSupply != 0) {
uint256 _price = price();
require(_price >= minPrice && _price <= maxPrice, "Slippage: price out of bounds");
}
// calculate the lp token shares to mint
lpTokenAmount = addQuote(baseTokenAmount, fractionalTokenAmount, lpTokenSupply);
// check that the amount of lp tokens outputted is greater than the min amount
require(lpTokenAmount >= minLpTokenAmount, "Slippage: lp token amount out");
// *** Effects *** //
// transfer fractional tokens in
_transferFrom(msg.sender, address(this), fractionalTokenAmount);
// *** Interactions *** //
// mint lp tokens to sender
lpToken.mint(msg.sender, lpTokenAmount);
// transfer first MINIMUM_LIQUIDITY lp tokens to the owner
if (lpTokenSupply == 0) {
lpToken.mint(caviar.owner(), MINIMUM_LIQUIDITY);
}
// transfer base tokens in if the base token is not ETH
if (baseToken != address(0)) {
// transfer base tokens in
ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount);
}
emit Add(baseTokenAmount, fractionalTokenAmount, lpTokenAmount);
}
/// @notice Removes liquidity from the pair.
/// @param lpTokenAmount The amount of LP tokens to burn.
/// @param minBaseTokenOutputAmount The minimum amount of base tokens to receive.
/// @param minFractionalTokenOutputAmount The minimum amount of fractional tokens to receive.
/// @param deadline The deadline before the trade expires.
/// @return baseTokenOutputAmount The amount of base tokens received.
/// @return fractionalTokenOutputAmount The amount of fractional tokens received.
function remove(
uint256 lpTokenAmount,
uint256 minBaseTokenOutputAmount,
uint256 minFractionalTokenOutputAmount,
uint256 deadline
) public returns (uint256 baseTokenOutputAmount, uint256 fractionalTokenOutputAmount) {
// *** Checks *** //
// check that the trade has not expired
require(deadline == 0 || deadline >= block.timestamp, "Expired");
// calculate the output amounts
(baseTokenOutputAmount, fractionalTokenOutputAmount) = removeQuote(lpTokenAmount);
// check that the base token output amount is greater than the min amount
require(baseTokenOutputAmount >= minBaseTokenOutputAmount, "Slippage: base token amount out");
// check that the fractional token output amount is greater than the min amount
require(fractionalTokenOutputAmount >= minFractionalTokenOutputAmount, "Slippage: fractional token out");
// *** Effects *** //
// transfer fractional tokens to sender
_transferFrom(address(this), msg.sender, fractionalTokenOutputAmount);
// *** Interactions *** //
// burn lp tokens from sender
lpToken.burn(msg.sender, lpTokenAmount);
if (baseToken == address(0)) {
// if base token is native ETH then send ether to sender
msg.sender.safeTransferETH(baseTokenOutputAmount);
} else {
// transfer base tokens to sender
ERC20(baseToken).safeTransfer(msg.sender, baseTokenOutputAmount);
}
emit Remove(baseTokenOutputAmount, fractionalTokenOutputAmount, lpTokenAmount);
}
/// @notice Buys fractional tokens from the pair.
/// @param outputAmount The amount of fractional tokens to buy.
/// @param maxInputAmount The maximum amount of base tokens to spend.
/// @param deadline The deadline before the trade expires.
/// @return inputAmount The amount of base tokens spent.
function buy(uint256 outputAmount, uint256 maxInputAmount, uint256 deadline)
public
payable
returns (uint256 inputAmount)
{
// *** Checks *** //
// check that the trade has not expired
require(deadline == 0 || deadline >= block.timestamp, "Expired");
// check that correct eth input was sent - if the baseToken equals address(0) then native ETH is used
require(baseToken == address(0) ? msg.value == maxInputAmount : msg.value == 0, "Invalid ether input");
// calculate required input amount using xyk invariant
inputAmount = buyQuote(outputAmount);
// check that the required amount of base tokens is less than the max amount
require(inputAmount <= maxInputAmount, "Slippage: amount in");
// *** Effects *** //
// transfer fractional tokens to sender
_transferFrom(address(this), msg.sender, outputAmount);
// *** Interactions *** //
if (baseToken == address(0)) {
// refund surplus eth
uint256 refundAmount = maxInputAmount - inputAmount;
if (refundAmount > 0) msg.sender.safeTransferETH(refundAmount);
} else {
// transfer base tokens in
ERC20(baseToken).safeTransferFrom(msg.sender, address(this), inputAmount);
}
emit Buy(inputAmount, outputAmount);
}
/// @notice Sells fractional tokens to the pair.
/// @param inputAmount The amount of fractional tokens to sell.
/// @param deadline The deadline before the trade expires.
/// @param minOutputAmount The minimum amount of base tokens to receive.
/// @return outputAmount The amount of base tokens received.
function sell(uint256 inputAmount, uint256 minOutputAmount, uint256 deadline)
public
returns (uint256 outputAmount)
{
// *** Checks *** //
// check that the trade has not expired
require(deadline == 0 || deadline >= block.timestamp, "Expired");
// calculate output amount using xyk invariant
outputAmount = sellQuote(inputAmount);
// check that the outputted amount of fractional tokens is greater than the min amount
require(outputAmount >= minOutputAmount, "Slippage: amount out");
// *** Effects *** //
// transfer fractional tokens from sender
_transferFrom(msg.sender, address(this), inputAmount);
// *** Interactions *** //
if (baseToken == address(0)) {
// transfer ether out
msg.sender.safeTransferETH(outputAmount);
} else {
// transfer base tokens out
ERC20(baseToken).safeTransfer(msg.sender, outputAmount);
}
emit Sell(inputAmount, outputAmount);
}
// ******************** //
// Wrap logic //
// ******************** //
/// @notice Wraps NFTs into fractional tokens.
/// @param tokenIds The ids of the NFTs to wrap.
/// @param proofs The merkle proofs for the NFTs proving that they can be used in the pair.
/// @return fractionalTokenAmount The amount of fractional tokens minted.
function wrap(uint256[] calldata tokenIds, bytes32[][] calldata proofs, ReservoirOracle.Message[] calldata messages)
public
returns (uint256 fractionalTokenAmount)
{
// *** Checks *** //
// check that wrapping is not closed
require(closeTimestamp == 0, "Wrap: closed");
// check the tokens exist in the merkle root
_validateTokenIds(tokenIds, proofs);
// check that the tokens are not stolen with reservoir oracle
_validateTokensAreNotStolen(tokenIds, messages);
// *** Effects *** //
// mint fractional tokens to sender
fractionalTokenAmount = tokenIds.length * ONE;
_mint(msg.sender, fractionalTokenAmount);
// *** Interactions *** //
// transfer nfts from sender
for (uint256 i = 0; i < tokenIds.length;) {
ERC721(nft).safeTransferFrom(msg.sender, address(this), tokenIds[i]);
unchecked {
i++;
}
}
emit Wrap(tokenIds);
}
/// @notice Unwraps fractional tokens into NFTs.
/// @param tokenIds The ids of the NFTs to unwrap.
/// @param withFee Whether to pay a fee for unwrapping or not.
/// @return fractionalTokenAmount The amount of fractional tokens burned.
function unwrap(uint256[] calldata tokenIds, bool withFee) public returns (uint256 fractionalTokenAmount) {
// *** Effects *** //
// burn fractional tokens from sender
fractionalTokenAmount = tokenIds.length * ONE;
_burn(msg.sender, fractionalTokenAmount);
// Take the fee if withFee is true
if (withFee) {
// calculate fee
uint256 fee = fractionalTokenAmount * 3 / 1000;
// transfer fee from sender
_transferFrom(msg.sender, address(this), fee);
fractionalTokenAmount += fee;
}
// transfer nfts to sender
for (uint256 i = 0; i < tokenIds.length;) {
ERC721(nft).safeTransferFrom(address(this), msg.sender, tokenIds[i]);
unchecked {
i++;
}
}
emit Unwrap(tokenIds);
}
// *********************** //
// NFT AMM logic //
// *********************** //
/// @notice nftAdd Adds liquidity to the pair using NFTs.
/// @param baseTokenAmount The amount of base tokens to add.
/// @param tokenIds The ids of the NFTs to add.
/// @param minLpTokenAmount The minimum amount of lp tokens to receive.
/// @param minPrice The minimum price of the pair.
/// @param maxPrice The maximum price of the pair.
/// @param deadline The deadline for the transaction.
/// @param proofs The merkle proofs for the NFTs.
/// @return lpTokenAmount The amount of lp tokens minted.
function nftAdd(
uint256 baseTokenAmount,
uint256[] calldata tokenIds,
uint256 minLpTokenAmount,
uint256 minPrice,
uint256 maxPrice,
uint256 deadline,
bytes32[][] calldata proofs,
ReservoirOracle.Message[] calldata messages
) public payable returns (uint256 lpTokenAmount) {
// wrap the incoming NFTs into fractional tokens
uint256 fractionalTokenAmount = wrap(tokenIds, proofs, messages);
// add liquidity using the fractional tokens and base tokens
lpTokenAmount = add(baseTokenAmount, fractionalTokenAmount, minLpTokenAmount, minPrice, maxPrice, deadline);
}
/// @notice Removes liquidity from the pair using NFTs.
/// @param lpTokenAmount The amount of lp tokens to remove.
/// @param minBaseTokenOutputAmount The minimum amount of base tokens to receive.
/// @param deadline The deadline before the trade expires.
/// @param tokenIds The ids of the NFTs to remove.
/// @param withFee Whether to pay a fee for unwrapping or not.
/// @return baseTokenOutputAmount The amount of base tokens received.
/// @return fractionalTokenOutputAmount The amount of fractional tokens received.
function nftRemove(
uint256 lpTokenAmount,
uint256 minBaseTokenOutputAmount,
uint256 deadline,
uint256[] calldata tokenIds,
bool withFee
) public returns (uint256 baseTokenOutputAmount, uint256 fractionalTokenOutputAmount) {
// remove liquidity and send fractional tokens and base tokens to sender
(baseTokenOutputAmount, fractionalTokenOutputAmount) =
remove(lpTokenAmount, minBaseTokenOutputAmount, tokenIds.length * ONE, deadline);
// unwrap the fractional tokens into NFTs and send to sender
unwrap(tokenIds, withFee);
}
/// @notice Buys NFTs from the pair using base tokens.
/// @param tokenIds The ids of the NFTs to buy.
/// @param maxInputAmount The maximum amount of base tokens to spend.
/// @param deadline The deadline before the trade expires.
/// @return inputAmount The amount of base tokens spent.
function nftBuy(uint256[] calldata tokenIds, uint256 maxInputAmount, uint256 deadline)
public
payable
returns (uint256 inputAmount)
{
// buy fractional tokens using base tokens
inputAmount = buy(tokenIds.length * ONE, maxInputAmount, deadline);
// unwrap the fractional tokens into NFTs and send to sender
unwrap(tokenIds, false);
}
/// @notice Sells NFTs to the pair for base tokens.
/// @param tokenIds The ids of the NFTs to sell.
/// @param minOutputAmount The minimum amount of base tokens to receive.
/// @param deadline The deadline before the trade expires.
/// @param proofs The merkle proofs for the NFTs.
/// @return outputAmount The amount of base tokens received.
function nftSell(
uint256[] calldata tokenIds,
uint256 minOutputAmount,
uint256 deadline,
bytes32[][] calldata proofs,
ReservoirOracle.Message[] calldata messages
) public returns (uint256 outputAmount) {
// wrap the incoming NFTs into fractional tokens
uint256 inputAmount = wrap(tokenIds, proofs, messages);
// sell fractional tokens for base tokens
outputAmount = sell(inputAmount, minOutputAmount, deadline);
}
// ****************************** //
// Emergency exit logic //
// ****************************** //
/// @notice Closes the pair to new wraps.
/// @dev Can only be called by the caviar owner. This is used as an emergency exit in case
/// the caviar owner suspects that the pair has been compromised.
function close() public {
// check that the sender is the caviar owner
require(caviar.owner() == msg.sender, "Close: not owner");
// set the close timestamp with a grace period
closeTimestamp = block.timestamp + CLOSE_GRACE_PERIOD;
// remove the pair from the Caviar contract
caviar.destroy(nft, baseToken, merkleRoot);
emit Close(closeTimestamp);
}
/// @notice Withdraws a particular NFT from the pair.
/// @dev Can only be called by the caviar owner after the close grace period has passed. This
/// is used to auction off the NFTs in the pair in case NFTs get stuck due to liquidity
/// imbalances. Proceeds from the auction should be distributed pro rata to fractional
/// token holders. See documentation for more details.
function withdraw(uint256 tokenId) public {
// check that the sender is the caviar owner
require(caviar.owner() == msg.sender, "Withdraw: not owner");
// check that the close period has been set
require(closeTimestamp != 0, "Withdraw not initiated");
// check that the close grace period has passed
require(block.timestamp >= closeTimestamp, "Not withdrawable yet");
// transfer the nft to the caviar owner
ERC721(nft).safeTransferFrom(address(this), msg.sender, tokenId);
emit Withdraw(tokenId);
}
// ***************** //
// Getters //
// ***************** //
function baseTokenReserves() public view returns (uint256) {
return _baseTokenReserves();
}
function fractionalTokenReserves() public view returns (uint256) {
return balanceOf[address(this)];
}
/// @notice The current price of one fractional token in base tokens with 18 decimals of precision.
/// @dev Calculated by dividing the base token reserves by the fractional token reserves.
/// @return price The price of one fractional token in base tokens * 1e18.
function price() public view returns (uint256) {
uint256 exponent = baseToken == address(0) ? 18 : (36 - ERC20(baseToken).decimals());
return (_baseTokenReserves() * 10 ** exponent) / fractionalTokenReserves();
}
/// @notice The amount of base tokens required to buy a given amount of fractional tokens.
/// @dev Calculated using the xyk invariant and a 30bps fee.
/// @param outputAmount The amount of fractional tokens to buy.
/// @return inputAmount The amount of base tokens required.
function buyQuote(uint256 outputAmount) public view returns (uint256) {
return FixedPointMathLib.mulDivUp(
outputAmount * 1000, baseTokenReserves(), (fractionalTokenReserves() - outputAmount) * 990
);
}
/// @notice The amount of base tokens received for selling a given amount of fractional tokens.
/// @dev Calculated using the xyk invariant and a 30bps fee.
/// @param inputAmount The amount of fractional tokens to sell.
/// @return outputAmount The amount of base tokens received.
function sellQuote(uint256 inputAmount) public view returns (uint256) {
uint256 inputAmountWithFee = inputAmount * 990;
return (inputAmountWithFee * baseTokenReserves()) / ((fractionalTokenReserves() * 1000) + inputAmountWithFee);
}
/// @notice The amount of lp tokens received for adding a given amount of base tokens and fractional tokens.
/// @dev Calculated as a share of existing deposits. If there are no existing deposits, then initializes to
/// sqrt(baseTokenAmount * fractionalTokenAmount).
/// @param baseTokenAmount The amount of base tokens to add.
/// @param fractionalTokenAmount The amount of fractional tokens to add.
/// @return lpTokenAmount The amount of lp tokens received.
function addQuote(uint256 baseTokenAmount, uint256 fractionalTokenAmount, uint256 lpTokenSupply)
public
view
returns (uint256)
{
if (lpTokenSupply != 0) {
// calculate amount of lp tokens as a fraction of existing reserves
uint256 baseTokenShare = (baseTokenAmount * lpTokenSupply) / baseTokenReserves();
uint256 fractionalTokenShare = (fractionalTokenAmount * lpTokenSupply) / fractionalTokenReserves();
return Math.min(baseTokenShare, fractionalTokenShare);
} else {
// if there is no liquidity then init
return Math.sqrt(baseTokenAmount * fractionalTokenAmount) - MINIMUM_LIQUIDITY;
}
}
/// @notice The amount of base tokens and fractional tokens received for burning a given amount of lp tokens.
/// @dev Calculated as a share of existing deposits.
/// @param lpTokenAmount The amount of lp tokens to burn.
/// @return baseTokenAmount The amount of base tokens received.
/// @return fractionalTokenAmount The amount of fractional tokens received.
function removeQuote(uint256 lpTokenAmount) public view returns (uint256, uint256) {
uint256 lpTokenSupply = lpToken.totalSupply();
uint256 baseTokenOutputAmount = (baseTokenReserves() * lpTokenAmount) / lpTokenSupply;
uint256 fractionalTokenOutputAmount = (fractionalTokenReserves() * lpTokenAmount) / lpTokenSupply;
uint256 upperFractionalTokenOutputAmount = (fractionalTokenReserves() * (lpTokenAmount + 1)) / lpTokenSupply;
if (
fractionalTokenOutputAmount % 1e18 != 0
&& upperFractionalTokenOutputAmount - fractionalTokenOutputAmount <= 1000 && lpTokenSupply > 1e15
) {
fractionalTokenOutputAmount = upperFractionalTokenOutputAmount;
}
return (baseTokenOutputAmount, fractionalTokenOutputAmount);
}
// ************************ //
// Internal utils //
// ************************ //
function _transferFrom(address from, address to, uint256 amount) internal returns (bool) {
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
function _validateTokensAreNotStolen(uint256[] calldata tokenIds, ReservoirOracle.Message[] calldata messages)
internal
view
{
address stolenNftFilterAddress = caviar.stolenNftFilterOracle();
// if filter address is not set then no need to check if nfts are stolen
if (stolenNftFilterAddress == address(0)) return;
// validate that nfts are not stolen
StolenNftFilterOracle(stolenNftFilterAddress).validateTokensAreNotStolen(nft, tokenIds, messages);
}
/// @dev Validates that the given tokenIds are valid for the contract's merkle root. Reverts
/// if any of the tokenId proofs are invalid.
function _validateTokenIds(uint256[] calldata tokenIds, bytes32[][] calldata proofs) internal view {
// if merkle root is not set then all tokens are valid
if (merkleRoot == bytes32(0)) return;
// validate merkle proofs against merkle root
for (uint256 i = 0; i < tokenIds.length;) {
bool isValid = MerkleProofLib.verify(
proofs[i],
merkleRoot,
// double hash to prevent second preimage attacks
keccak256(bytes.concat(keccak256(abi.encode(tokenIds[i]))))
);
require(isValid, "Invalid merkle proof");
unchecked {
i++;
}
}
}
/// @dev Returns the current base token reserves. If the base token is ETH then it ignores
/// the msg.value that is being sent in the current call context - this is to ensure the
/// xyk math is correct in the buy() and add() functions.
function _baseTokenReserves() internal view returns (uint256) {
return baseToken == address(0)
? address(this).balance - msg.value // subtract the msg.value if the base token is ETH
: ERC20(baseToken).balanceOf(address(this));
}
}
Caviar.sol 73 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "solmate/auth/Owned.sol";
import "./lib/SafeERC20Namer.sol";
import "./Pair.sol";
/// @title caviar.sh
/// @author out.eth (@outdoteth)
/// @notice An AMM for creating and trading fractionalized NFTs.
contract Caviar is Owned {
using SafeERC20Namer for address;
/// @dev pairs[nft][baseToken][merkleRoot] -> pair
mapping(address => mapping(address => mapping(bytes32 => address))) public pairs;
/// @dev The stolen nft filter oracle address
address public stolenNftFilterOracle;
event SetStolenNftFilterOracle(address indexed stolenNftFilterOracle);
event Create(address indexed nft, address indexed baseToken, bytes32 indexed merkleRoot);
event Destroy(address indexed nft, address indexed baseToken, bytes32 indexed merkleRoot);
constructor(address _stolenNftFilterOracle) Owned(msg.sender) {
stolenNftFilterOracle = _stolenNftFilterOracle;
}
/// @notice Sets the stolen nft filter oracle address.
/// @param _stolenNftFilterOracle The stolen nft filter oracle address.
function setStolenNftFilterOracle(address _stolenNftFilterOracle) public onlyOwner {
stolenNftFilterOracle = _stolenNftFilterOracle;
emit SetStolenNftFilterOracle(_stolenNftFilterOracle);
}
/// @notice Creates a new pair.
/// @param nft The NFT contract address.
/// @param baseToken The base token contract address.
/// @param merkleRoot The merkle root for the valid tokenIds.
/// @return pair The address of the new pair.
function create(address nft, address baseToken, bytes32 merkleRoot) public returns (Pair pair) {
// check that the pair doesn't already exist
require(pairs[nft][baseToken][merkleRoot] == address(0), "Pair already exists");
require(nft.code.length > 0, "Invalid NFT contract");
require(baseToken.code.length > 0 || baseToken == address(0), "Invalid base token contract");
// deploy the pair
string memory baseTokenSymbol = baseToken == address(0) ? "ETH" : baseToken.tokenSymbol();
string memory nftSymbol = nft.tokenSymbol();
string memory nftName = nft.tokenName();
string memory pairSymbol = string.concat(nftSymbol, ":", baseTokenSymbol);
pair = new Pair(nft, baseToken, merkleRoot, pairSymbol, nftName, nftSymbol);
// save the pair
pairs[nft][baseToken][merkleRoot] = address(pair);
emit Create(nft, baseToken, merkleRoot);
}
/// @notice Deletes the pair for the given NFT, base token, and merkle root.
/// @param nft The NFT contract address.
/// @param baseToken The base token contract address.
/// @param merkleRoot The merkle root for the valid tokenIds.
function destroy(address nft, address baseToken, bytes32 merkleRoot) public {
// check that a pair can only destroy itself
require(msg.sender == pairs[nft][baseToken][merkleRoot], "Only pair can destroy itself");
// delete the pair
delete pairs[nft][baseToken][merkleRoot];
emit Destroy(nft, baseToken, merkleRoot);
}
}
LpToken.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "solmate/auth/Owned.sol";
import "solmate/tokens/ERC20.sol";
/// @title LP token
/// @author out.eth (@outdoteth)
/// @notice LP token which is minted and burned by the Pair contract to represent liquidity in the pool.
contract LpToken is Owned, ERC20 {
constructor(string memory pairSymbol)
Owned(msg.sender)
ERC20(string.concat(pairSymbol, " LP token"), string.concat("LP-", pairSymbol), 18)
{}
/// @notice Mints new LP tokens to the given address.
/// @param to The address to mint to.
/// @param amount The amount to mint.
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
/// @notice Burns LP tokens from the given address.
/// @param from The address to burn from.
/// @param amount The amount to burn.
function burn(address from, uint256 amount) public onlyOwner {
_burn(from, amount);
}
}
SafeERC20Namer.sol 80 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "openzeppelin/utils/Strings.sol";
// modified from https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/SafeERC20Namer.sol
// produces token descriptors from inconsistent or absent ERC20 symbol implementations that can return string or bytes32
// this library will always produce a string symbol to represent the token
library SafeERC20Namer {
function bytes32ToString(bytes32 x) private pure returns (string memory) {
bytes memory bytesString = new bytes(32);
uint256 charCount = 0;
for (uint256 j = 0; j < 32; j++) {
bytes1 char = x[j];
if (char != 0) {
bytesString[charCount] = char;
charCount++;
}
}
bytes memory bytesStringTrimmed = new bytes(charCount);
for (uint256 j = 0; j < charCount; j++) {
bytesStringTrimmed[j] = bytesString[j];
}
return string(bytesStringTrimmed);
}
// uses a heuristic to produce a token name from the address
// the heuristic returns the full hex of the address string
function addressToName(address token) private pure returns (string memory) {
return Strings.toHexString(uint160(token));
}
// uses a heuristic to produce a token symbol from the address
// the heuristic returns the first 4 hex of the address string
function addressToSymbol(address token) private pure returns (string memory) {
return Strings.toHexString(uint160(token) >> (160 - 4 * 4));
}
// calls an external view token contract method that returns a symbol or name, and parses the output into a string
function callAndParseStringReturn(address token, bytes4 selector) private view returns (string memory) {
(bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(selector));
// if not implemented, or returns empty data, return empty string
if (!success || data.length == 0) {
return "";
}
// bytes32 data always has length 32
if (data.length == 32) {
bytes32 decoded = abi.decode(data, (bytes32));
return bytes32ToString(decoded);
} else if (data.length > 64) {
return abi.decode(data, (string));
}
return "";
}
// attempts to extract the token symbol. if it does not implement symbol, returns a symbol derived from the address
function tokenSymbol(address token) internal view returns (string memory) {
// 0x95d89b41 = bytes4(keccak256("symbol()"))
string memory symbol = callAndParseStringReturn(token, 0x95d89b41);
if (bytes(symbol).length == 0) {
// fallback to 6 uppercase hex of address
return addressToSymbol(token);
}
return symbol;
}
// attempts to extract the token name. if it does not implement name, returns a name derived from the address
function tokenName(address token) internal view returns (string memory) {
// 0x06fdde03 = bytes4(keccak256("name()"))
string memory name = callAndParseStringReturn(token, 0x06fdde03);
if (bytes(name).length == 0) {
// fallback to full hex of address
return addressToName(token);
}
return name;
}
}
StolenNftFilterOracle.sol 57 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "solmate/auth/Owned.sol";
import "reservoir-oracle/ReservoirOracle.sol";
/// @title StolenNftFilterOracle
/// @author out.eth (@outdoteth)
/// @notice A contract to check that a set of NFTs are not stolen.
contract StolenNftFilterOracle is ReservoirOracle, Owned {
bytes32 private constant TOKEN_TYPE_HASH = keccak256("Token(address contract,uint256 tokenId)");
uint256 public cooldownPeriod = 0;
uint256 public validFor = 60 minutes;
constructor() Owned(msg.sender) ReservoirOracle(0xAeB1D03929bF87F69888f381e73FBf75753d75AF) {}
/// @notice Sets the cooldown period.
/// @param _cooldownPeriod The cooldown period.
function setCooldownPeriod(uint256 _cooldownPeriod) public onlyOwner {
cooldownPeriod = _cooldownPeriod;
}
/// @notice Sets the valid for period.
/// @param _validFor The valid for period.
function setValidFor(uint256 _validFor) public onlyOwner {
validFor = _validFor;
}
function updateReservoirOracleAddress(address newReservoirOracleAddress) public override onlyOwner {
RESERVOIR_ORACLE_ADDRESS = newReservoirOracleAddress;
}
/// @notice Checks that a set of NFTs are not stolen.
/// @param tokenAddress The address of the NFT contract.
/// @param tokenIds The ids of the NFTs.
/// @param messages The messages signed by the reservoir oracle.
function validateTokensAreNotStolen(address tokenAddress, uint256[] calldata tokenIds, Message[] calldata messages)
public
view
{
for (uint256 i = 0; i < tokenIds.length; i++) {
Message calldata message = messages[i];
// check that the signer is correct and message id matches token id + token address
bytes32 expectedMessageId = keccak256(abi.encode(TOKEN_TYPE_HASH, tokenAddress, tokenIds[i]));
require(_verifyMessage(expectedMessageId, validFor, message), "Message has invalid signature");
(bool isFlagged, uint256 lastTransferTime) = abi.decode(message.payload, (bool, uint256));
// check that the NFT is not stolen
require(!isFlagged, "NFT is flagged as suspicious");
// check that the NFT was not transferred too recently
require(lastTransferTime + cooldownPeriod < block.timestamp, "NFT was transferred too recently");
}
}
}
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);
}
}
ERC20.sol 206 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
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;
}
}
ReservoirOracle.sol 111 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// Inspired by https://github.com/ZeframLou/trustus
abstract contract ReservoirOracle {
// --- Structs ---
struct Message {
bytes32 id;
bytes payload;
// The UNIX timestamp when the message was signed by the oracle
uint256 timestamp;
// ECDSA signature or EIP-2098 compact signature
bytes signature;
}
// --- Errors ---
error InvalidMessage();
// --- Fields ---
address public RESERVOIR_ORACLE_ADDRESS;
// --- Constructor ---
constructor(address reservoirOracleAddress) {
RESERVOIR_ORACLE_ADDRESS = reservoirOracleAddress;
}
// --- Public methods ---
function updateReservoirOracleAddress(address newReservoirOracleAddress)
public
virtual;
// --- Internal methods ---
function _verifyMessage(
bytes32 id,
uint256 validFor,
Message memory message
) internal view virtual returns (bool success) {
// Ensure the message matches the requested id
if (id != message.id) {
return false;
}
// Ensure the message timestamp is valid
if (
message.timestamp > block.timestamp ||
message.timestamp + validFor < block.timestamp
) {
return false;
}
bytes32 r;
bytes32 s;
uint8 v;
// Extract the individual signature fields from the signature
bytes memory signature = message.signature;
if (signature.length == 64) {
// EIP-2098 compact signature
bytes32 vs;
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
s := and(
vs,
0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
)
v := add(shr(255, vs), 27)
}
} else if (signature.length == 65) {
// ECDSA signature
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
} else {
return false;
}
address signerAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
// EIP-712 structured-data hash
keccak256(
abi.encode(
keccak256(
"Message(bytes32 id,bytes payload,uint256 timestamp)"
),
message.id,
keccak256(message.payload),
message.timestamp
)
)
)
),
v,
r,
s
);
// Ensure the signer matches the designated oracle address
return signerAddress == RESERVOIR_ORACLE_ADDRESS;
}
}
MerkleProofLib.sol 47 lines
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
/// @notice Gas optimized merkle proof verification library.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
library MerkleProofLib {
function verify(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool isValid) {
assembly {
if proof.length {
// Left shifting by 5 is like multiplying by 32.
let end := add(proof.offset, shl(5, proof.length))
// Initialize offset to the offset of the proof in calldata.
let offset := proof.offset
// Iterate over proof elements to compute root hash.
// prettier-ignore
for {} 1 {} {
// Slot where the leaf should be put in scratch space. If
// leaf > calldataload(offset): slot 32, otherwise: slot 0.
let leafSlot := shl(5, gt(leaf, calldataload(offset)))
// Store elements to hash contiguously in scratch space.
// The xor puts calldataload(offset) in whichever slot leaf
// is not occupying, so 0 if leafSlot is 32, and 32 otherwise.
mstore(leafSlot, leaf)
mstore(xor(leafSlot, 32), calldataload(offset))
// Reuse leaf to store the hash to reduce stack operations.
leaf := keccak256(0, 64) // Hash both slots of scratch space.
offset := add(offset, 32) // Shift 1 word per cycle.
// prettier-ignore
if iszero(lt(offset, end)) { break }
}
}
isValid := eq(leaf, root) // The proof is valid if the roots match.
}
}
}
SafeTransferLib.sol 124 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
FixedPointMathLib.sol 248 lines
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
Strings.sol 70 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
Math.sol 345 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
}
}
}
Read Contract
CLOSE_GRACE_PERIOD 0x956c8327 → uint256
DOMAIN_SEPARATOR 0x3644e515 → bytes32
addQuote 0x0a94914e → uint256
allowance 0xdd62ed3e → uint256
balanceOf 0x70a08231 → uint256
baseToken 0xc55dae63 → address
baseTokenReserves 0x203ae565 → uint256
buyQuote 0x168585e5 → uint256
caviar 0x217c1d68 → address
closeTimestamp 0x052d7c00 → uint256
decimals 0x313ce567 → uint8
fractionalTokenReserves 0xfceacc83 → uint256
lpToken 0x5fcbd285 → address
merkleRoot 0x2eb4a7ab → bytes32
name 0x06fdde03 → string
nft 0x47ccca02 → address
nonces 0x7ecebe00 → uint256
price 0xa035b1fe → uint256
removeQuote 0x037d785b → uint256, uint256
sellQuote 0xd7a2e4c9 → uint256
symbol 0x95d89b41 → string
totalSupply 0x18160ddd → uint256
Write Contract 17 functions
These functions modify contract state and require a wallet transaction to execute.
add 0x1d989f83
uint256 baseTokenAmount
uint256 fractionalTokenAmount
uint256 minLpTokenAmount
uint256 minPrice
uint256 maxPrice
uint256 deadline
returns: uint256
approve 0x095ea7b3
address spender
uint256 amount
returns: bool
buy 0x40993b26
uint256 outputAmount
uint256 maxInputAmount
uint256 deadline
returns: uint256
close 0x43d726d6
No parameters
nftAdd 0xf435bc23
uint256 baseTokenAmount
uint256[] tokenIds
uint256 minLpTokenAmount
uint256 minPrice
uint256 maxPrice
uint256 deadline
bytes32[][] proofs
tuple[] messages
returns: uint256
nftBuy 0x2a6f869d
uint256[] tokenIds
uint256 maxInputAmount
uint256 deadline
returns: uint256
nftRemove 0x4cba3eca
uint256 lpTokenAmount
uint256 minBaseTokenOutputAmount
uint256 deadline
uint256[] tokenIds
bool withFee
returns: uint256, uint256
nftSell 0x799860c7
uint256[] tokenIds
uint256 minOutputAmount
uint256 deadline
bytes32[][] proofs
tuple[] messages
returns: uint256
onERC721Received 0x150b7a02
address
address
uint256
bytes
returns: bytes4
permit 0xd505accf
address owner
address spender
uint256 value
uint256 deadline
uint8 v
bytes32 r
bytes32 s
remove 0x1ae74cdd
uint256 lpTokenAmount
uint256 minBaseTokenOutputAmount
uint256 minFractionalTokenOutputAmount
uint256 deadline
returns: uint256, uint256
sell 0xd3c9727c
uint256 inputAmount
uint256 minOutputAmount
uint256 deadline
returns: uint256
transfer 0xa9059cbb
address to
uint256 amount
returns: bool
transferFrom 0x23b872dd
address from
address to
uint256 amount
returns: bool
unwrap 0x28660571
uint256[] tokenIds
bool withFee
returns: uint256
withdraw 0x2e1a7d4d
uint256 tokenId
wrap 0x59be9f11
uint256[] tokenIds
bytes32[][] proofs
tuple[] messages
returns: uint256
Recent Transactions
No transactions found for this address