diff --git a/.gitignore b/.gitignore index dd5f792..d7ae498 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules/ coverage/ coverage.json build/ +.vscode +.idea diff --git a/OneSplit.full.bin b/OneSplit.full.bin index 4d09eec..5c5cece 100644 --- a/OneSplit.full.bin +++ b/OneSplit.full.bin @@ -1 +1 @@ -608060405234801561001057600080fd5b50604051613e86380380613e868339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055613e21806100656000396000f3fe6080604052600436106103505760003560e01c806375a8b012116101c6578063c9257775116100f7578063d77366a411610095578063f4b9fa751161006f578063f4b9fa7514610906578063f56e281f1461091b578063f69e204614610930578063fbe4ed951461094557610350565b8063d77366a414610812578063dc1536b214610827578063e2a7515e1461083c57610350565b8063cc26e9fc116100d1578063cc26e9fc146107be578063cede5f6a146107d3578063d393c3e9146107e8578063d70a2d1f146107fd57610350565b8063c92577751461077f578063c989b66714610794578063c9b42c67146107a957610350565b8063a1b4d01111610164578063b3bc78441161013e578063b3bc78441461072b578063b69d045614610740578063c762a46c14610755578063c77b9de61461076a57610350565b8063a1b4d011146106ec578063a734f06e14610701578063b0a7ef291461071657610350565b80637e09b9c2116101a05780637e09b9c214610698578063819faf7b146106ad578063851954fa146106c25780638bdb2afa146106d757610350565b806375a8b0121461065957806375b5be2d1461066e5780637a88bdbd1461068357610350565b80633ca5b234116102a057806351f1985c1161023e5780635c0cb479116102185780635c0cb4791461060557806364ec4e5c1461061a57806368e2a0141461062f5780636cbc4a6e1461064457610350565b806351f1985c146105c65780635aa8fb48146105db5780635ae51b82146105f057610350565b8063423d03f91161027a578063423d03f91461057257806344211d62146105875780634a7101d51461059c5780634b57b0be146105b157610350565b80633ca5b234146105335780633e413bee1461054857806340ab7b8c1461055d57610350565b806321a360f51161030d5780632e707bd2116102e75780632e707bd2146104df5780632f48ab7d146104f457806334b4dabb14610509578063372a26cb1461051e57610350565b806321a360f5146104a057806322320c98146104b55780632d3b5207146104ca57610350565b806305d8aa0a1461035f578063085e2c5b1461038657806312dea160146104305780631388b4201461046157806313989140146104765780632113240d1461048b575b3332141561035d57600080fd5b005b34801561036b57600080fd5b5061037461095a565b60408051918252519081900360200190f35b34801561039257600080fd5b506103d5600480360360a08110156103a957600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060800135610961565b6040518083815260200180602001828103825283818151815260200191508051906020019060200280838360005b8381101561041b578181015183820152602001610403565b50505050905001935050505060405180910390f35b34801561043c57600080fd5b50610445610aad565b604080516001600160a01b039092168252519081900360200190f35b34801561046d57600080fd5b50610445610ac5565b34801561048257600080fd5b50610374610add565b34801561049757600080fd5b50610374610ae3565b3480156104ac57600080fd5b50610374610ae9565b3480156104c157600080fd5b50610445610af2565b3480156104d657600080fd5b50610374610b0a565b3480156104eb57600080fd5b50610374610b13565b34801561050057600080fd5b50610445610b18565b34801561051557600080fd5b50610374610b30565b34801561052a57600080fd5b50610445610b35565b34801561053f57600080fd5b50610445610b4d565b34801561055457600080fd5b50610445610b65565b34801561056957600080fd5b50610445610b77565b34801561057e57600080fd5b50610445610b8f565b34801561059357600080fd5b50610374610ba7565b3480156105a857600080fd5b50610374610bac565b3480156105bd57600080fd5b50610445610bb1565b3480156105d257600080fd5b50610445610bc9565b3480156105e757600080fd5b50610374610be1565b3480156105fc57600080fd5b50610374610be7565b34801561061157600080fd5b50610374610bed565b34801561062657600080fd5b50610374610bf2565b34801561063b57600080fd5b50610374610bf9565b34801561065057600080fd5b50610374610c00565b34801561066557600080fd5b50610374610c07565b34801561067a57600080fd5b50610445610c0d565b34801561068f57600080fd5b50610374610c20565b3480156106a457600080fd5b50610374610c25565b3480156106b957600080fd5b50610445610c2c565b3480156106ce57600080fd5b50610445610c44565b3480156106e357600080fd5b50610445610c5c565b3480156106f857600080fd5b50610445610c74565b34801561070d57600080fd5b50610445610c8c565b34801561072257600080fd5b50610374610ca4565b34801561073757600080fd5b50610374610caa565b34801561074c57600080fd5b50610445610cb3565b34801561076157600080fd5b50610374610ccb565b34801561077657600080fd5b50610374610cd0565b34801561078b57600080fd5b50610445610cd6565b3480156107a057600080fd5b50610374610cee565b3480156107b557600080fd5b50610374610cf5565b3480156107ca57600080fd5b50610374610cfc565b3480156107df57600080fd5b50610445610d01565b3480156107f457600080fd5b50610374610d19565b34801561080957600080fd5b50610445610d20565b34801561081e57600080fd5b50610445610d38565b34801561083357600080fd5b50610374610d50565b61035d600480360360c081101561085257600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a08101608082013564010000000081111561089257600080fd5b8201836020820111156108a457600080fd5b803590602001918460208302840111640100000000831117156108c657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250610d56915050565b34801561091257600080fd5b50610445610f78565b34801561092757600080fd5b50610374610f8a565b34801561093c57600080fd5b50610445610f8f565b34801561095157600080fd5b50610445610fa7565b6220000081565b600080546040805163085e2c5b60e01b81526001600160a01b03898116600483015288811660248301526044820188905260648201879052608482018690529151606093929092169163085e2c5b9160a4808201928792909190829003018186803b1580156109cf57600080fd5b505afa1580156109e3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610a0c57600080fd5b815160208301805160405192949293830192919084640100000000821115610a3357600080fd5b908301906020820185811115610a4857600080fd5b8251866020820283011164010000000082111715610a6557600080fd5b82525081516020918201928201910280838360005b83811015610a92578181015183820152602001610a7a565b50505050905001604052505050915091509550959350505050565b7352ae12abe5d8bd778bd5397f99ca900624cfadd481565b73794e6e91555438afc3ccf1c5076a74f42133d08d81565b61200081565b61800081565b64020000000081565b73a5407eae9ba41422680e2e00537571bcc53efbfd81565b64010000000081565b608081565b73dac17f958d2ee523a2206206994597c13d831ec781565b604081565b7379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2781565b734fabb145d64652a948d72533023f6e7a623c7c5381565b600080516020613d2b83398151915281565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c81565b7345f783cce6b7ff23b2ab2d70e416cdb7d6055f5181565b601081565b602081565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b73a2b47e3d5c44877cca798226b7b8118f9bfb7a5681565b61400081565b61080081565b600881565b6202000081565b6210000081565b6208000081565b61040081565b6e085d4780b73119b644ae5ecd22b37681565b600281565b6240000081565b73398ec7346dcd622edc5ae82352f02be94c62d11981565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce31581565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9581565b734ddc2d193948926d02f9b1fe9e1daa0718270ed581565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b61100081565b64040000000081565b7306af07097c9eeb7fd685c692751d5c66db49c21581565b600181565b61020081565b7357ab1ec28d129707052df4df418d58a2d46d5f5181565b6280000081565b6204000081565b600c81565b7352ea46506b9cc5ef470c5bf89f17dc28bb35d85c81565b6201000081565b73f6e2d7f616b67e46d708e4410746e9aab3a4c51881565b73818e6fecd516ecc3849daf6845e3ec868087b75581565b61010081565b846001600160a01b0316866001600160a01b03161415610d7557610f70565b610d7d613c8f565b604051806101800160405280610fb6815260200161123781526020016113c281526020016116e781526020016119c08152602001611b4b8152602001611d1c8152602001611f418152602001612170815260200161239f815260200161253d81526020016126e98152509050600c83511115610e2a5760405162461bcd60e51b8152600401808060200182810382526042815260200180613dab6042913960600191505060405180910390fd5b600080805b8551811015610e88576000868281518110610e4657fe5b60200260200101511115610e8057610e7a868281518110610e6357fe5b60200260200101518461285590919063ffffffff16565b92508091505b600101610e2f565b5060008211610ec85760405162461bcd60e51b815260040180806020018281038252602f815260200180613cdb602f913960400191505060405180910390fd5b8660005b8651811015610f6a57868181518110610ee157fe5b602002602001015160001415610ef657610f62565b6000610f2e85610f228a8581518110610f0b57fe5b60200260200101518d6128b890919063ffffffff16565b9063ffffffff61291116565b905083821415610f3b5750815b8083039250610f5f8c8c838986600c8110610f5257fe5b602002015163ffffffff16565b50505b600101610ecc565b50505050505b505050505050565b600080516020613cbb83398151915281565b600481565b733d9819210a31b4961b30ef54be2aed79b9c9cd3b81565b6000546001600160a01b031681565b600081610fcb6001600160a01b038616612953565b6110fb57604080516303795fb160e11b81526001600160a01b0387166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b15801561102d57600080fd5b505afa158015611041573d6000803e3d6000fd5b505050506040513d602081101561105757600080fd5b505190506001600160a01b038116156110f957611074868261298f565b604080516395e3c50b60e01b8152600481018490526001602482015242604482015290516001600160a01b038316916395e3c50b9160648083019260209291908290030181600087803b1580156110ca57600080fd5b505af11580156110de573d6000803e3d6000fd5b505050506040513d60208110156110f457600080fd5b505191505b505b61110d846001600160a01b0316612953565b61122d57604080516303795fb160e11b81526001600160a01b0386166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b15801561116f57600080fd5b505afa158015611183573d6000803e3d6000fd5b505050506040513d602081101561119957600080fd5b505190506001600160a01b0381161561122b57806001600160a01b031663f39b5b9b836001426040518463ffffffff1660e01b815260040180838152602001828152602001925050506020604051808303818588803b1580156111fb57600080fd5b505af115801561120f573d6000803e3d6000fd5b50505050506040513d602081101561122657600080fd5b505191505b505b90505b9392505050565b60006112578473818e6fecd516ecc3849daf6845e3ec868087b75561298f565b73818e6fecd516ecc3849daf6845e3ec868087b7556329589f616112836001600160a01b038716612953565b61128e576000611290565b835b6112a2876001600160a01b0316612953565b6112ac57866112c2565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b856112d5886001600160a01b0316612953565b6112df57876112f5565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b604080516001600160e01b031960e088901b1681526001600160a01b039485166004820152602481019390935292166044820152306064820152600160ff1b6084820152600060a48201819052734d37f28d2db99e8d35a6c725a5f1749a085850a360c483015261010060e4830152610104820152905161014480830192602092919082900301818588803b15801561138d57600080fd5b505af11580156113a1573d6000803e3d6000fd5b50505050506040513d60208110156113b857600080fd5b5051949350505050565b60006113d6846001600160a01b0316612953565b156114445773c0829421c1d260bd3cb3e0f06cfe2d52db2ce3156001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801561142a57600080fd5b505af115801561143e573d6000803e3d6000fd5b50505050505b60007352ae12abe5d8bd778bd5397f99ca900624cfadd46001600160a01b031663bb34534c6040518163ffffffff1660e01b815260040180806c42616e636f724e6574776f726b60981b815250602001905060206040518083038186803b1580156114ae57600080fd5b505afa1580156114c2573d6000803e3d6000fd5b505050506040513d60208110156114d857600080fd5b5051905060606114e88686612a48565b90506115256114ff876001600160a01b0316612953565b611509578661151f565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce3155b8361298f565b6000826001600160a01b031663c7ba24bc838760016040518463ffffffff1660e01b81526004018080602001848152602001838152602001828103825285818151815260200191508051906020019060200280838360005b8381101561159557818101518382015260200161157d565b50505050905001945050505050602060405180830381600087803b1580156115bc57600080fd5b505af11580156115d0573d6000803e3d6000fd5b505050506040513d60208110156115e657600080fd5b505190506115fc6001600160a01b038716612953565b156116dd57604080516370a0823160e01b8152306004820152905173c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561165a57600080fd5b505afa15801561166e573d6000803e3d6000fd5b505050506040513d602081101561168457600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b1580156116c457600080fd5b505af11580156116d8573d6000803e3d6000fd5b505050505b9695505050505050565b60006116fb846001600160a01b0316612953565b156117695773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801561174f57600080fd5b505af1158015611763573d6000803e3d6000fd5b50505050505b6117b861177e856001600160a01b0316612953565b611788578461179e565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b73794e6e91555438afc3ccf1c5076a74f42133d08d61298f565b600073794e6e91555438afc3ccf1c5076a74f42133d08d630621b4f66117e66001600160a01b038816612953565b6117f05786611806565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b85611819886001600160a01b0316612953565b6118235787611839565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b604080516001600160e01b031960e087901b1681526001600160a01b03948516600482015260248101939093529216604482015260016064820152905160848083019260209291908290030181600087803b15801561189757600080fd5b505af11580156118ab573d6000803e3d6000fd5b505050506040513d60208110156118c157600080fd5b505190506118d76001600160a01b038516612953565b1561122d57604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561193557600080fd5b505afa158015611949573d6000803e3d6000fd5b505050506040513d602081101561195f57600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b15801561199f57600080fd5b505af11580156119b3573d6000803e3d6000fd5b5050505090509392505050565b6000806001600160a01b038516600080516020613d2b833981519152146119e85760006119eb565b60025b6001600160a01b038616600080516020613cbb83398151915214611a10576000611a13565b60015b0160ff1690506000600080516020613d2b8339815191526001600160a01b03861614611a40576000611a43565b60025b6001600160a01b038616600080516020613cbb83398151915214611a68576000611a6b565b60015b0160ff16905081600f0b60001480611a86575080600f0b6000145b15611a9657600092505050611230565b611ab48673a2b47e3d5c44877cca798226b7b8118f9bfb7a5661298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a2b47e3d5c44877cca798226b7b8118f9bfb7a569263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b505af1158015611b3e573d6000803e3d6000fd5b5050505050509392505050565b6000806001600160a01b03851673dac17f958d2ee523a2206206994597c13d831ec714611b79576000611b7c565b60035b6001600160a01b038616600080516020613d2b83398151915214611ba1576000611ba4565b60025b6001600160a01b038716600080516020613cbb83398151915214611bc9576000611bcc565b60015b010160ff169050600073dac17f958d2ee523a2206206994597c13d831ec76001600160a01b0316856001600160a01b031614611c09576000611c0c565b60035b6001600160a01b038616600080516020613d2b83398151915214611c31576000611c34565b60025b6001600160a01b038716600080516020613cbb83398151915214611c59576000611c5c565b60015b010160ff16905081600f0b60001480611c78575080600f0b6000145b15611c8857600092505050611230565b611ca6867352ea46506b9cc5ef470c5bf89f17dc28bb35d85c61298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517352ea46506b9cc5ef470c5bf89f17dc28bb35d85c9263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b0385166e085d4780b73119b644ae5ecd22b37614611d45576000611d48565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611d73576000611d76565b60035b6001600160a01b038716600080516020613d2b83398151915214611d9b576000611d9e565b60025b6001600160a01b038816600080516020613cbb83398151915214611dc3576000611dc6565b60015b01010160ff16905060006e085d4780b73119b644ae5ecd22b3766001600160a01b0316856001600160a01b031614611dff576000611e02565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611e2d576000611e30565b60035b6001600160a01b038716600080516020613d2b83398151915214611e55576000611e58565b60025b6001600160a01b038816600080516020613cbb83398151915214611e7d576000611e80565b60015b01010160ff16905081600f0b60001480611e9d575080600f0b6000145b15611ead57600092505050611230565b611ecb867345f783cce6b7ff23b2ab2d70e416cdb7d6055f5161298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517345f783cce6b7ff23b2ab2d70e416cdb7d6055f519263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b038516734fabb145d64652a948d72533023f6e7a623c7c5314611f6f576000611f72565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611f9d576000611fa0565b60035b6001600160a01b038716600080516020613d2b83398151915214611fc5576000611fc8565b60025b6001600160a01b038816600080516020613cbb83398151915214611fed576000611ff0565b60015b01010160ff1690506000734fabb145d64652a948d72533023f6e7a623c7c536001600160a01b0316856001600160a01b03161461202e576000612031565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec71461205c57600061205f565b60035b6001600160a01b038716600080516020613d2b83398151915214612084576000612087565b60025b6001600160a01b038816600080516020613cbb833981519152146120ac5760006120af565b60015b01010160ff16905081600f0b600014806120cc575080600f0b6000145b156120dc57600092505050611230565b6120fa867379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2761298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517379a8c46dea5ada233abaffd40f3a0a2b1e5a4f279263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b0385167357ab1ec28d129707052df4df418d58a2d46d5f511461219e5760006121a1565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec7146121cc5760006121cf565b60035b6001600160a01b038716600080516020613d2b833981519152146121f45760006121f7565b60025b6001600160a01b038816600080516020613cbb8339815191521461221c57600061221f565b60015b01010160ff16905060007357ab1ec28d129707052df4df418d58a2d46d5f516001600160a01b0316856001600160a01b03161461225d576000612260565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec71461228b57600061228e565b60035b6001600160a01b038716600080516020613d2b833981519152146122b35760006122b6565b60025b6001600160a01b038816600080516020613cbb833981519152146122db5760006122de565b60015b01010160ff16905081600f0b600014806122fb575080600f0b6000145b1561230b57600092505050611230565b6123298673a5407eae9ba41422680e2e00537571bcc53efbfd61298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a5407eae9ba41422680e2e00537571bcc53efbfd9263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b60006123b3846001600160a01b0316612953565b61246c5760006123c285613152565b90506123ce858261298f565b806001600160a01b031663a0712d68846040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561241457600080fd5b505af1158015612428573d6000803e3d6000fd5b505050506040513d602081101561243e57600080fd5b506124649050818561245f6001600160a01b0383163063ffffffff61334216565b610fb6565b915050611230565b61247e836001600160a01b0316612953565b61253357600061248d84613152565b9050600061249c868386610fb6565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b1580156124e457600080fd5b505af11580156124f8573d6000803e3d6000fd5b505050506040513d602081101561250e57600080fd5b5061252a90506001600160a01b0386163063ffffffff61334216565b92505050611230565b5060009392505050565b60006001600160a01b038416600080516020613cbb83398151915214156126205761257c847306af07097c9eeb7fd685c692751d5c66db49c21561298f565b60408051633b4da69f60e01b81523060048201526024810184905290517306af07097c9eeb7fd685c692751d5c66db49c21591633b4da69f91604480830192600092919082900301818387803b1580156125d557600080fd5b505af11580156125e9573d6000803e3d6000fd5b5061261992507306af07097c9eeb7fd685c692751d5c66db49c215915085905061245f823063ffffffff61334216565b9050611230565b6001600160a01b038316600080516020613cbb8339815191521415612533576000612660857306af07097c9eeb7fd685c692751d5c66db49c21585610fb6565b6040805163ef693bed60e01b81523060048201526024810183905290519192507306af07097c9eeb7fd685c692751d5c66db49c2159163ef693bed9160448082019260009290919082900301818387803b1580156126bd57600080fd5b505af11580156126d1573d6000803e3d6000fd5b50612464925050506001600160a01b03851630613342565b60006126fd846001600160a01b0316612953565b6127bb57600061270c856133ec565b9050612718858261298f565b60408051636968703360e11b81526001600160a01b03871660048201526024810185905261044d6044820152905173398ec7346dcd622edc5ae82352f02be94c62d1199163d2d0e06691606480830192600092919082900301818387803b15801561278257600080fd5b505af1158015612796573d6000803e3d6000fd5b50505050612464818561245f30856001600160a01b031661334290919063ffffffff16565b6127cd836001600160a01b0316612953565b6125335760006127dc846133ec565b905060006127eb868386610fb6565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561283357600080fd5b505af1158015612847573d6000803e3d6000fd5b505050508092505050611230565b6000828201838110156128af576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b6000826128c7575060006128b2565b828202828482816128d457fe5b04146128af5760405162461bcd60e51b8152600401808060200182810382526021815260200180613d0a6021913960400191505060405180910390fd5b60006128af83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061380f565b60006001600160a01b038216158061298757506001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee145b90505b919050565b6129a1826001600160a01b0316612953565b612a445760408051636eb1769f60e11b81523060048201526001600160a01b038381166024830152915160ff9285169163dd62ed3e916044808301926020929190829003018186803b1580156129f657600080fd5b505afa158015612a0a573d6000803e3d6000fd5b505050506040513d6020811015612a2057600080fd5b5051901c612a4457612a446001600160a01b0383168260001963ffffffff6138b116565b5050565b6060816001600160a01b0316836001600160a01b03161415612a7957506040805160008152602081019091526128b2565b612a8b836001600160a01b0316612953565b15612aa85773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31592505b612aba826001600160a01b0316612953565b15612ad75773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591505b6001600160a01b038316731f573d6fb3f13d689ff844b4ce37794d79a7ff1c1480612b1e57506001600160a01b038216731f573d6fb3f13d689ff844b4ce37794d79a7ff1c145b15612b4957604080516003808252608082019092529060208201606080388339019050509050612b6b565b60408051600580825260c08201909252906020820160a0803883390190505090505b6000806001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14612d34576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b612bc86001600160a01b038b16612953565b612bd25789612be8565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310612c665780518252601f199092019160209182019101612c47565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d8060008114612cc7576040519150601f19603f3d011682016040523d82523d6000602084013e612ccc565b606091505b509150915081612cf45760408051600080825260208201909252905b509450505050506128b2565b808060200190516020811015612d0957600080fd5b505193506001600160a01b038416612d31576040805160008082526020820190925290612ce8565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14612ef2576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b612d8e6001600160a01b038a16612953565b612d985788612dae565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310612e2c5780518252601f199092019160209182019101612e0d565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d8060008114612e8d576040519150601f19603f3d011682016040523d82523d6000602084013e612e92565b606091505b509150915081612eb2576040805160008082526020820190925290612ce8565b808060200190516020811015612ec757600080fd5b505192506001600160a01b038316612eef576040805160008082526020820190925290612ce8565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c1415612fb5578483600081518110612f2557fe5b60200260200101906001600160a01b031690816001600160a01b0316815250508183600181518110612f5357fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c83600281518110612f9557fe5b6001600160a01b0390921660209283029190910190910152506128b29050565b6001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c141561305857731f573d6fb3f13d689ff844b4ce37794d79a7ff1c83600081518110612ffc57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360018151811061302a57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250508383600281518110612f9557fe5b848360008151811061306657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818360018151811061309457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c836002815181106130d657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360038151811061310457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050838360048151811061313257fe5b6001600160a01b0390921660209283029190910190910152505092915050565b6000613166826001600160a01b0316612953565b156131865750734ddc2d193948926d02f9b1fe9e1daa0718270ed561298a565b6001600160a01b038216600080516020613cbb83398151915214156131c05750735d3a536e4d6dbd6114cc1ead35777bab948e364361298a565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef14156132005750736c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e61298a565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e8621415613240575073158079ee67fce2f58472a96584a73c7ab9ac95c161298a565b6001600160a01b038216600080516020613d2b833981519152141561327a57507339aa39c021dfbae8fac545936693ac917d5e756361298a565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c59914156132ba575073c11b1268c1a384e55c48c2391d8d480264a3a7f461298a565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f49814156132fa575073b3319f5d18bc0d84dd1b4825dcde5d5f7266d40761298a565b6001600160a01b03821673dac17f958d2ee523a2206206994597c13d831ec7141561333a575073f650c3d88d12db855b8bf7d11be6c55a4e07dcc961298a565b506000919050565b600061334d83612953565b1561336357506001600160a01b038116316128b2565b826001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156133b957600080fd5b505afa1580156133cd573d6000803e3d6000fd5b505050506040513d60208110156133e357600080fd5b505190506128b2565b6000613400826001600160a01b0316612953565b156134205750733a3a65aab0dd2a17e3f1947ba16138cd37d08c0461298a565b6001600160a01b038216600080516020613cbb833981519152141561345a575073fc1e690f61efd961294b3e1ce3313fbd8aa4f85d61298a565b6001600160a01b038216600080516020613d2b83398151915214156134945750739ba00d6856a4edf4665bca2c2309936572473b7e61298a565b6001600160a01b0382167357ab1ec28d129707052df4df418d58a2d46d5f5114156134d4575073625ae63000f46200499120b906716420bd05924061298a565b6001600160a01b038216734fabb145d64652a948d72533023f6e7a623c7c5314156135145750736ee0f7bb50a54ab5253da0667b0dc2ee526c30a861298a565b6001600160a01b0382166e085d4780b73119b644ae5ecd22b376141561354f5750734da9b813057d04baef4e5800e36083717b4a034161298a565b6001600160a01b03821673dac17f958d2ee523a2206206994597c13d831ec7141561358f57507371fc860f7d3a592a4a98740e39db31d25db65ae861298a565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef14156135cf575073e1ba0fb44ccb0d11b80f92f4f8ed94ca3ff51d0061298a565b6001600160a01b03821673dd974d5c2e2928dea5f71b9825b8b646686bd200141561360f5750739d91be44c06d373a8a226e1f3b146956083803eb61298a565b6001600160a01b0382167380fb784b7ed66730e8b1dbd9820afd29931aab03141561364f5750737d2d3688df45ce7c552e19c27e007673da9204b861298a565b6001600160a01b03821673514910771af9ca656af840dff83e8264ecf986ca141561368f575073a64bd6c70cb9051f6a9ba1f163fdc07e0dfb5f8461298a565b6001600160a01b038216730f5d2fb29fb7d3cfee444a200298f468908cc94214156136cf5750736fce4a401b6b80ace52baaefe4421bd188e76f6f61298a565b6001600160a01b038216739f8f72aa9304c8b593d555f12ef6589cc3a579a2141561370f5750737deb5e830be29f91e298ba5ff1356bb7f814699861298a565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e862141561374f57507371010a9d003445ac60c4e6a7017c1e89a477b43861298a565b6001600160a01b03821673c011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f141561378f575073328c4c80bc7aca0834db37e6600a6c49e12da4de61298a565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c59914156137cf575073fc4b8ed459e00e5400be803a9bb3954234fd50e361298a565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f498141561333a5750736fb0855c404e09c47c3fbca25f08d4e41f9f062f61298a565b6000818361389b5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613860578181015183820152602001613848565b50505050905090810190601f16801561388d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385816138a757fe5b0495945050505050565b6138ba83612953565b61398257600081118015613948575060408051636eb1769f60e11b81523060048201526001600160a01b038481166024830152915160009286169163dd62ed3e916044808301926020929190829003018186803b15801561391a57600080fd5b505afa15801561392e573d6000803e3d6000fd5b505050506040513d602081101561394457600080fd5b5051115b15613968576139686001600160a01b03841683600063ffffffff61398716565b6139826001600160a01b038416838363ffffffff61398716565b505050565b801580613a0d575060408051636eb1769f60e11b81523060048201526001600160a01b03848116602483015291519185169163dd62ed3e91604480820192602092909190829003018186803b1580156139df57600080fd5b505afa1580156139f3573d6000803e3d6000fd5b505050506040513d6020811015613a0957600080fd5b5051155b613a485760405162461bcd60e51b8152600401808060200182810382526036815260200180613d756036913960400191505060405180910390fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052613982908490613aa7826001600160a01b0316613c53565b613af8576040805162461bcd60e51b815260206004820152601f60248201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604482015290519081900360640190fd5b60006060836001600160a01b0316836040518082805190602001908083835b60208310613b365780518252601f199092019160209182019101613b17565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613b98576040519150601f19603f3d011682016040523d82523d6000602084013e613b9d565b606091505b509150915081613bf4576040805162461bcd60e51b815260206004820181905260248201527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604482015290519081900360640190fd5b805115613c4d57808060200190516020811015613c1057600080fd5b5051613c4d5760405162461bcd60e51b815260040180806020018281038252602a815260200180613d4b602a913960400191505060405180910390fd5b50505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590613c8757508115155b949350505050565b604051806101800160405280600c905b613cb8815260200190600190039081613c9f5790505090565bfefe0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f4f6e6553706c69743a20646973747269627574696f6e2073686f756c6420636f6e7461696e206e6f6e2d7a65726f73536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565645361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f20746f206e6f6e2d7a65726f20616c6c6f77616e63654f6e6553706c69743a20446973747269627574696f6e2061727261792073686f756c64206e6f74206578636565642072657365727665732061727261792073697a65a265627a7a72315820b7b106c8ffa502d2e0dd6598694f6eb143e8f1d38bb5268aaee7f0681a39891b64736f6c63430005100032 \ No newline at end of file +608060405234801561001057600080fd5b50604051613e86380380613e868339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b0319909216919091179055613e21806100656000396000f3fe6080604052600436106103505760003560e01c806375a8b012116101c6578063c9257775116100f7578063d77366a411610095578063f4b9fa751161006f578063f4b9fa7514610906578063f56e281f1461091b578063f69e204614610930578063fbe4ed951461094557610350565b8063d77366a414610812578063dc1536b214610827578063e2a7515e1461083c57610350565b8063cc26e9fc116100d1578063cc26e9fc146107be578063cede5f6a146107d3578063d393c3e9146107e8578063d70a2d1f146107fd57610350565b8063c92577751461077f578063c989b66714610794578063c9b42c67146107a957610350565b8063a1b4d01111610164578063b3bc78441161013e578063b3bc78441461072b578063b69d045614610740578063c762a46c14610755578063c77b9de61461076a57610350565b8063a1b4d011146106ec578063a734f06e14610701578063b0a7ef291461071657610350565b80637e09b9c2116101a05780637e09b9c214610698578063819faf7b146106ad578063851954fa146106c25780638bdb2afa146106d757610350565b806375a8b0121461065957806375b5be2d1461066e5780637a88bdbd1461068357610350565b80633ca5b234116102a057806351f1985c1161023e5780635c0cb479116102185780635c0cb4791461060557806364ec4e5c1461061a57806368e2a0141461062f5780636cbc4a6e1461064457610350565b806351f1985c146105c65780635aa8fb48146105db5780635ae51b82146105f057610350565b8063423d03f91161027a578063423d03f91461057257806344211d62146105875780634a7101d51461059c5780634b57b0be146105b157610350565b80633ca5b234146105335780633e413bee1461054857806340ab7b8c1461055d57610350565b806321a360f51161030d5780632e707bd2116102e75780632e707bd2146104df5780632f48ab7d146104f457806334b4dabb14610509578063372a26cb1461051e57610350565b806321a360f5146104a057806322320c98146104b55780632d3b5207146104ca57610350565b806305d8aa0a1461035f578063085e2c5b1461038657806312dea160146104305780631388b4201461046157806313989140146104765780632113240d1461048b575b3332141561035d57600080fd5b005b34801561036b57600080fd5b5061037461095a565b60408051918252519081900360200190f35b34801561039257600080fd5b506103d5600480360360a08110156103a957600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060800135610961565b6040518083815260200180602001828103825283818151815260200191508051906020019060200280838360005b8381101561041b578181015183820152602001610403565b50505050905001935050505060405180910390f35b34801561043c57600080fd5b50610445610aad565b604080516001600160a01b039092168252519081900360200190f35b34801561046d57600080fd5b50610445610ac5565b34801561048257600080fd5b50610374610add565b34801561049757600080fd5b50610374610ae3565b3480156104ac57600080fd5b50610374610ae9565b3480156104c157600080fd5b50610445610af2565b3480156104d657600080fd5b50610374610b0a565b3480156104eb57600080fd5b50610374610b13565b34801561050057600080fd5b50610445610b18565b34801561051557600080fd5b50610374610b30565b34801561052a57600080fd5b50610445610b35565b34801561053f57600080fd5b50610445610b4d565b34801561055457600080fd5b50610445610b65565b34801561056957600080fd5b50610445610b77565b34801561057e57600080fd5b50610445610b8f565b34801561059357600080fd5b50610374610ba7565b3480156105a857600080fd5b50610374610bac565b3480156105bd57600080fd5b50610445610bb1565b3480156105d257600080fd5b50610445610bc9565b3480156105e757600080fd5b50610374610be1565b3480156105fc57600080fd5b50610374610be7565b34801561061157600080fd5b50610374610bed565b34801561062657600080fd5b50610374610bf2565b34801561063b57600080fd5b50610374610bf9565b34801561065057600080fd5b50610374610c00565b34801561066557600080fd5b50610374610c07565b34801561067a57600080fd5b50610445610c0d565b34801561068f57600080fd5b50610374610c20565b3480156106a457600080fd5b50610374610c25565b3480156106b957600080fd5b50610445610c2c565b3480156106ce57600080fd5b50610445610c44565b3480156106e357600080fd5b50610445610c5c565b3480156106f857600080fd5b50610445610c74565b34801561070d57600080fd5b50610445610c8c565b34801561072257600080fd5b50610374610ca4565b34801561073757600080fd5b50610374610caa565b34801561074c57600080fd5b50610445610cb3565b34801561076157600080fd5b50610374610ccb565b34801561077657600080fd5b50610374610cd0565b34801561078b57600080fd5b50610445610cd6565b3480156107a057600080fd5b50610374610cee565b3480156107b557600080fd5b50610374610cf5565b3480156107ca57600080fd5b50610374610cfc565b3480156107df57600080fd5b50610445610d01565b3480156107f457600080fd5b50610374610d19565b34801561080957600080fd5b50610445610d20565b34801561081e57600080fd5b50610445610d38565b34801561083357600080fd5b50610374610d50565b61035d600480360360c081101561085257600080fd5b6001600160a01b03823581169260208101359091169160408201359160608101359181019060a08101608082013564010000000081111561089257600080fd5b8201836020820111156108a457600080fd5b803590602001918460208302840111640100000000831117156108c657600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505091359250610d56915050565b34801561091257600080fd5b50610445610f78565b34801561092757600080fd5b50610374610f8a565b34801561093c57600080fd5b50610445610f8f565b34801561095157600080fd5b50610445610fa7565b6220000081565b600080546040805163085e2c5b60e01b81526001600160a01b03898116600483015288811660248301526044820188905260648201879052608482018690529151606093929092169163085e2c5b9160a4808201928792909190829003018186803b1580156109cf57600080fd5b505afa1580156109e3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040908152811015610a0c57600080fd5b815160208301805160405192949293830192919084640100000000821115610a3357600080fd5b908301906020820185811115610a4857600080fd5b8251866020820283011164010000000082111715610a6557600080fd5b82525081516020918201928201910280838360005b83811015610a92578181015183820152602001610a7a565b50505050905001604052505050915091509550959350505050565b7352ae12abe5d8bd778bd5397f99ca900624cfadd481565b73794e6e91555438afc3ccf1c5076a74f42133d08d81565b61200081565b61800081565b64020000000081565b73a5407eae9ba41422680e2e00537571bcc53efbfd81565b64010000000081565b608081565b73dac17f958d2ee523a2206206994597c13d831ec781565b604081565b7379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2781565b734fabb145d64652a948d72533023f6e7a623c7c5381565b600080516020613d2b83398151915281565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c81565b7345f783cce6b7ff23b2ab2d70e416cdb7d6055f5181565b601081565b602081565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc281565b73a2b47e3d5c44877cca798226b7b8118f9bfb7a5681565b61400081565b61080081565b600881565b6202000081565b6210000081565b6208000081565b61040081565b6e085d4780b73119b644ae5ecd22b37681565b600281565b6240000081565b73398ec7346dcd622edc5ae82352f02be94c62d11981565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce31581565b73c0a47dfe034b400b47bdad5fecda2621de6c4d9581565b734ddc2d193948926d02f9b1fe9e1daa0718270ed581565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b61100081565b64040000000081565b7306af07097c9eeb7fd685c692751d5c66db49c21581565b600181565b61020081565b7357ab1ec28d129707052df4df418d58a2d46d5f5181565b6280000081565b6204000081565b600c81565b7352ea46506b9cc5ef470c5bf89f17dc28bb35d85c81565b6201000081565b73f6e2d7f616b67e46d708e4410746e9aab3a4c51881565b73818e6fecd516ecc3849daf6845e3ec868087b75581565b61010081565b846001600160a01b0316866001600160a01b03161415610d7557610f70565b610d7d613c8f565b604051806101800160405280610fb6815260200161123781526020016113c281526020016116e781526020016119c08152602001611b4b8152602001611d1c8152602001611f418152602001612170815260200161239f815260200161253d81526020016126e98152509050600c83511115610e2a5760405162461bcd60e51b8152600401808060200182810382526042815260200180613dab6042913960600191505060405180910390fd5b600080805b8551811015610e88576000868281518110610e4657fe5b60200260200101511115610e8057610e7a868281518110610e6357fe5b60200260200101518461285590919063ffffffff16565b92508091505b600101610e2f565b5060008211610ec85760405162461bcd60e51b815260040180806020018281038252602f815260200180613cdb602f913960400191505060405180910390fd5b8660005b8651811015610f6a57868181518110610ee157fe5b602002602001015160001415610ef657610f62565b6000610f2e85610f228a8581518110610f0b57fe5b60200260200101518d6128b890919063ffffffff16565b9063ffffffff61291116565b905083821415610f3b5750815b8083039250610f5f8c8c838986600c8110610f5257fe5b602002015163ffffffff16565b50505b600101610ecc565b50505050505b505050505050565b600080516020613cbb83398151915281565b600481565b733d9819210a31b4961b30ef54be2aed79b9c9cd3b81565b6000546001600160a01b031681565b600081610fcb6001600160a01b038616612953565b6110fb57604080516303795fb160e11b81526001600160a01b0387166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b15801561102d57600080fd5b505afa158015611041573d6000803e3d6000fd5b505050506040513d602081101561105757600080fd5b505190506001600160a01b038116156110f957611074868261298f565b604080516395e3c50b60e01b8152600481018490526001602482015242604482015290516001600160a01b038316916395e3c50b9160648083019260209291908290030181600087803b1580156110ca57600080fd5b505af11580156110de573d6000803e3d6000fd5b505050506040513d60208110156110f457600080fd5b505191505b505b61110d846001600160a01b0316612953565b61122d57604080516303795fb160e11b81526001600160a01b0386166004820152905160009173c0a47dfe034b400b47bdad5fecda2621de6c4d95916306f2bf6291602480820192602092909190829003018186803b15801561116f57600080fd5b505afa158015611183573d6000803e3d6000fd5b505050506040513d602081101561119957600080fd5b505190506001600160a01b0381161561122b57806001600160a01b031663f39b5b9b836001426040518463ffffffff1660e01b815260040180838152602001828152602001925050506020604051808303818588803b1580156111fb57600080fd5b505af115801561120f573d6000803e3d6000fd5b50505050506040513d602081101561122657600080fd5b505191505b505b90505b9392505050565b60006112578473818e6fecd516ecc3849daf6845e3ec868087b75561298f565b73818e6fecd516ecc3849daf6845e3ec868087b7556329589f616112836001600160a01b038716612953565b61128e576000611290565b835b6112a2876001600160a01b0316612953565b6112ac57866112c2565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b856112d5886001600160a01b0316612953565b6112df57876112f5565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee5b604080516001600160e01b031960e088901b1681526001600160a01b039485166004820152602481019390935292166044820152306064820152600160ff1b6084820152600060a48201819052734d37f28d2db99e8d35a6c725a5f1749a085850a360c483015261010060e4830152610104820152905161014480830192602092919082900301818588803b15801561138d57600080fd5b505af11580156113a1573d6000803e3d6000fd5b50505050506040513d60208110156113b857600080fd5b5051949350505050565b60006113d6846001600160a01b0316612953565b156114445773c0829421c1d260bd3cb3e0f06cfe2d52db2ce3156001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801561142a57600080fd5b505af115801561143e573d6000803e3d6000fd5b50505050505b60007352ae12abe5d8bd778bd5397f99ca900624cfadd46001600160a01b031663bb34534c6040518163ffffffff1660e01b815260040180806c42616e636f724e6574776f726b60981b815250602001905060206040518083038186803b1580156114ae57600080fd5b505afa1580156114c2573d6000803e3d6000fd5b505050506040513d60208110156114d857600080fd5b5051905060606114e88686612a48565b90506115256114ff876001600160a01b0316612953565b611509578661151f565b73c0829421c1d260bd3cb3e0f06cfe2d52db2ce3155b8361298f565b6000826001600160a01b031663c7ba24bc838760016040518463ffffffff1660e01b81526004018080602001848152602001838152602001828103825285818151815260200191508051906020019060200280838360005b8381101561159557818101518382015260200161157d565b50505050905001945050505050602060405180830381600087803b1580156115bc57600080fd5b505af11580156115d0573d6000803e3d6000fd5b505050506040513d60208110156115e657600080fd5b505190506115fc6001600160a01b038716612953565b156116dd57604080516370a0823160e01b8152306004820152905173c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561165a57600080fd5b505afa15801561166e573d6000803e3d6000fd5b505050506040513d602081101561168457600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b1580156116c457600080fd5b505af11580156116d8573d6000803e3d6000fd5b505050505b9695505050505050565b60006116fb846001600160a01b0316612953565b156117695773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26001600160a01b031663d0e30db0836040518263ffffffff1660e01b81526004016000604051808303818588803b15801561174f57600080fd5b505af1158015611763573d6000803e3d6000fd5b50505050505b6117b861177e856001600160a01b0316612953565b611788578461179e565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b73794e6e91555438afc3ccf1c5076a74f42133d08d61298f565b600073794e6e91555438afc3ccf1c5076a74f42133d08d630621b4f66117e66001600160a01b038816612953565b6117f05786611806565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b85611819886001600160a01b0316612953565b6118235787611839565b73c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25b604080516001600160e01b031960e087901b1681526001600160a01b03948516600482015260248101939093529216604482015260016064820152905160848083019260209291908290030181600087803b15801561189757600080fd5b505af11580156118ab573d6000803e3d6000fd5b505050506040513d60208110156118c157600080fd5b505190506118d76001600160a01b038516612953565b1561122d57604080516370a0823160e01b8152306004820152905173c02aaa39b223fe8d0a0e5c4f27ead9083c756cc291632e1a7d4d9183916370a08231916024808301926020929190829003018186803b15801561193557600080fd5b505afa158015611949573d6000803e3d6000fd5b505050506040513d602081101561195f57600080fd5b5051604080516001600160e01b031960e085901b168152600481019290925251602480830192600092919082900301818387803b15801561199f57600080fd5b505af11580156119b3573d6000803e3d6000fd5b5050505090509392505050565b6000806001600160a01b038516600080516020613d2b833981519152146119e85760006119eb565b60025b6001600160a01b038616600080516020613cbb83398151915214611a10576000611a13565b60015b0160ff1690506000600080516020613d2b8339815191526001600160a01b03861614611a40576000611a43565b60025b6001600160a01b038616600080516020613cbb83398151915214611a68576000611a6b565b60015b0160ff16905081600f0b60001480611a86575080600f0b6000145b15611a9657600092505050611230565b611ab48673a2b47e3d5c44877cca798226b7b8118f9bfb7a5661298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a2b47e3d5c44877cca798226b7b8118f9bfb7a569263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b505af1158015611b3e573d6000803e3d6000fd5b5050505050509392505050565b6000806001600160a01b03851673dac17f958d2ee523a2206206994597c13d831ec714611b79576000611b7c565b60035b6001600160a01b038616600080516020613d2b83398151915214611ba1576000611ba4565b60025b6001600160a01b038716600080516020613cbb83398151915214611bc9576000611bcc565b60015b010160ff169050600073dac17f958d2ee523a2206206994597c13d831ec76001600160a01b0316856001600160a01b031614611c09576000611c0c565b60035b6001600160a01b038616600080516020613d2b83398151915214611c31576000611c34565b60025b6001600160a01b038716600080516020613cbb83398151915214611c59576000611c5c565b60015b010160ff16905081600f0b60001480611c78575080600f0b6000145b15611c8857600092505050611230565b611ca6867352ea46506b9cc5ef470c5bf89f17dc28bb35d85c61298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517352ea46506b9cc5ef470c5bf89f17dc28bb35d85c9263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b0385166e085d4780b73119b644ae5ecd22b37614611d45576000611d48565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611d73576000611d76565b60035b6001600160a01b038716600080516020613d2b83398151915214611d9b576000611d9e565b60025b6001600160a01b038816600080516020613cbb83398151915214611dc3576000611dc6565b60015b01010160ff16905060006e085d4780b73119b644ae5ecd22b3766001600160a01b0316856001600160a01b031614611dff576000611e02565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611e2d576000611e30565b60035b6001600160a01b038716600080516020613d2b83398151915214611e55576000611e58565b60025b6001600160a01b038816600080516020613cbb83398151915214611e7d576000611e80565b60015b01010160ff16905081600f0b60001480611e9d575080600f0b6000145b15611ead57600092505050611230565b611ecb867345f783cce6b7ff23b2ab2d70e416cdb7d6055f5161298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517345f783cce6b7ff23b2ab2d70e416cdb7d6055f519263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b038516734fabb145d64652a948d72533023f6e7a623c7c5314611f6f576000611f72565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec714611f9d576000611fa0565b60035b6001600160a01b038716600080516020613d2b83398151915214611fc5576000611fc8565b60025b6001600160a01b038816600080516020613cbb83398151915214611fed576000611ff0565b60015b01010160ff1690506000734fabb145d64652a948d72533023f6e7a623c7c536001600160a01b0316856001600160a01b03161461202e576000612031565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec71461205c57600061205f565b60035b6001600160a01b038716600080516020613d2b83398151915214612084576000612087565b60025b6001600160a01b038816600080516020613cbb833981519152146120ac5760006120af565b60015b01010160ff16905081600f0b600014806120cc575080600f0b6000145b156120dc57600092505050611230565b6120fa867379a8c46dea5ada233abaffd40f3a0a2b1e5a4f2761298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b60248201526044810186905260006064820181905291517379a8c46dea5ada233abaffd40f3a0a2b1e5a4f279263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b6000806001600160a01b0385167357ab1ec28d129707052df4df418d58a2d46d5f511461219e5760006121a1565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec7146121cc5760006121cf565b60035b6001600160a01b038716600080516020613d2b833981519152146121f45760006121f7565b60025b6001600160a01b038816600080516020613cbb8339815191521461221c57600061221f565b60015b01010160ff16905060007357ab1ec28d129707052df4df418d58a2d46d5f516001600160a01b0316856001600160a01b03161461225d576000612260565b60045b6001600160a01b03861673dac17f958d2ee523a2206206994597c13d831ec71461228b57600061228e565b60035b6001600160a01b038716600080516020613d2b833981519152146122b35760006122b6565b60025b6001600160a01b038816600080516020613cbb833981519152146122db5760006122de565b60015b01010160ff16905081600f0b600014806122fb575080600f0b6000145b1561230b57600092505050611230565b6123298673a5407eae9ba41422680e2e00537571bcc53efbfd61298f565b60408051635320bf6b60e11b8152600019808501600f90810b810b6004840152908401810b900b602482015260448101869052600060648201819052915173a5407eae9ba41422680e2e00537571bcc53efbfd9263a6417ed6926084808201939182900301818387803b158015611b2a57600080fd5b60006123b3846001600160a01b0316612953565b61246c5760006123c285613152565b90506123ce858261298f565b806001600160a01b031663a0712d68846040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b15801561241457600080fd5b505af1158015612428573d6000803e3d6000fd5b505050506040513d602081101561243e57600080fd5b506124649050818561245f6001600160a01b0383163063ffffffff61334216565b610fb6565b915050611230565b61247e836001600160a01b0316612953565b61253357600061248d84613152565b9050600061249c868386610fb6565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050602060405180830381600087803b1580156124e457600080fd5b505af11580156124f8573d6000803e3d6000fd5b505050506040513d602081101561250e57600080fd5b5061252a90506001600160a01b0386163063ffffffff61334216565b92505050611230565b5060009392505050565b60006001600160a01b038416600080516020613cbb83398151915214156126205761257c847306af07097c9eeb7fd685c692751d5c66db49c21561298f565b60408051633b4da69f60e01b81523060048201526024810184905290517306af07097c9eeb7fd685c692751d5c66db49c21591633b4da69f91604480830192600092919082900301818387803b1580156125d557600080fd5b505af11580156125e9573d6000803e3d6000fd5b5061261992507306af07097c9eeb7fd685c692751d5c66db49c215915085905061245f823063ffffffff61334216565b9050611230565b6001600160a01b038316600080516020613cbb8339815191521415612533576000612660857306af07097c9eeb7fd685c692751d5c66db49c21585610fb6565b6040805163ef693bed60e01b81523060048201526024810183905290519192507306af07097c9eeb7fd685c692751d5c66db49c2159163ef693bed9160448082019260009290919082900301818387803b1580156126bd57600080fd5b505af11580156126d1573d6000803e3d6000fd5b50612464925050506001600160a01b03851630613342565b60006126fd846001600160a01b0316612953565b6127bb57600061270c856133ec565b9050612718858261298f565b60408051636968703360e11b81526001600160a01b03871660048201526024810185905261044d6044820152905173398ec7346dcd622edc5ae82352f02be94c62d1199163d2d0e06691606480830192600092919082900301818387803b15801561278257600080fd5b505af1158015612796573d6000803e3d6000fd5b50505050612464818561245f30856001600160a01b031661334290919063ffffffff16565b6127cd836001600160a01b0316612953565b6125335760006127dc846133ec565b905060006127eb868386610fb6565b9050816001600160a01b031663db006a75826040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b15801561283357600080fd5b505af1158015612847573d6000803e3d6000fd5b505050508092505050611230565b6000828201838110156128af576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b6000826128c7575060006128b2565b828202828482816128d457fe5b04146128af5760405162461bcd60e51b8152600401808060200182810382526021815260200180613d0a6021913960400191505060405180910390fd5b60006128af83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f00000000000081525061380f565b60006001600160a01b038216158061298757506001600160a01b03821673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee145b90505b919050565b6129a1826001600160a01b0316612953565b612a445760408051636eb1769f60e11b81523060048201526001600160a01b038381166024830152915160ff9285169163dd62ed3e916044808301926020929190829003018186803b1580156129f657600080fd5b505afa158015612a0a573d6000803e3d6000fd5b505050506040513d6020811015612a2057600080fd5b5051901c612a4457612a446001600160a01b0383168260001963ffffffff6138b116565b5050565b6060816001600160a01b0316836001600160a01b03161415612a7957506040805160008152602081019091526128b2565b612a8b836001600160a01b0316612953565b15612aa85773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31592505b612aba826001600160a01b0316612953565b15612ad75773c0829421c1d260bd3cb3e0f06cfe2d52db2ce31591505b6001600160a01b038316731f573d6fb3f13d689ff844b4ce37794d79a7ff1c1480612b1e57506001600160a01b038216731f573d6fb3f13d689ff844b4ce37794d79a7ff1c145b15612b4957604080516003808252608082019092529060208201606080388339019050509050612b6b565b60408051600580825260c08201909252906020820160a0803883390190505090505b6000806001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14612d34576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b612bc86001600160a01b038b16612953565b612bd25789612be8565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310612c665780518252601f199092019160209182019101612c47565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d8060008114612cc7576040519150601f19603f3d011682016040523d82523d6000602084013e612ccc565b606091505b509150915081612cf45760408051600080825260208201909252905b509450505050506128b2565b808060200190516020811015612d0957600080fd5b505193506001600160a01b038416612d31576040805160008082526020820190925290612ce8565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c14612ef2576000606073f6e2d7f616b67e46d708e4410746e9aab3a4c518612710636b625ad960e11b612d8e6001600160a01b038a16612953565b612d985788612dae565b731f573d6fb3f13d689ff844b4ce37794d79a7ff1c5b604080516001600160a01b039092166024830152600060448084019190915281518084039091018152606490920181526020820180516001600160e01b03166001600160e01b0319909416939093178352518151919290918291908083835b60208310612e2c5780518252601f199092019160209182019101612e0d565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303818686fa925050503d8060008114612e8d576040519150601f19603f3d011682016040523d82523d6000602084013e612e92565b606091505b509150915081612eb2576040805160008082526020820190925290612ce8565b808060200190516020811015612ec757600080fd5b505192506001600160a01b038316612eef576040805160008082526020820190925290612ce8565b50505b6001600160a01b038416731f573d6fb3f13d689ff844b4ce37794d79a7ff1c1415612fb5578483600081518110612f2557fe5b60200260200101906001600160a01b031690816001600160a01b0316815250508183600181518110612f5357fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c83600281518110612f9557fe5b6001600160a01b0390921660209283029190910190910152506128b29050565b6001600160a01b038516731f573d6fb3f13d689ff844b4ce37794d79a7ff1c141561305857731f573d6fb3f13d689ff844b4ce37794d79a7ff1c83600081518110612ffc57fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360018151811061302a57fe5b60200260200101906001600160a01b031690816001600160a01b0316815250508383600281518110612f9557fe5b848360008151811061306657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050818360018151811061309457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050731f573d6fb3f13d689ff844b4ce37794d79a7ff1c836002815181106130d657fe5b60200260200101906001600160a01b031690816001600160a01b031681525050808360038151811061310457fe5b60200260200101906001600160a01b031690816001600160a01b031681525050838360048151811061313257fe5b6001600160a01b0390921660209283029190910190910152505092915050565b6000613166826001600160a01b0316612953565b156131865750734ddc2d193948926d02f9b1fe9e1daa0718270ed561298a565b6001600160a01b038216600080516020613cbb83398151915214156131c05750735d3a536e4d6dbd6114cc1ead35777bab948e364361298a565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef14156132005750736c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e61298a565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e8621415613240575073158079ee67fce2f58472a96584a73c7ab9ac95c161298a565b6001600160a01b038216600080516020613d2b833981519152141561327a57507339aa39c021dfbae8fac545936693ac917d5e756361298a565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c59914156132ba575073c11b1268c1a384e55c48c2391d8d480264a3a7f461298a565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f49814156132fa575073b3319f5d18bc0d84dd1b4825dcde5d5f7266d40761298a565b6001600160a01b03821673dac17f958d2ee523a2206206994597c13d831ec7141561333a575073f650c3d88d12db855b8bf7d11be6c55a4e07dcc961298a565b506000919050565b600061334d83612953565b1561336357506001600160a01b038116316128b2565b826001600160a01b03166370a08231836040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b0316815260200191505060206040518083038186803b1580156133b957600080fd5b505afa1580156133cd573d6000803e3d6000fd5b505050506040513d60208110156133e357600080fd5b505190506128b2565b6000613400826001600160a01b0316612953565b156134205750733a3a65aab0dd2a17e3f1947ba16138cd37d08c0461298a565b6001600160a01b038216600080516020613cbb833981519152141561345a575073fc1e690f61efd961294b3e1ce3313fbd8aa4f85d61298a565b6001600160a01b038216600080516020613d2b83398151915214156134945750739ba00d6856a4edf4665bca2c2309936572473b7e61298a565b6001600160a01b0382167357ab1ec28d129707052df4df418d58a2d46d5f5114156134d4575073625ae63000f46200499120b906716420bd05924061298a565b6001600160a01b038216734fabb145d64652a948d72533023f6e7a623c7c5314156135145750736ee0f7bb50a54ab5253da0667b0dc2ee526c30a861298a565b6001600160a01b0382166e085d4780b73119b644ae5ecd22b376141561354f5750734da9b813057d04baef4e5800e36083717b4a034161298a565b6001600160a01b03821673dac17f958d2ee523a2206206994597c13d831ec7141561358f57507371fc860f7d3a592a4a98740e39db31d25db65ae861298a565b6001600160a01b038216730d8775f648430679a709e98d2b0cb6250d2887ef14156135cf575073e1ba0fb44ccb0d11b80f92f4f8ed94ca3ff51d0061298a565b6001600160a01b03821673dd974d5c2e2928dea5f71b9825b8b646686bd200141561360f5750739d91be44c06d373a8a226e1f3b146956083803eb61298a565b6001600160a01b0382167380fb784b7ed66730e8b1dbd9820afd29931aab03141561364f5750737d2d3688df45ce7c552e19c27e007673da9204b861298a565b6001600160a01b03821673514910771af9ca656af840dff83e8264ecf986ca141561368f575073a64bd6c70cb9051f6a9ba1f163fdc07e0dfb5f8461298a565b6001600160a01b038216730f5d2fb29fb7d3cfee444a200298f468908cc94214156136cf5750736fce4a401b6b80ace52baaefe4421bd188e76f6f61298a565b6001600160a01b038216739f8f72aa9304c8b593d555f12ef6589cc3a579a2141561370f5750737deb5e830be29f91e298ba5ff1356bb7f814699861298a565b6001600160a01b038216731985365e9f78359a9b6ad760e32412f4a445e862141561374f57507371010a9d003445ac60c4e6a7017c1e89a477b43861298a565b6001600160a01b03821673c011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f141561378f575073328c4c80bc7aca0834db37e6600a6c49e12da4de61298a565b6001600160a01b038216732260fac5e5542a773aa44fbcfedf7c193bc2c59914156137cf575073fc4b8ed459e00e5400be803a9bb3954234fd50e361298a565b6001600160a01b03821673e41d2489571d322189246dafa5ebde1f4699f498141561333a5750736fb0855c404e09c47c3fbca25f08d4e41f9f062f61298a565b6000818361389b5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015613860578181015183820152602001613848565b50505050905090810190601f16801561388d5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385816138a757fe5b0495945050505050565b6138ba83612953565b61398257600081118015613948575060408051636eb1769f60e11b81523060048201526001600160a01b038481166024830152915160009286169163dd62ed3e916044808301926020929190829003018186803b15801561391a57600080fd5b505afa15801561392e573d6000803e3d6000fd5b505050506040513d602081101561394457600080fd5b5051115b15613968576139686001600160a01b03841683600063ffffffff61398716565b6139826001600160a01b038416838363ffffffff61398716565b505050565b801580613a0d575060408051636eb1769f60e11b81523060048201526001600160a01b03848116602483015291519185169163dd62ed3e91604480820192602092909190829003018186803b1580156139df57600080fd5b505afa1580156139f3573d6000803e3d6000fd5b505050506040513d6020811015613a0957600080fd5b5051155b613a485760405162461bcd60e51b8152600401808060200182810382526036815260200180613d756036913960400191505060405180910390fd5b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663095ea7b360e01b179052613982908490613aa7826001600160a01b0316613c53565b613af8576040805162461bcd60e51b815260206004820152601f60248201527f5361666545524332303a2063616c6c20746f206e6f6e2d636f6e747261637400604482015290519081900360640190fd5b60006060836001600160a01b0316836040518082805190602001908083835b60208310613b365780518252601f199092019160209182019101613b17565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114613b98576040519150601f19603f3d011682016040523d82523d6000602084013e613b9d565b606091505b509150915081613bf4576040805162461bcd60e51b815260206004820181905260248201527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564604482015290519081900360640190fd5b805115613c4d57808060200190516020811015613c1057600080fd5b5051613c4d5760405162461bcd60e51b815260040180806020018281038252602a815260200180613d4b602a913960400191505060405180910390fd5b50505050565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590613c8757508115155b949350505050565b604051806101800160405280600c905b613cb8815260200190600190039081613c9f5790505090565bfefe0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f4f6e6553706c69743a20646973747269627574696f6e2073686f756c6420636f6e7461696e206e6f6e2d7a65726f73536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb485361666545524332303a204552433230206f7065726174696f6e20646964206e6f7420737563636565645361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f20746f206e6f6e2d7a65726f20616c6c6f77616e63654f6e6553706c69743a20446973747269627574696f6e2061727261792073686f756c64206e6f74206578636565642072657365727665732061727261792073697a65a265627a7a72315820b7b106c8ffa502d2e0dd6598694f6eb143e8f1d38bb5268aaee7f0681a39891b64736f6c63430005100032 diff --git a/OneSplit.full.sol b/OneSplit.full.sol index 0b13254..a53fd98 100644 --- a/OneSplit.full.sol +++ b/OneSplit.full.sol @@ -3780,22 +3780,521 @@ contract OneSplitWeth is OneSplitBaseWrap { } } -// File: contracts/OneSplit.sol +// File: contracts/interface/ISmartTokenConverter.sol + +pragma solidity ^0.5.0; +//pragma experimental ABIEncoderV2; + + +interface ISmartTokenConverter { + + struct Reserve { + uint256 virtualBalance; // reserve virtual balance + uint32 ratio; // reserve ratio, represented in ppm, 1-1000000 + bool isVirtualBalanceEnabled; // true if virtual balance is enabled, false if not + bool isSaleEnabled; // is sale of the reserve token enabled, can be set by the owner + bool isSet; // used to tell if the mapping element is defined + } + + function version() external view returns (uint16); + + function reserves(address) external view returns (Reserve memory); + + function getReserveRatio(IERC20 token) external view returns (uint256); + + function connectorTokenCount() external view returns (uint256); + + function connectorTokens(uint256 i) external view returns (IERC20); + + function liquidate(uint256 _amount) external; + + function fund(uint256 _amount) external; + + function convert2(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn, address _affiliateAccount, uint256 _affiliateFee) external returns (uint256); + + function convert(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn) external returns (uint256); + +} + +// File: contracts/interface/ISmartToken.sol + +pragma solidity ^0.5.0; + + + + +interface ISmartToken { + function owner() external view returns (ISmartTokenConverter); +} + +// File: contracts/interface/ISmartTokenRegistry.sol + +pragma solidity ^0.5.0; + + + +interface ISmartTokenRegistry { + function isSmartToken(IERC20 token) external view returns (bool); +} + +// File: contracts/interface/ISmartTokenFormula.sol + +pragma solidity ^0.5.0; + + + +interface ISmartTokenFormula { + function calculateLiquidateReturn( + uint256 supply, + uint256 reserveBalance, + uint32 totalRatio, + uint256 amount + ) external view returns (uint256); + + function calculatePurchaseReturn( + uint256 supply, + uint256 reserveBalance, + uint32 totalRatio, + uint256 amount + ) external view returns (uint256); +} + +// File: contracts/OneSplitSmartToken.sol pragma solidity ^0.5.0; +//pragma experimental ABIEncoderV2; + + + + + + +contract OneSplitSmartTokenBase { + using SafeMath for uint256; + + ISmartTokenRegistry smartTokenRegistry = ISmartTokenRegistry(0xf6E2D7F616B67E46D708e4410746E9AAb3a4C518); + ISmartTokenFormula smartTokenFormula = ISmartTokenFormula(0x524619EB9b4cdFFa7DA13029b33f24635478AFc0); + + struct TokenWithRatio { + IERC20 token; + uint256 ratio; + } + + struct SmartTokenDetails { + TokenWithRatio[] reserveTokenList; + address converter; + uint256 totalReserveTokensRatio; + } + + function _getSmartTokenDetails(ISmartToken smartToken) internal view returns (SmartTokenDetails memory details) { + ISmartTokenConverter converter = smartToken.owner(); + (TokenWithRatio[] memory reserveTokenList, uint256 totalReserveTokensRatio) = _getTokens(converter); + + details.reserveTokenList = reserveTokenList; + details.converter = address(converter); + details.totalReserveTokensRatio = totalReserveTokensRatio; + + return details; + } + + function _getTokens( + ISmartTokenConverter converter + ) + internal + view + returns(TokenWithRatio[] memory reserveTokenList, uint256 totalRatio) + { + reserveTokenList = new TokenWithRatio[](converter.connectorTokenCount()); + for (uint256 i = 0; i < reserveTokenList.length; i++) { + reserveTokenList[i].token = converter.connectorTokens(i); + reserveTokenList[i].ratio = _getReserveRatio(converter, reserveTokenList[i].token); + totalRatio = totalRatio.add(reserveTokenList[i].ratio); + } + return (reserveTokenList, totalRatio); + } + + function _getReserveRatio( + ISmartTokenConverter converter, + IERC20 token + ) + internal + view + returns (uint256) + { + if (converter.version() >= 22) { + return converter.getReserveRatio(token); + } + + return uint256(converter.reserves(address(token)).ratio); + } + + function _calcExchangeAmount(uint256 amount, uint256 ratio, uint256 totalRatio) internal pure returns (uint256) { + return amount.mul(ratio).div(totalRatio); + } + +} + + +contract OneSplitSmartTokenView is OneSplitBaseView, OneSplitSmartTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + public + view + returns( + uint256, + uint256[] memory + ) + { + + if (fromToken == toToken) { + return (amount, new uint256[](9)); + } + + if (!disableFlags.check(FLAG_DISABLE_SMART_TOKEN)) { + + if (smartTokenRegistry.isSmartToken(fromToken)) { + + return _getExpectedReturnFromSmartToken( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + + } + + if (smartTokenRegistry.isSmartToken(toToken)) { + + return _getExpectedReturnToSmartToken( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + } + + function _getExpectedReturnFromSmartToken( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](9); + + SmartTokenDetails memory smartTokenDetails = _getSmartTokenDetails(ISmartToken(address(fromToken))); + + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( + fromToken.totalSupply(), + smartTokenDetails.reserveTokenList[i].token.balanceOf(smartTokenDetails.converter), + uint32(smartTokenDetails.totalReserveTokensRatio), + amount + ); + + (uint256 ret, uint256[] memory dist) = super.getExpectedReturn( + smartTokenDetails.reserveTokenList[i].token, + toToken, + srcAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] = distribution[j].add(dist[j] << (i * 8)); + } + } + return (returnAmount, distribution); + } + + function _getExpectedReturnToSmartToken( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](9); + minFundAmount = uint256(-1); + + SmartTokenDetails memory smartTokenDetails = _getSmartTokenDetails(ISmartToken(address(toToken))); + + uint256[] memory fundAmounts = new uint256[](smartTokenDetails.reserveTokenList.length); + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + + uint256 exchangeAmount = _calcExchangeAmount( + amount, + smartTokenDetails.reserveTokenList[i].ratio, + smartTokenDetails.totalReserveTokensRatio + ); + + (uint256 tokenAmount, uint256[] memory dist) = super.getExpectedReturn( + fromToken, + smartTokenDetails.reserveTokenList[i].token, + exchangeAmount, + parts, + disableFlags | FLAG_DISABLE_BANCOR + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] = distribution[j].add(dist[j] << (i * 8)); + } + + fundAmounts[i] = toToken.totalSupply() + .mul(tokenAmount) + .div(smartTokenDetails.reserveTokenList[i].token.balanceOf(smartTokenDetails.converter)); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + + // Swap leftovers for SmartToken + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + uint256 reserveBalance = smartTokenDetails.reserveTokenList[i].token.balanceOf(smartTokenDetails.converter); + + uint256 leftover = fundAmounts[i].sub(minFundAmount) + .mul(reserveBalance) + .div(toToken.totalSupply()); + + if (leftover > 0) { + minFundAmount = minFundAmount.add( + smartTokenFormula.calculatePurchaseReturn( + toToken.totalSupply(), + reserveBalance, + uint32(smartTokenDetails.totalReserveTokensRatio), + leftover + ) + ); + } + } + return (minFundAmount, distribution); + } + +} +contract OneSplitSmartToken is OneSplitBase, OneSplitSmartTokenBase { + // todo: think about smart tokens with one token in reserve + // todo: think about the case when toToken == reserveToken + // todo: think about the case when fromToken == reserveToken + // todo: think about the case when fromToken == smartToken1, toToken == smartToken2 + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) internal { + if (fromToken == toToken) { + return; + } + if (!disableFlags.check(FLAG_DISABLE_SMART_TOKEN)) { + if (smartTokenRegistry.isSmartToken(fromToken)) { + return _swapFromSmartToken( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + if (smartTokenRegistry.isSmartToken(toToken)) { + return _swapToSmartToken( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); -//import "./OneSplitSmartToken.sol"; + } + } + return super._swap( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + + function _swapFromSmartToken( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + SmartTokenDetails memory smartTokenDetails = _getSmartTokenDetails(ISmartToken(address(fromToken))); + + uint256[] memory tokenBalanceBefore = new uint256[](smartTokenDetails.reserveTokenList.length); + uint256[][] memory dist = new uint256[][](smartTokenDetails.reserveTokenList.length); + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + dist[i] = new uint256[](distribution.length); + + tokenBalanceBefore[i] = smartTokenDetails.reserveTokenList[i].token.balanceOf(msg.sender); + + for (uint j = 0; j < distribution.length; j++) { + dist[i][j] = (distribution[j] >> (i * 8)) & 0xFF; + } + } + + ISmartTokenConverter(smartTokenDetails.converter).liquidate(amount); + + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + uint256 tokenBalanceAfter = smartTokenDetails.reserveTokenList[i].token.balanceOf(msg.sender); + + return super._swap( + smartTokenDetails.reserveTokenList[i].token, + toToken, + tokenBalanceAfter.sub(tokenBalanceBefore[i]), + dist[i], + disableFlags + ); + } + + } + + function _swapToSmartToken( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + uint256 minFundAmount = uint256(-1); + + SmartTokenDetails memory smartTokenDetails = _getSmartTokenDetails(ISmartToken(address(toToken))); + + uint256[] memory fundAmounts = new uint256[](smartTokenDetails.reserveTokenList.length); + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + + uint256 exchangeAmount = _calcExchangeAmount( + amount, + smartTokenDetails.reserveTokenList[i].ratio, + smartTokenDetails.totalReserveTokensRatio + ); + + uint256 tokenBalanceBefore = smartTokenDetails.reserveTokenList[i].token.balanceOf(msg.sender); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + super._swap( + fromToken, + smartTokenDetails.reserveTokenList[i].token, + exchangeAmount, + distribution, + disableFlags + ); + + uint256 tokenBalanceAfter = smartTokenDetails.reserveTokenList[i].token.balanceOf(msg.sender); + + fundAmounts[i] = toToken.totalSupply() + .mul(tokenBalanceAfter.sub(tokenBalanceBefore)) + .div(smartTokenDetails.reserveTokenList[i].token.balanceOf(smartTokenDetails.converter)); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + + _infiniteApproveIfNeeded(smartTokenDetails.reserveTokenList[i].token, smartTokenDetails.converter); + } + + ISmartTokenConverter(smartTokenDetails.converter).fund(minFundAmount); + + // Swap leftovers for SmartToken + for (uint16 i = 0; i < smartTokenDetails.reserveTokenList.length; i++) { + uint256 reserveBalance = smartTokenDetails.reserveTokenList[i].token.balanceOf(smartTokenDetails.converter); + + uint256 leftover = fundAmounts[i].sub(minFundAmount) + .mul(reserveBalance) + .div(toToken.totalSupply()); + + if (leftover > 0) { + + convert( + ISmartTokenConverter(smartTokenDetails.converter), + smartTokenDetails.reserveTokenList[i].token, + toToken, + leftover + ); + + } + } + + } + + function convert( + ISmartTokenConverter converter, + IERC20 _fromToken, + IERC20 _toToken, + uint256 _amount + ) + private + returns (uint256) + { + // todo: think about minReturn, affiliateAccount, affiliateFee + if (converter.version() >= 16) { + return converter.convert2(_fromToken, _toToken, _amount, 0, address(0), 0); + } + + return converter.convert(_fromToken, _toToken, _amount, 0); + } + +} + +// File: contracts/OneSplit.sol + +pragma solidity ^0.5.0; +pragma experimental ABIEncoderV2; contract OneSplitViewWrap is OneSplitViewWrapBase, @@ -3808,7 +4307,7 @@ contract OneSplitViewWrap is OneSplitIearnView, OneSplitIdleView, OneSplitWethView - //OneSplitSmartTokenView + OneSplitSmartTokenView { IOneSplitView public oneSplitView; @@ -3879,7 +4378,7 @@ contract OneSplitWrap is OneSplitIearn, OneSplitIdle, OneSplitWeth - //OneSplitSmartToken + OneSplitSmartToken { IOneSplitView public oneSplitView; IOneSplit public oneSplit; diff --git a/contracts/IOneSplit.sol b/contracts/IOneSplit.sol index 3703cef..c20050a 100644 --- a/contracts/IOneSplit.sol +++ b/contracts/IOneSplit.sol @@ -49,8 +49,33 @@ contract IOneSplitConsts { uint256 public constant FLAG_ENABLE_UNISWAP_CHAI = 0x200000; // Works only when ETH<>DAI or FLAG_ENABLE_MULTI_PATH_ETH uint256 public constant FLAG_ENABLE_UNISWAP_AAVE = 0x400000; // Works only when one of assets is ETH or FLAG_ENABLE_MULTI_PATH_ETH uint256 public constant FLAG_DISABLE_IDLE = 0x800000; -} + uint256 public constant FLAG_DISABLE_UNISWAP_POOL_TOKEN = 0x1000000; + uint256 public constant FLAG_DISABLE_BALANCER_POOL_TOKEN = 0x2000000; + uint256 public constant FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN = 0x4000000; + + uint256 public constant FLAG_DISABLE_ALL_SOURCES = + FLAG_DISABLE_UNISWAP | + FLAG_DISABLE_KYBER | + FLAG_DISABLE_BANCOR | + FLAG_DISABLE_OASIS | + FLAG_DISABLE_CURVE_COMPOUND | + FLAG_DISABLE_CURVE_USDT | + FLAG_DISABLE_CURVE_Y | + FLAG_DISABLE_CURVE_BINANCE | + FLAG_DISABLE_CURVE_SYNTHETIX; + + uint256 public constant FLAG_DISABLE_ALL_WRAPPERS = + FLAG_DISABLE_COMPOUND | + FLAG_DISABLE_FULCRUM | + FLAG_DISABLE_CHAI | + FLAG_DISABLE_AAVE | + FLAG_DISABLE_SMART_TOKEN | + FLAG_DISABLE_BDAI | + FLAG_DISABLE_IEARN | + FLAG_DISABLE_WETH; + uint256 public constant FLAG_DISABLE_ALL = FLAG_DISABLE_ALL_SOURCES | FLAG_DISABLE_ALL_WRAPPERS; +} contract IOneSplit is IOneSplitConsts { function getExpectedReturn( diff --git a/contracts/OneSplit.sol b/contracts/OneSplit.sol index 22c1b0c..5386118 100644 --- a/contracts/OneSplit.sol +++ b/contracts/OneSplit.sol @@ -11,7 +11,10 @@ import "./OneSplitIearn.sol"; import "./OneSplitIdle.sol"; import "./OneSplitAave.sol"; import "./OneSplitWeth.sol"; -//import "./OneSplitSmartToken.sol"; +import "./OneSplitBalancerPoolToken.sol"; +import "./OneSplitUniswapPoolToken.sol"; +import "./OneSplitCurveSusdPoolToken.sol"; +import "./OneSplitSmartToken.sol"; contract OneSplitViewWrap is @@ -24,8 +27,11 @@ contract OneSplitViewWrap is OneSplitCompoundView, OneSplitIearnView, OneSplitIdleView, - OneSplitWethView - //OneSplitSmartTokenView + OneSplitWethView, + OneSplitBalancerPoolTokenView, + OneSplitUniswapPoolTokenView, + OneSplitCurveSusdPoolTokenView + OneSplitSmartTokenView { IOneSplitView public oneSplitView; @@ -95,8 +101,11 @@ contract OneSplitWrap is OneSplitCompound, OneSplitIearn, OneSplitIdle, - OneSplitWeth - //OneSplitSmartToken + OneSplitWeth, + OneSplitBalancerPoolToken, + OneSplitUniswapPoolToken, + OneSplitCurveSusdPoolToken + OneSplitSmartToken { IOneSplitView public oneSplitView; IOneSplit public oneSplit; @@ -142,14 +151,19 @@ contract OneSplitWrap is uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] uint256 flags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI ) public payable { - fromToken.universalTransferFrom(msg.sender, address(this), amount); + if (msg.sender != address(this)) { + fromToken.universalTransferFrom(msg.sender, address(this), amount); + } _swap(fromToken, toToken, amount, distribution, flags); uint256 returnAmount = toToken.universalBalanceOf(address(this)); require(returnAmount >= minReturn, "OneSplit: actual return amount is less than minReturn"); - toToken.universalTransfer(msg.sender, returnAmount); - fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + + if (msg.sender != address(this)) { + toToken.universalTransfer(msg.sender, returnAmount); + fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + } } function _swapFloor( diff --git a/contracts/OneSplitBalancerPoolToken.sol b/contracts/OneSplitBalancerPoolToken.sol new file mode 100644 index 0000000..3f91a5c --- /dev/null +++ b/contracts/OneSplitBalancerPoolToken.sol @@ -0,0 +1,449 @@ +pragma solidity ^0.5.0; + +import "./OneSplitBase.sol"; +import "./interface/IBFactory.sol"; +import "./interface/IBPool.sol"; + + +contract OneSplitBalancerPoolTokenBase { + using SafeMath for uint256; + + // todo: factory for Bronze release + // may be changed in future + IBFactory bFactory = IBFactory(0x9424B1412450D0f8Fc2255FAf6046b98213B76Bd); + + struct TokenWithWeight { + IERC20 token; + uint256 reserveBalance; + uint256 denormalizedWeight; + } + + struct PoolTokenDetails { + TokenWithWeight[] tokens; + uint256 totalWeight; + uint256 totalSupply; + } + + function _getPoolDetails(IBPool poolToken) + internal + view + returns(PoolTokenDetails memory details) + { + address[] memory currentTokens = poolToken.getCurrentTokens(); + details.tokens = new TokenWithWeight[](currentTokens.length); + details.totalWeight = poolToken.getTotalDenormalizedWeight(); + details.totalSupply = poolToken.totalSupply(); + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = IERC20(currentTokens[i]); + details.tokens[i].denormalizedWeight = poolToken.getDenormalizedWeight(currentTokens[i]); + details.tokens[i].reserveBalance = poolToken.getBalance(currentTokens[i]); + } + } + +} + +contract OneSplitBalancerPoolTokenView is OneSplitViewWrapBase, OneSplitBalancerPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + public + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!disableFlags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToBalancerPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToBalancerPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + } + + function _getExpectedReturnFromBalancerPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + IBPool bToken = IBPool(address(poolToken)); + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 pAiAfterExitFee = amount.sub( + amount.mul(bToken.EXIT_FEE()) + ); + uint256 ratio = pAiAfterExitFee.mul(1e18).div(poolToken.totalSupply()); + for (uint i = 0; i < currentTokens.length; i++) { + uint256 tokenAmountOut = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18); + + if (currentTokens[i] == address(toToken)) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } + + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + IERC20(currentTokens[i]), + toToken, + tokenAmountOut, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToBalancerPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns ( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount.mul( + details.tokens[i].denormalizedWeight + ).div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = tokenAmounts[i] + .mul(details.totalSupply) + .div(details.tokens[i].reserveBalance); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + +// uint256 _minFundAmount = minFundAmount; +// uint256 swapFee = IBPool(address(poolToken)).getSwapFee(); + // Swap leftovers for PoolToken +// for (uint i = 0; i < details.tokens.length; i++) { +// if (_minFundAmount == fundAmounts[i]) { +// continue; +// } +// +// uint256 leftover = tokenAmounts[i].sub( +// fundAmounts[i].mul(details.tokens[i].reserveBalance).div(details.totalSupply) +// ); +// +// uint256 tokenRet = IBPool(address(poolToken)).calcPoolOutGivenSingleIn( +// details.tokens[i].reserveBalance, +// details.tokens[i].denormalizedWeight, +// details.totalSupply, +// details.totalWeight, +// leftover, +// swapFee +// ); +// +// minFundAmount = minFundAmount.add(tokenRet); +// } + + return (minFundAmount, distribution); + } + +} + + +contract OneSplitBalancerPoolToken is OneSplitBaseWrap, OneSplitBalancerPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!disableFlags.check(FLAG_DISABLE_BALANCER_POOL_TOKEN)) { + bool isPoolTokenFrom = bFactory.isBPool(address(fromToken)); + bool isPoolTokenTo = bFactory.isBPool(address(toToken)); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromBalancerPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToBalancerPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToBalancerPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_BALANCER_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + + function _swapFromBalancerPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + IBPool bToken = IBPool(address(poolToken)); + + address[] memory currentTokens = bToken.getCurrentTokens(); + + uint256 ratio = amount.sub( + amount.mul(bToken.EXIT_FEE()) + ).mul(1e18).div(poolToken.totalSupply()); + + uint256[] memory minAmountsOut = new uint256[](currentTokens.length); + for (uint i = 0; i < currentTokens.length; i++) { + minAmountsOut[i] = bToken.getBalance(currentTokens[i]).mul(ratio).div(1e18).mul(995).div(1000); // 0.5% slippage; + } + + bToken.exitPool(amount, minAmountsOut); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < currentTokens.length; i++) { + + if (currentTokens[i] == address(toToken)) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = IERC20(currentTokens[i]).balanceOf(address(this)); + + this.swap( + IERC20(currentTokens[i]), + toToken, + exchangeTokenAmount, + 0, + dist, + disableFlags + ); + } + + } + + function _swapToBalancerPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + PoolTokenDetails memory details = _getPoolDetails(IBPool(address(poolToken))); + + uint256[] memory maxAmountsIn = new uint256[](details.tokens.length); + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].denormalizedWeight) + .div(details.totalWeight); + + if (details.tokens[i].token != fromToken) { + uint256 tokenBalanceBefore = details.tokens[i].token.balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + disableFlags + ); + + uint256 tokenBalanceAfter = details.tokens[i].token.balanceOf(address(this)); + + curFundAmount = ( + tokenBalanceAfter.sub(tokenBalanceBefore) + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } else { + curFundAmount = ( + exchangeAmount + ).mul(details.totalSupply).div(details.tokens[i].reserveBalance); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + maxAmountsIn[i] = uint256(-1); + _infiniteApproveIfNeeded(details.tokens[i].token, address(poolToken)); + } + + // todo: check for vulnerability + IBPool(address(poolToken)).joinPool(minFundAmount, maxAmountsIn); + + // Return leftovers + for (uint i = 0; i < details.tokens.length; i++) { + details.tokens[i].token.universalTransfer(msg.sender, details.tokens[i].token.balanceOf(address(this))); + } + } +} diff --git a/contracts/OneSplitBase.sol b/contracts/OneSplitBase.sol index 0799d3f..9458357 100644 --- a/contracts/OneSplitBase.sol +++ b/contracts/OneSplitBase.sol @@ -1105,6 +1105,20 @@ contract OneSplit is IOneSplit, OneSplitRoot { IERC20 fromToken, IERC20 toToken, uint256 amount + ) internal returns(uint256) { + uint256 ret = _swapOnBancorSafe( + fromToken, + toToken, + amount + ); + require(ret > 0); + return ret; + } + + function _swapOnBancorSafe( + IERC20 fromToken, + IERC20 toToken, + uint256 amount ) internal returns(uint256) { if (fromToken.isETH()) { bancorEtherToken.deposit.value(amount)(); @@ -1114,9 +1128,18 @@ contract OneSplit is IOneSplit, OneSplitRoot { address[] memory path = _buildBancorPath(fromToken, toToken); _infiniteApproveIfNeeded(fromToken.isETH() ? bancorEtherToken : fromToken, address(bancorNetwork)); - uint256 returnAmount = bancorNetwork.claimAndConvert(path, amount, 1); + (bool success, bytes memory data) = address(bancorNetwork).call.gas(1500000)( + abi.encodeWithSelector( + bancorNetwork.claimAndConvert.selector, + path, + amount, + 1 + ) + ); - if (toToken.isETH()) { + uint256 returnAmount = success ? abi.decode(data, (uint256)) : 0; + + if (toToken.isETH() && returnAmount > 0) { bancorEtherToken.withdraw(bancorEtherToken.balanceOf(address(this))); } diff --git a/contracts/OneSplitCurveSusdPoolToken.sol b/contracts/OneSplitCurveSusdPoolToken.sol new file mode 100644 index 0000000..72ab979 --- /dev/null +++ b/contracts/OneSplitCurveSusdPoolToken.sol @@ -0,0 +1,319 @@ +pragma solidity ^0.5.0; + +import "./OneSplitBase.sol"; +import "./interface/ICurve.sol"; + + +contract OneSplitCurveSusdPoolTokenBase { + using SafeMath for uint256; + using UniversalERC20 for IERC20; + + IERC20 constant curveSusdToken = IERC20(0xC25a3A3b969415c80451098fa907EC722572917F); + ICurve constant curve = ICurve(0xA5407eAE9Ba41422680e2e00537571bcC53efBfD); + + struct CurveSusdTokenInfo { + IERC20 token; + uint256 weightedReserveBalance; + } + + struct CurveSusdPoolTokenDetails { + CurveSusdTokenInfo[] tokens; + uint256 totalWeightedBalance; + } + + function _getPoolDetails() + internal + view + returns(CurveSusdPoolTokenDetails memory details) + { + details.tokens = new CurveSusdTokenInfo[](4); + for (uint256 i = 0; i < 4; i++) { + details.tokens[i].token = IERC20(curve.coins(int128(i))); + details.tokens[i].weightedReserveBalance = curve.balances(int128(i)) + .mul(1e18).div(10 ** details.tokens[i].token.universalDecimals()); + details.totalWeightedBalance = details.totalWeightedBalance.add( + details.tokens[i].weightedReserveBalance + ); + } + } +} + + +contract OneSplitCurveSusdPoolTokenView is OneSplitViewWrapBase, OneSplitCurveSusdPoolTokenBase { + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + public + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!disableFlags.check(FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN)) { + if (fromToken == curveSusdToken) { + return _getExpectedReturnFromCurveSusdPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN + ); + } + + if (toToken == curveSusdToken) { + return _getExpectedReturnToCurveSusdPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + } + + function _getExpectedReturnFromCurveSusdPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + uint256 totalSupply = poolToken.totalSupply(); + for (uint i = 0; i < 4; i++) { + IERC20 coin = IERC20(curve.coins(int128(i))); + + uint256 tokenAmountOut = curve.balances(int128(i)) + .mul(amount) + .div(totalSupply); + + if (coin == toToken) { + returnAmount = returnAmount.add(tokenAmountOut); + continue; + } + + (uint256 ret, uint256[] memory dist) = this.getExpectedReturn( + coin, + toToken, + tokenAmountOut, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToCurveSusdPoolToken( + IERC20 fromToken, + IERC20, // poolToken + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns ( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](DEXES_COUNT); + + CurveSusdPoolTokenDetails memory details = _getPoolDetails(); + + uint256[4] memory tokenAmounts; + uint256[] memory dist; + for (uint i = 0; i < 4; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + if (details.tokens[i].token == fromToken) { + tokenAmounts[i] = exchangeAmount; + continue; + } + + (tokenAmounts[i], dist) = this.getExpectedReturn( + fromToken, + details.tokens[i].token, + exchangeAmount, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + returnAmount = curve.calc_token_amount(tokenAmounts, true); + + return (returnAmount, distribution); + } +} + + +contract OneSplitCurveSusdPoolToken is OneSplitBaseWrap, OneSplitCurveSusdPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!disableFlags.check(FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN)) { + if (fromToken == curveSusdToken) { + return _swapFromCurveSusdPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN + ); + } + + if (toToken == curveSusdToken) { + return _swapToCurveSusdPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_CURVE_SUSD_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + + function _swapFromCurveSusdPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + uint256 totalSupply = poolToken.totalSupply(); + uint256[4] memory minAmountsOut; + for (uint i = 0; i < 4; i++) { + minAmountsOut[i] = curve.balances(int128(i)) + .mul(amount) + .div(totalSupply) + .mul(995).div(1000); // 0.5% slippage; + } + + curve.remove_liquidity(amount, minAmountsOut); + + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < 4; i++) { + IERC20 coin = IERC20(curve.coins(int128(i))); + + if (coin == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + uint256 exchangeTokenAmount = coin.universalBalanceOf(address(this)); + + this.swap( + coin, + toToken, + exchangeTokenAmount, + 0, + dist, + disableFlags + ); + } + } + + function _swapToCurveSusdPoolToken( + IERC20 fromToken, + IERC20, // poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + CurveSusdPoolTokenDetails memory details = _getPoolDetails(); + + uint256[4] memory tokenAmounts; + for (uint i = 0; i < 4; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].weightedReserveBalance) + .div(details.totalWeightedBalance); + + _infiniteApproveIfNeeded(details.tokens[i].token, address(curve)); + + if (details.tokens[i].token == fromToken) { + tokenAmounts[i] = exchangeAmount; + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + fromToken, + details.tokens[i].token, + exchangeAmount, + 0, + dist, + disableFlags + ); + + tokenAmounts[i] = details.tokens[i].token.universalBalanceOf(address(this)); + } + + curve.add_liquidity(tokenAmounts, 0); + } +} diff --git a/contracts/OneSplitSmartToken.sol b/contracts/OneSplitSmartToken.sol index b93643e..5240a02 100644 --- a/contracts/OneSplitSmartToken.sol +++ b/contracts/OneSplitSmartToken.sol @@ -12,27 +12,65 @@ contract OneSplitSmartTokenBase { ISmartTokenRegistry smartTokenRegistry = ISmartTokenRegistry(0xf6E2D7F616B67E46D708e4410746E9AAb3a4C518); ISmartTokenFormula smartTokenFormula = ISmartTokenFormula(0x524619EB9b4cdFFa7DA13029b33f24635478AFc0); + IERC20 bntToken = IERC20(0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C); + IERC20 usdbToken = IERC20(0x309627af60F0926daa6041B8279484312f2bf060); - struct TokensWithRatio { - IERC20[] tokens; - uint256[] ratios; + IERC20 public susd = IERC20(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51); + IERC20 public acientSUSD = IERC20(0x57Ab1E02fEE23774580C119740129eAC7081e9D3); + + struct TokenWithRatio { + IERC20 token; + uint256 ratio; + } + + struct SmartTokenDetails { + TokenWithRatio[] tokens; + address converter; uint256 totalRatio; } - function _getTokens( - ISmartTokenConverter converter + function _getSmartTokenDetails(ISmartToken smartToken) + internal + view + returns(SmartTokenDetails memory details) + { + ISmartTokenConverter converter = smartToken.owner(); + details.converter = address(converter); + details.tokens = new TokenWithRatio[](converter.connectorTokenCount()); + + for (uint256 i = 0; i < details.tokens.length; i++) { + details.tokens[i].token = converter.connectorTokens(i); + details.tokens[i].ratio = _getReserveRatio(converter, details.tokens[i].token); + details.totalRatio = details.totalRatio.add(details.tokens[i].ratio); + } + } + + function _getReserveRatio( + ISmartTokenConverter converter, + IERC20 token ) internal view - returns(TokensWithRatio memory tokens) + returns (uint256) { - tokens.tokens = new IERC20[](converter.connectorTokenCount()); - tokens.ratios = new uint256[](tokens.tokens.length); - for (uint256 i = 0; i < tokens.tokens.length; i++) { - tokens.tokens[i] = converter.connectorTokens(i); - tokens.ratios[i] = converter.getReserveRatio(tokens.tokens[i]); - tokens.totalRatio = tokens.totalRatio.add(tokens.ratios[i]); + (bool success, bytes memory data) = address(converter).staticcall.gas(10000)( + abi.encodeWithSelector( + converter.getReserveRatio.selector, + token + ) + ); + + if (success) { + return abi.decode(data, (uint256)); } + + (, uint32 ratio, , ,) = converter.connectors(address(token)); + + return uint256(ratio); + } + + function _canonicalSUSD(IERC20 token) internal view returns(IERC20) { + return token == acientSUSD ? susd : token; } } @@ -48,94 +86,67 @@ contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase public view returns( - uint256 returnAmount, - uint256[] memory distribution + uint256, + uint256[] memory ) { if (fromToken == toToken) { return (amount, new uint256[](DEXES_COUNT)); } - if (!flags.check(FLAG_DISABLE_SMART_TOKEN)) { - distribution = new uint256[](DEXES_COUNT); - if (smartTokenRegistry.isSmartToken(fromToken)) { - this; - // ISmartTokenConverter converter = ISmartToken(address(fromToken)).owner(); - - // TokensWithRatio memory tokens = _getTokens(converter); - - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( - // toToken.totalSupply(), - // tokens.tokens[i].balanceOf(address(converter)), - // uint32(tokens.totalRatio), - // amount - // ); - - // (uint256 ret, uint256[] memory dist) = super.getExpectedReturn( - // tokens.tokens[i], - // toToken, - // srcAmount, - // parts, - // flags - // ); - - // returnAmount = returnAmount.add(ret); - // for (uint j = 0; j < distribution.length; j++) { - // distribution[j] = distribution[j].add(dist[j] << (i * 8)); - // } - // } - // return (returnAmount, distribution); - } - - if (smartTokenRegistry.isSmartToken(toToken)) { - this; - // ISmartTokenConverter converter = ISmartToken(address(fromToken)).owner(); - - // TokensWithRatio memory tokens = _getTokens(converter); - - // uint256 minFundAmount = uint256(-1); - // uint256[] memory fundAmounts = new uint256[](tokens.tokens.length); - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // (uint256 tokenAmount, uint256[] memory dist) = super.getExpectedReturn( - // fromToken, - // tokens.tokens[i], - // amount.mul(tokens.ratios[i]).div(tokens.totalRatio), - // parts, - // flags | FLAG_DISABLE_BANCOR - // ); - // for (uint j = 0; j < distribution.length; j++) { - // distribution[j] = distribution[j].add(dist[j] << (i * 8)); - // } - - // fundAmounts[i] = toToken.totalSupply() - // .mul(tokenAmount) - // .div(tokens.tokens[i].balanceOf(address(converter))); - - // if (fundAmounts[i] < minFundAmount) { - // minFundAmount = fundAmounts[i]; - // } - // } - - // // Swap leftovers for SmartToken - // for (uint256 i = 0; i < tokens.tokens.length; i++) { - // uint256 leftover = fundAmounts[i].sub(minFundAmount) - // .mul(tokens.tokens[i].balanceOf(address(converter))) - // .div(toToken.totalSupply()); - - // if (leftover > 0) { - // minFundAmount = minFundAmount.add( - // smartTokenFormula.calculatePurchaseReturn( - // toToken.totalSupply(), - // tokens.tokens[i].balanceOf(address(converter)), - // uint32(tokens.totalRatio), - // leftover - // ) - // ); - // } - // } - - // return (minFundAmount, distribution); + if (!disableFlags.check(FLAG_DISABLE_SMART_TOKEN)) { + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + ( + uint256 returnBntAmount, + uint256[] memory smartTokenFromDistribution + ) = _getExpectedReturnFromSmartToken( + fromToken, + bntToken, + amount, + parts, + 0 + ); + + ( + uint256 returnSmartTokenToAmount, + uint256[] memory smartTokenToDistribution + ) = _getExpectedReturnToSmartToken( + bntToken, + toToken, + returnBntAmount, + parts, + 0 + ); + + for (uint i = 0; i < smartTokenToDistribution.length; i++) { + smartTokenFromDistribution[i] |= smartTokenToDistribution[i] << 128; + } + + return (returnSmartTokenToAmount, smartTokenFromDistribution); + } + + if (isSmartTokenFrom) { + return _getExpectedReturnFromSmartToken( + fromToken, + toToken, + amount, + parts, + 0 + ); + } + + if (isSmartTokenTo) { + return _getExpectedReturnToSmartToken( + fromToken, + toToken, + amount, + parts, + 0 + ); + } } @@ -147,6 +158,141 @@ contract OneSplitSmartTokenView is OneSplitViewWrapBase, OneSplitSmartTokenBase flags ); } + + function _getExpectedReturnFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](9); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 srcAmount = smartTokenFormula.calculateLiquidateReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + amount + ); + + if (details.tokens[i].token == toToken) { + returnAmount = returnAmount.add(srcAmount); + continue; + } + + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + _canonicalSUSD(details.tokens[i].token), + toToken, + srcAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 minFundAmount, + uint256[] memory distribution + ) + { + distribution = new uint256[](9); + minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256[] memory tokenAmounts = new uint256[](details.tokens.length); + uint256[] memory dist; + uint256[] memory fundAmounts = new uint256[](details.tokens.length); + + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + (tokenAmounts[i], dist) = getExpectedReturn( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << (i * 8); + } + } else { + tokenAmounts[i] = exchangeAmount; + } + + fundAmounts[i] = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenAmounts[i] + ); + + if (fundAmounts[i] < minFundAmount) { + minFundAmount = fundAmounts[i]; + } + } + + uint256 _minFundAmount = minFundAmount; + IERC20 _smartToken = smartToken; + + // Swap leftovers for SmartToken + for (uint i = 0; i < details.tokens.length; i++) { + if (_minFundAmount == fundAmounts[i]) { + continue; + } + + uint256 leftover = tokenAmounts[i].sub( + smartTokenFormula.calculateLiquidateReturn( + _smartToken.totalSupply().add(_minFundAmount), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter).add(tokenAmounts[i]), + uint32(details.totalRatio), + _minFundAmount + ) + ); + + uint256 tokenRet = calculateBancorReturn( + _canonicalSUSD(details.tokens[i].token), + _smartToken, + leftover, + disableFlags + ); + + minFundAmount = minFundAmount.add(tokenRet); + } + + return (minFundAmount, distribution); + } } @@ -158,11 +304,67 @@ contract OneSplitSmartToken is OneSplitBaseWrap, OneSplitSmartTokenBase { uint256[] memory distribution, uint256 flags ) internal { + if (fromToken == toToken) { return; } - // TODO: + if (!disableFlags.check(FLAG_DISABLE_SMART_TOKEN)) { + + bool isSmartTokenFrom = smartTokenRegistry.isSmartToken(fromToken); + bool isSmartTokenTo = smartTokenRegistry.isSmartToken(toToken); + + if (isSmartTokenFrom && isSmartTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 bntBalanceBefore = bntToken.balanceOf(address(this)); + + _swapFromSmartToken( + fromToken, + bntToken, + amount, + dist, + 0 + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 bntBalanceAfter = bntToken.balanceOf(address(this)); + + return _swapToSmartToken( + bntToken, + toToken, + bntBalanceAfter.sub(bntBalanceBefore), + dist, + 0 + ); + } + + if (isSmartTokenFrom) { + return _swapFromSmartToken( + fromToken, + toToken, + amount, + distribution, + 0 + ); + } + + if (isSmartTokenTo) { + return _swapToSmartToken( + fromToken, + toToken, + amount, + distribution, + 0 + ); + } + } return super._swap( fromToken, @@ -172,4 +374,116 @@ contract OneSplitSmartToken is OneSplitBaseWrap, OneSplitSmartTokenBase { flags ); } + + function _swapFromSmartToken( + IERC20 smartToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + ISmartTokenConverter(details.converter).liquidate(amount); + + uint256[] memory dist = new uint256[](distribution.length); + + for (uint i = 0; i < details.tokens.length; i++) { + if (details.tokens[i].token == toToken) { + continue; + } + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + this.swap( + _canonicalSUSD(details.tokens[i].token), + toToken, + _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)), + 0, + dist, + disableFlags + ); + } + } + + function _swapToSmartToken( + IERC20 fromToken, + IERC20 smartToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + uint256 minFundAmount = uint256(-1); + + SmartTokenDetails memory details = _getSmartTokenDetails(ISmartToken(address(smartToken))); + + uint256 curFundAmount; + for (uint i = 0; i < details.tokens.length; i++) { + uint256 exchangeAmount = amount + .mul(details.tokens[i].ratio) + .div(details.totalRatio); + + if (details.tokens[i].token != fromToken) { + + uint256 tokenBalanceBefore = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> (i * 8)) & 0xFF; + } + + super._swap( + fromToken, + _canonicalSUSD(details.tokens[i].token), + exchangeAmount, + dist, + disableFlags + ); + + uint256 tokenBalanceAfter = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + tokenBalanceAfter.sub(tokenBalanceBefore) + ); + } else { + curFundAmount = smartTokenFormula.calculatePurchaseReturn( + smartToken.totalSupply(), + _canonicalSUSD(details.tokens[i].token).balanceOf(details.converter), + uint32(details.totalRatio), + exchangeAmount + ); + } + + if (curFundAmount < minFundAmount) { + minFundAmount = curFundAmount; + } + + _infiniteApproveIfNeeded(_canonicalSUSD(details.tokens[i].token), details.converter); + } + + ISmartTokenConverter(details.converter).fund(minFundAmount); + + // Swap leftovers for SmartToken + for (uint i = 0; i < details.tokens.length; i++) { + IERC20 reserveToken = _canonicalSUSD(details.tokens[i].token); + + uint256 leftover = _canonicalSUSD(details.tokens[i].token).balanceOf(address(this)); + + uint256 ret = _swapOnBancorSafe( + reserveToken, + smartToken, + leftover + ); + + if (ret == 0) { + reserveToken.universalTransfer(msg.sender, leftover); + } + } + } } diff --git a/contracts/OneSplitUniswapPoolToken.sol b/contracts/OneSplitUniswapPoolToken.sol new file mode 100644 index 0000000..30852f3 --- /dev/null +++ b/contracts/OneSplitUniswapPoolToken.sol @@ -0,0 +1,466 @@ +pragma solidity ^0.5.0; + +import "./interface/IUniswapExchange.sol"; +import "./interface/IUniswapFactory.sol"; +import "./OneSplitBase.sol"; + + +contract OneSplitUniswapPoolTokenBase { + using SafeMath for uint256; + + IUniswapFactory uniswapFactory = IUniswapFactory(0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95); + + function isLiquidityPool(IERC20 token) internal view returns (bool) { + return address(uniswapFactory.getToken(address(token))) != address(0); + } + + function getMaxPossibleFund( + IERC20 poolToken, + IERC20 uniswapToken, + uint256 tokenAmount, + uint256 existEthAmount + ) + internal + view + returns ( + uint256, + uint256 + ) + { + uint256 ethReserve = address(poolToken).balance; + uint256 totalLiquidity = poolToken.totalSupply(); + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + + uint256 possibleEthAmount = ethReserve.mul( + tokenAmount.sub(1) + ).div(tokenReserve); + + if (existEthAmount > possibleEthAmount) { + return ( + possibleEthAmount, + possibleEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + + return ( + existEthAmount, + existEthAmount.mul(totalLiquidity).div(ethReserve) + ); + } + +} + +contract OneSplitUniswapPoolTokenView is OneSplitViewWrapBase, OneSplitUniswapPoolTokenBase { + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + public + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + if (fromToken == toToken) { + return (amount, new uint256[](DEXES_COUNT)); + } + + + if (!disableFlags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + ( + uint256 returnETHAmount, + uint256[] memory poolTokenFromDistribution + ) = _getExpectedReturnFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + ( + uint256 returnPoolTokenToAmount, + uint256[] memory poolTokenToDistribution + ) = _getExpectedReturnToPoolToken( + ETH_ADDRESS, + toToken, + returnETHAmount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < poolTokenToDistribution.length; i++) { + poolTokenFromDistribution[i] |= poolTokenToDistribution[i] << 128; + } + + return (returnPoolTokenToAmount, poolTokenFromDistribution); + } + + if (isPoolTokenFrom) { + return _getExpectedReturnFromPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _getExpectedReturnToPoolToken( + fromToken, + toToken, + amount, + parts, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super.getExpectedReturn( + fromToken, + toToken, + amount, + parts, + disableFlags + ); + } + + function _getExpectedReturnFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 totalSupply = poolToken.totalSupply(); + + uint256 ethReserve = address(poolToken).balance; + uint256 ethAmount = amount.mul(ethReserve).div(totalSupply); + + if (!toToken.isETH()) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + ETH_ADDRESS, + toToken, + ethAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + returnAmount = returnAmount.add(ethAmount); + } + + uint256 tokenReserve = uniswapToken.balanceOf(address(poolToken)); + uint256 exchangeTokenAmount = amount.mul(tokenReserve).div(totalSupply); + + if (toToken != uniswapToken) { + (uint256 ret, uint256[] memory dist) = getExpectedReturn( + uniswapToken, + toToken, + exchangeTokenAmount, + parts, + disableFlags + ); + + returnAmount = returnAmount.add(ret); + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + returnAmount = returnAmount.add(exchangeTokenAmount); + } + + return (returnAmount, distribution); + } + + function _getExpectedReturnToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256 parts, + uint256 disableFlags + ) + private + view + returns( + uint256 returnAmount, + uint256[] memory distribution + ) + { + + distribution = new uint256[](DEXES_COUNT); + + uint256[] memory dist = new uint256[](DEXES_COUNT); + + uint256 ethAmount; + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + (ethAmount, dist) = super.getExpectedReturn( + fromToken, + ETH_ADDRESS, + partAmountForEth, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j]; + } + } else { + ethAmount = partAmountForEth; + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 tokenAmount; + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + (tokenAmount, dist) = super.getExpectedReturn( + fromToken, + uniswapToken, + partAmountForToken, + parts, + disableFlags + ); + + for (uint j = 0; j < distribution.length; j++) { + distribution[j] |= dist[j] << 8; + } + } else { + tokenAmount = partAmountForToken; + } + + (, returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenAmount, + ethAmount + ); + + return ( + returnAmount, + distribution + ); + } + +} + + +contract OneSplitUniswapPoolToken is OneSplitBaseWrap, OneSplitUniswapPoolTokenBase { + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) internal { + if (fromToken == toToken) { + return; + } + + if (!disableFlags.check(FLAG_DISABLE_UNISWAP_POOL_TOKEN)) { + bool isPoolTokenFrom = isLiquidityPool(fromToken); + bool isPoolTokenTo = isLiquidityPool(toToken); + + if (isPoolTokenFrom && isPoolTokenTo) { + uint256[] memory dist = new uint256[](distribution.length); + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] & ((1 << 128) - 1); + } + + uint256 ethBalanceBefore = address(this).balance; + + _swapFromPoolToken( + fromToken, + ETH_ADDRESS, + amount, + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + + for (uint i = 0; i < distribution.length; i++) { + dist[i] = distribution[i] >> 128; + } + + uint256 ethBalanceAfter = address(this).balance; + + return _swapToPoolToken( + ETH_ADDRESS, + toToken, + ethBalanceAfter.sub(ethBalanceBefore), + dist, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenFrom) { + return _swapFromPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + + if (isPoolTokenTo) { + return _swapToPoolToken( + fromToken, + toToken, + amount, + distribution, + FLAG_DISABLE_UNISWAP_POOL_TOKEN + ); + } + } + + return super._swap( + fromToken, + toToken, + amount, + distribution, + disableFlags + ); + } + + function _swapFromPoolToken( + IERC20 poolToken, + IERC20 toToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + + uint256[] memory dist = new uint256[](distribution.length); + + ( + uint256 ethAmount, + uint256 exchangeTokenAmount + ) = IUniswapExchange(address(poolToken)).removeLiquidity( + amount, + 1, + 1, + now.add(1800) + ); + + if (!toToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + ETH_ADDRESS, + toToken, + ethAmount, + dist, + disableFlags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + if (toToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + uniswapToken, + toToken, + exchangeTokenAmount, + dist, + disableFlags + ); + } + } + + function _swapToPoolToken( + IERC20 fromToken, + IERC20 poolToken, + uint256 amount, + uint256[] memory distribution, + uint256 disableFlags + ) private { + uint256[] memory dist = new uint256[](distribution.length); + + uint256 partAmountForEth = amount.div(2); + if (!fromToken.isETH()) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j]) & 0xFF; + } + + super._swap( + fromToken, + ETH_ADDRESS, + partAmountForEth, + dist, + disableFlags + ); + } + + IERC20 uniswapToken = uniswapFactory.getToken(address(poolToken)); + + uint256 partAmountForToken = amount.sub(partAmountForEth); + if (fromToken != uniswapToken) { + for (uint j = 0; j < distribution.length; j++) { + dist[j] = (distribution[j] >> 8) & 0xFF; + } + + super._swap( + fromToken, + uniswapToken, + partAmountForToken, + dist, + disableFlags + ); + + _infiniteApproveIfNeeded(uniswapToken, address(poolToken)); + } + + uint256 ethBalance = address(this).balance; + uint256 tokenBalance = uniswapToken.balanceOf(address(this)); + + (uint256 ethAmount, uint256 returnAmount) = getMaxPossibleFund( + poolToken, + uniswapToken, + tokenBalance, + ethBalance + ); + + IUniswapExchange(address(poolToken)).addLiquidity.value(ethAmount)( + returnAmount.mul(995).div(1000), // 0.5% slippage + uint256(-1), // todo: think about another value + now.add(1800) + ); + + // todo: do we need to check difference between balance before and balance after? + uniswapToken.universalTransfer(msg.sender, uniswapToken.balanceOf(address(this))); + ETH_ADDRESS.universalTransfer(msg.sender, address(this).balance); + } +} diff --git a/contracts/interface/IBFactory.sol b/contracts/interface/IBFactory.sol new file mode 100644 index 0000000..f89d171 --- /dev/null +++ b/contracts/interface/IBFactory.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.5.0; + +interface IBFactory { + function isBPool(address b) external view returns (bool); +} diff --git a/contracts/interface/IBPool.sol b/contracts/interface/IBPool.sol new file mode 100644 index 0000000..42d4dd5 --- /dev/null +++ b/contracts/interface/IBPool.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract BConst { + uint public constant EXIT_FEE = 0; +} + +contract IBMath is BConst { + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) + public + pure returns (uint poolAmountOut); +} + +contract IBPool is IERC20, IBMath { + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) external; + + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) external; + + function joinswapExternAmountIn(address tokenIn, uint tokenAmountIn, uint minPoolAmountOut) external returns (uint poolAmountOut); + + function getCurrentTokens() external view returns (address[] memory tokens); + + function getBalance(address token) external view returns (uint); + + function getNormalizedWeight(address token) external view returns (uint); + + function getDenormalizedWeight(address token) external view returns (uint); + + function getTotalDenormalizedWeight() external view returns (uint); + + function getSwapFee() external view returns (uint); +} diff --git a/contracts/interface/ICurve.sol b/contracts/interface/ICurve.sol index 652e409..f3deb7e 100644 --- a/contracts/interface/ICurve.sol +++ b/contracts/interface/ICurve.sol @@ -5,6 +5,18 @@ interface ICurve { // solium-disable-next-line mixedcase function get_dy_underlying(int128 i, int128 j, uint256 dx) external view returns(uint256 dy); + function get_virtual_price() external view returns(uint256); + // solium-disable-next-line mixedcase function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external; + + function coins(int128 arg0) external view returns (address); + + function balances(int128 arg0) external view returns (uint256); + + function add_liquidity(uint256[4] calldata amounts, uint256 min_mint_amount) external; + + function remove_liquidity(uint256 _amount, uint256[4] calldata min_amounts) external; + + function calc_token_amount(uint256[4] calldata amounts, bool deposit) external view returns (uint256); } diff --git a/contracts/interface/ISmartTokenConverter.sol b/contracts/interface/ISmartTokenConverter.sol index 45e8927..02f4744 100644 --- a/contracts/interface/ISmartTokenConverter.sol +++ b/contracts/interface/ISmartTokenConverter.sol @@ -2,11 +2,24 @@ pragma solidity ^0.5.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - interface ISmartTokenConverter { - function getReserveRatio(IERC20 token) external view returns (uint32); + + function version() external view returns (uint16); + + function connectors(address) external view returns (uint256, uint32, bool, bool, bool); + + function getReserveRatio(IERC20 token) external view returns (uint256); function connectorTokenCount() external view returns (uint256); function connectorTokens(uint256 i) external view returns (IERC20); + + function liquidate(uint256 _amount) external; + + function fund(uint256 _amount) external; + + function convert2(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn, address _affiliateAccount, uint256 _affiliateFee) external returns (uint256); + + function convert(IERC20 _fromToken, IERC20 _toToken, uint256 _amount, uint256 _minReturn) external returns (uint256); + } diff --git a/contracts/interface/IUniswapExchange.sol b/contracts/interface/IUniswapExchange.sol index f1200c3..0196bd6 100644 --- a/contracts/interface/IUniswapExchange.sol +++ b/contracts/interface/IUniswapExchange.sol @@ -24,4 +24,8 @@ interface IUniswapExchange { uint256 deadline, address tokenAddr ) external returns (uint256 tokensBought); + + function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) external payable returns (uint256); + + function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) external returns (uint256, uint256); } diff --git a/contracts/interface/IUniswapFactory.sol b/contracts/interface/IUniswapFactory.sol index bc352fc..14f9069 100644 --- a/contracts/interface/IUniswapFactory.sol +++ b/contracts/interface/IUniswapFactory.sol @@ -5,4 +5,6 @@ import "./IUniswapExchange.sol"; interface IUniswapFactory { function getExchange(IERC20 token) external view returns (IUniswapExchange exchange); + + function getToken(address exchange) external view returns (IERC20 token); } diff --git a/test/OneSplit.js b/test/OneSplit.js index 1ce439d..308cc95 100644 --- a/test/OneSplit.js +++ b/test/OneSplit.js @@ -1,5 +1,5 @@ -// const { expectRevert } = require('openzeppelin-test-helpers'); -// const { expect } = require('chai'); +const { expectRevert } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); const assert = require('assert'); const OneSplitView = artifacts.require('OneSplitView'); @@ -8,6 +8,40 @@ const OneSplit = artifacts.require('OneSplit'); const OneSplitWrap = artifacts.require('OneSplitWrap'); contract('OneSplit', function ([_, addr1]) { + + describe('OneSplitSmartContract', async function () { + beforeEach('should be ok', async function () { + this.smartTokenView = await OneSplitViewMock.new(); + }); + + it('should view buying price', async function () { + const res = await this.smartTokenView.getExpectedReturn( + '0x0000000000000000000000000000000000000000', // ETH + '0x482c31355F4f7966fFcD38eC5c9635ACAe5F4D4F', // Ether Token Smart Relay Token (ETHUSDB) + '0x' + Number(web3.utils.toWei('0.5')).toString(16), + '0x' + (10).toString(16), + '0x0', + ); + + console.log(res['0'].toString()); + console.log(res['1'].map(x => x.toString())); + }); + + it('should view selling price', async function () { + const res = await this.smartTokenView.getExpectedReturn( + '0x482c31355F4f7966fFcD38eC5c9635ACAe5F4D4F', // Ether Token Smart Relay Token (ETHUSDB) + '0x0000000000000000000000000000000000000000', // ETH + '0x' + Number(web3.utils.toWei('20')).toString(16), + '0x' + (10).toString(16), + '0x0' + ); + + console.log(res['0'].toString()); + console.log(res['1'].map(x => x.toString())); + }); + + }); + describe('OneSplit', async function () { beforeEach('should be ok', async function () { const subSplitView = await OneSplitView.new();