From 208cfd29e3144967eed40c100f60977e5afc3fe7 Mon Sep 17 00:00:00 2001 From: The Dance Date: Mon, 13 Jan 2025 11:32:52 +0000 Subject: [PATCH 1/4] test: tests for nine more proxies --- .../data/contracts/ethereum/local/CWIA.json | 233 + .../ethereum/local/ClonesFactory.json | 70 + .../contracts/ethereum/local/OldCWIA.json | 374 ++ .../ethereum/local/SequenceFactory.json | 91 + .../ethereum/local/SoladyFactory.json | 78 + .../contracts/ethereum/local/SplitsCWIA.json | 188 + .../ethereum/local/VyperFactory.json | 1223 +++- tests/functional/data/sources/CWIA.sol | 686 ++ .../functional/data/sources/ClonesFactory.sol | 179 + .../data/sources/LSSVMPairFactory.sol | 5732 +++++++++++++++++ tests/functional/data/sources/OldCWIA.sol | 777 +++ .../data/sources/SequenceFactory.sol | 86 + .../functional/data/sources/SoladyFactory.sol | 2877 +++++++++ tests/functional/data/sources/SplitsCWIA.sol | 455 ++ .../functional/data/sources/SudoswapCWIA.sol | 2 + tests/functional/data/sources/VyperFactory.vy | 6 + tests/functional/geth/test_proxy.py | 21 + tests/functional/test_proxy.py | 93 + 18 files changed, 13170 insertions(+), 1 deletion(-) create mode 100644 tests/functional/data/contracts/ethereum/local/CWIA.json create mode 100644 tests/functional/data/contracts/ethereum/local/ClonesFactory.json create mode 100644 tests/functional/data/contracts/ethereum/local/OldCWIA.json create mode 100644 tests/functional/data/contracts/ethereum/local/SequenceFactory.json create mode 100644 tests/functional/data/contracts/ethereum/local/SoladyFactory.json create mode 100644 tests/functional/data/contracts/ethereum/local/SplitsCWIA.json create mode 100644 tests/functional/data/sources/CWIA.sol create mode 100644 tests/functional/data/sources/ClonesFactory.sol create mode 100644 tests/functional/data/sources/LSSVMPairFactory.sol create mode 100644 tests/functional/data/sources/OldCWIA.sol create mode 100644 tests/functional/data/sources/SequenceFactory.sol create mode 100644 tests/functional/data/sources/SoladyFactory.sol create mode 100644 tests/functional/data/sources/SplitsCWIA.sol create mode 100644 tests/functional/data/sources/SudoswapCWIA.sol diff --git a/tests/functional/data/contracts/ethereum/local/CWIA.json b/tests/functional/data/contracts/ethereum/local/CWIA.json new file mode 100644 index 0000000000..8f89e6f88d --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/CWIA.json @@ -0,0 +1,233 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ExampleClone", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CreateFail", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + } + ], + "name": "addressOfClone2", + "outputs": [ + { + "internalType": "address", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "addressOfClone3", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + } + ], + "name": "createClone", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + } + ], + "name": "createClone2", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "createClone3", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "contractName": "ExampleCloneFactory", + "deploymentBytecode": { + "bytecode": "0x6080604052348015600e575f5ffd5b5060405161080b38038061080b833981016040819052602b91604e565b5f80546001600160a01b0319166001600160a01b03929092169190911790556079565b5f60208284031215605d575f5ffd5b81516001600160a01b03811681146072575f5ffd5b9392505050565b610785806100865f395ff3fe608060405260043610610054575f3560e01c80633feab925146100585780635c60da1b14610087578063684fbe55146100a55780637df12552146100b85780638059ce3d146100d7578063ea8fb4df146100f6575b5f5ffd5b61006b610066366004610654565b610109565b6040516001600160a01b03909116815260200160405180910390f35b348015610092575f5ffd5b505f5461006b906001600160a01b031681565b61006b6100b3366004610654565b610155565b3480156100c3575f5ffd5b5061006b6100d2366004610654565b6101df565b3480156100e2575f5ffd5b5061006b6100f136600461069e565b610220565b61006b6101043660046106b5565b610230565b5f5f858585856040516020016101229493929190610706565b60408051601f198184030181529190525f5490915061014b906001600160a01b0316823461027e565b9695505050505050565b5f5f8585858560405160200161016e9493929190610706565b60408051601f198184030181529190525f54909150610197906001600160a01b031682346102c6565b6040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a150949350505050565b5f5f858585856040516020016101f89493929190610706565b60408051601f198184030181529190525f5490915061014b906001600160a01b031682610305565b5f61022a8261036d565b92915050565b5f5f868686866040516020016102499493929190610706565b60408051601f198184030181529190525f54909150610273906001600160a01b03168285346103cb565b979650505050505050565b5f5f61028a8585610525565b90505f81516020830185f591506001600160a01b0382166102be57604051631d7fde3160e31b815260040160405180910390fd5b509392505050565b5f5f6102d28585610525565b905080516020820184f091506001600160a01b0382166102be57604051631d7fde3160e31b815260040160405180910390fd5b5f5f6103118484610525565b8051602091820120604080516001600160f81b0319818501523060601b6bffffffffffffffffffffffff191660218201525f60358201526055808201939093528151808203909301835260750190528051910120949350505050565b5f604051305f5260ff600b53826020527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f6040526055600b20601452806040525061d6945f52600160345350506017601e206001600160a01b031690565b8251604051613d6160f01b8152603a820160f090811b6002838101919091526680600b3d3981f360c81b600484015264363d3d376160d81b600b8401528301901b6010820181905268603836393d3d3d366160b81b6012830152601b82015262013d7360e81b601d820152606086901b6020808301919091526e5af43d82803e903d91603657fd5bf360881b60348301525f929160458301918390604383019089015b6020831061048d5780518252601f19909201916020918201910161046e565b5f6001846020036101000a0319905080825116835283830192508660f01b83526f67363d3d37363d34f03d5260086018f35f52896010805ff5806104d85763ebfef1885f526004601cfd5b8060145261d6945f5260016034536001600160a01b036017601e201698505f5f88888d855af115893b15171561051557638f86d2f15f526004601cfd5b5050505050505050949350505050565b80516040805160438084018083528285019091018352606160f81b60208084019182526039860160f081811b60218701526f3d81600a3d39f33d3d3d3d363d3d376160801b602387015260028801901b603386018190526560373639366160d01b6035870152603b86015262013d7360e81b603d860152606089901b958501959095526c5af43d3d93803e603557fd5bf360981b6054850152929493919286019084606187015b602082106105eb5783518152602093840193601f1990920191016105cc565b92515f1960208390036101000a011916835260f09590951b91909401525091949350505050565b80356001600160a01b0381168114610628575f5ffd5b919050565b803567ffffffffffffffff81168114610628575f5ffd5b803560ff81168114610628575f5ffd5b5f5f5f5f60808587031215610667575f5ffd5b61067085610612565b9350602085013592506106856040860161062d565b915061069360608601610644565b905092959194509250565b5f602082840312156106ae575f5ffd5b5035919050565b5f5f5f5f5f60a086880312156106c9575f5ffd5b6106d286610612565b9450602086013593506106e76040870161062d565b92506106f560608701610644565b949793965091946080013592915050565b60609490941b6bffffffffffffffffffffffff19168452601484019290925260c01b6001600160c01b031916603483015260f81b6001600160f81b031916603c820152603d019056fea26469706673582212205559884f32a8a932c7edc025fca15945f9c4d5c0aba79378ff3471ff2c39348b64736f6c634300081c0033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "addressOfClone2(address,uint256,uint64,uint8)": "0x7df12552", + "addressOfClone3(bytes32)": "0x8059ce3d", + "createClone(address,uint256,uint64,uint8)": "0x684fbe55", + "createClone2(address,uint256,uint64,uint8)": "0x3feab925", + "createClone3(address,uint256,uint64,uint8,bytes32)": "0xea8fb4df", + "implementation()": "0x5c60da1b" + }, + "runtimeBytecode": { + "bytecode": "0x608060405260043610610054575f3560e01c80633feab925146100585780635c60da1b14610087578063684fbe55146100a55780637df12552146100b85780638059ce3d146100d7578063ea8fb4df146100f6575b5f5ffd5b61006b610066366004610654565b610109565b6040516001600160a01b03909116815260200160405180910390f35b348015610092575f5ffd5b505f5461006b906001600160a01b031681565b61006b6100b3366004610654565b610155565b3480156100c3575f5ffd5b5061006b6100d2366004610654565b6101df565b3480156100e2575f5ffd5b5061006b6100f136600461069e565b610220565b61006b6101043660046106b5565b610230565b5f5f858585856040516020016101229493929190610706565b60408051601f198184030181529190525f5490915061014b906001600160a01b0316823461027e565b9695505050505050565b5f5f8585858560405160200161016e9493929190610706565b60408051601f198184030181529190525f54909150610197906001600160a01b031682346102c6565b6040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a150949350505050565b5f5f858585856040516020016101f89493929190610706565b60408051601f198184030181529190525f5490915061014b906001600160a01b031682610305565b5f61022a8261036d565b92915050565b5f5f868686866040516020016102499493929190610706565b60408051601f198184030181529190525f54909150610273906001600160a01b03168285346103cb565b979650505050505050565b5f5f61028a8585610525565b90505f81516020830185f591506001600160a01b0382166102be57604051631d7fde3160e31b815260040160405180910390fd5b509392505050565b5f5f6102d28585610525565b905080516020820184f091506001600160a01b0382166102be57604051631d7fde3160e31b815260040160405180910390fd5b5f5f6103118484610525565b8051602091820120604080516001600160f81b0319818501523060601b6bffffffffffffffffffffffff191660218201525f60358201526055808201939093528151808203909301835260750190528051910120949350505050565b5f604051305f5260ff600b53826020527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f6040526055600b20601452806040525061d6945f52600160345350506017601e206001600160a01b031690565b8251604051613d6160f01b8152603a820160f090811b6002838101919091526680600b3d3981f360c81b600484015264363d3d376160d81b600b8401528301901b6010820181905268603836393d3d3d366160b81b6012830152601b82015262013d7360e81b601d820152606086901b6020808301919091526e5af43d82803e903d91603657fd5bf360881b60348301525f929160458301918390604383019089015b6020831061048d5780518252601f19909201916020918201910161046e565b5f6001846020036101000a0319905080825116835283830192508660f01b83526f67363d3d37363d34f03d5260086018f35f52896010805ff5806104d85763ebfef1885f526004601cfd5b8060145261d6945f5260016034536001600160a01b036017601e201698505f5f88888d855af115893b15171561051557638f86d2f15f526004601cfd5b5050505050505050949350505050565b80516040805160438084018083528285019091018352606160f81b60208084019182526039860160f081811b60218701526f3d81600a3d39f33d3d3d3d363d3d376160801b602387015260028801901b603386018190526560373639366160d01b6035870152603b86015262013d7360e81b603d860152606089901b958501959095526c5af43d3d93803e603557fd5bf360981b6054850152929493919286019084606187015b602082106105eb5783518152602093840193601f1990920191016105cc565b92515f1960208390036101000a011916835260f09590951b91909401525091949350505050565b80356001600160a01b0381168114610628575f5ffd5b919050565b803567ffffffffffffffff81168114610628575f5ffd5b803560ff81168114610628575f5ffd5b5f5f5f5f60808587031215610667575f5ffd5b61067085610612565b9350602085013592506106856040860161062d565b915061069360608601610644565b905092959194509250565b5f602082840312156106ae575f5ffd5b5035919050565b5f5f5f5f5f60a086880312156106c9575f5ffd5b6106d286610612565b9450602086013593506106e76040870161062d565b92506106f560608701610644565b949793965091946080013592915050565b60609490941b6bffffffffffffffffffffffff19168452601484019290925260c01b6001600160c01b031916603483015260f81b6001600160f81b031916603c820152603d019056fea26469706673582212205559884f32a8a932c7edc025fca15945f9c4d5c0aba79378ff3471ff2c39348b64736f6c634300081c0033" + }, + "sourceId": "CWIA.sol", + "sourcemap": "30048:1808:0:-:0;;;30205:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;30257:14;:32;;-1:-1:-1;;;;;;30257:32:0;-1:-1:-1;;;;;30257:32:0;;;;;;;;;;30048:1808;;14:310:1;104:6;157:2;145:9;136:7;132:23;128:32;125:52;;;173:1;170;163:12;125:52;199:16;;-1:-1:-1;;;;;244:31:1;;234:42;;224:70;;290:1;287;280:12;224:70;313:5;14:310;-1:-1:-1;;;14:310:1:o;:::-;30048:1808:0;;;;;;", + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/ClonesFactory.json b/tests/functional/data/contracts/ethereum/local/ClonesFactory.json new file mode 100644 index 0000000000..77fb1a3fd2 --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/ClonesFactory.json @@ -0,0 +1,70 @@ +{ + "abi": [ + { + "inputs": [], + "name": "CreateError", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "name": "deployClonesProxy", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "contractName": "ClonesFactory", + "deploymentBytecode": { + "bytecode": "0x608060405234801561001057600080fd5b506101c6806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063303d29d814610030575b600080fd5b61004361003e366004610162565b61005f565b6040516001600160a01b03909116815260200160405180910390f35b600061006a826100ae565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1919050565b6000604051723d605d80600a3d3981f336603057343d52307f60681b81527f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160138201527260203da23d3df35b3d3d3d3d363d3d37363d7360681b60338201528260601b60468201526c5af43d3d93803e605b57fd5bf360981b605a8201526067816000f09150506001600160a01b03811661015d57604051630985da9b60e41b815260040160405180910390fd5b919050565b600060208284031215610173578081fd5b81356001600160a01b0381168114610189578182fd5b939250505056fea2646970667358221220892d71408f9c85c29b24e1d399653328e4fe20da80e56db141ab111318d618d164736f6c63430008040033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "deployClonesProxy(address)": "0x303d29d8" + }, + "runtimeBytecode": { + "bytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063303d29d814610030575b600080fd5b61004361003e366004610162565b61005f565b6040516001600160a01b03909116815260200160405180910390f35b600061006a826100ae565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1919050565b6000604051723d605d80600a3d3981f336603057343d52307f60681b81527f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160138201527260203da23d3df35b3d3d3d3d363d3d37363d7360681b60338201528260601b60468201526c5af43d3d93803e605b57fd5bf360981b605a8201526067816000f09150506001600160a01b03811661015d57604051630985da9b60e41b815260040160405180910390fd5b919050565b600060208284031215610173578081fd5b81356001600160a01b0381168114610189578182fd5b939250505056fea2646970667358221220892d71408f9c85c29b24e1d399653328e4fe20da80e56db141ab111318d618d164736f6c63430008040033" + }, + "sourceId": "tests/functional/data/sources/ClonesFactory.sol", + "sourcemap": "6708:230:0:-:0;;;;;;;;;;;;;;;;;;;", + "userdoc": { + "errors": { + "CreateError()": [ + { + "notice": "create opcode failed" + } + ] + }, + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/OldCWIA.json b/tests/functional/data/contracts/ethereum/local/OldCWIA.json new file mode 100644 index 0000000000..922bb324f2 --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/OldCWIA.json @@ -0,0 +1,374 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Cloned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "log", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "log_address", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "log_bytes", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "log_bytes32", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "name": "log_int", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "val", + "type": "address" + } + ], + "name": "log_named_address", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "val", + "type": "bytes" + } + ], + "name": "log_named_bytes", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "val", + "type": "bytes32" + } + ], + "name": "log_named_bytes32", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "int256", + "name": "val", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + } + ], + "name": "log_named_decimal_int", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "val", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "decimals", + "type": "uint256" + } + ], + "name": "log_named_decimal_uint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "int256", + "name": "val", + "type": "int256" + } + ], + "name": "log_named_int", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "val", + "type": "string" + } + ], + "name": "log_named_string", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "val", + "type": "uint256" + } + ], + "name": "log_named_uint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "log_string", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "log_uint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "logs", + "type": "event" + }, + { + "inputs": [], + "name": "IS_TEST", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "clone1", + "outputs": [ + { + "internalType": "contract Template", + "name": "clonedGreeter", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "clone2", + "outputs": [ + { + "internalType": "contract Template", + "name": "clonedGreeter", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "failed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "contractName": "Template", + "deploymentBytecode": { + "bytecode": "0x60806040525f805460ff19166001179055348015601a575f5ffd5b50610669806100285f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063332b3e641461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c366004610490565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610490565b610143565b5f5461008f9060ff1681565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061018392505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061034092505050565b5f5f825160026101939190610512565b90505f6101a1826043610512565b90505f6101af600b8361052b565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b6034820152600285610245919061052b565b9450845f610254836043610512565b90506020890193505b602082106102955783518152610274602082610512565b9050610281602085610512565b935061028e60208361052b565b915061025d565b5f60016102a384602061052b565b6102af90610100610621565b6102b9919061052b565b1990508085511682528260206102cf919061052b565b6102d99083610512565b91508760f01b825286845ff098506001600160a01b0389166103325760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f9081610350826038610512565b90505f61035e600b8361052b565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f90816103c9826038610512565b90506020880192505b6020861061040a57825181526103e9602082610512565b90506103f6602084610512565b925061040360208761052b565b95506103d2565b5f600161041888602061052b565b61042490610100610621565b61042e919061052b565b19905080845116825285835ff097506001600160a01b0388166104835760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b6044820152606401610329565b5050505050505092915050565b5f5f602083850312156104a1575f5ffd5b823567ffffffffffffffff8111156104b7575f5ffd5b8301601f810185136104c7575f5ffd5b803567ffffffffffffffff8111156104dd575f5ffd5b8560208284010111156104ee575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610525576105256104fe565b92915050565b81810381811115610525576105256104fe565b6001815b60018411156105795780850481111561055d5761055d6104fe565b600184161561056b57908102905b60019390931c928002610542565b935093915050565b5f8261058f57506001610525565b8161059b57505f610525565b81600181146105b157600281146105bb576105d7565b6001915050610525565b60ff8411156105cc576105cc6104fe565b50506001821b610525565b5060208310610133831016604e8410600b84101617156105fa575081810a610525565b6106065f19848461053e565b805f1904821115610619576106196104fe565b029392505050565b5f61062c8383610581565b939250505056fea2646970667358221220cb5bb3c3a6888cbff08b06e22489530072b67ef62743eb6ffdf63db6316064f664736f6c634300081c0033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "IS_TEST()": "0xfa7626d4", + "clone1(bytes)": "0xce57ed7b", + "clone2(bytes)": "0x332b3e64", + "failed()": "0xba414fa6" + }, + "runtimeBytecode": { + "bytecode": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063332b3e641461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c366004610490565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610490565b610143565b5f5461008f9060ff1681565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061018392505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061034092505050565b5f5f825160026101939190610512565b90505f6101a1826043610512565b90505f6101af600b8361052b565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b6034820152600285610245919061052b565b9450845f610254836043610512565b90506020890193505b602082106102955783518152610274602082610512565b9050610281602085610512565b935061028e60208361052b565b915061025d565b5f60016102a384602061052b565b6102af90610100610621565b6102b9919061052b565b1990508085511682528260206102cf919061052b565b6102d99083610512565b91508760f01b825286845ff098506001600160a01b0389166103325760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f9081610350826038610512565b90505f61035e600b8361052b565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f90816103c9826038610512565b90506020880192505b6020861061040a57825181526103e9602082610512565b90506103f6602084610512565b925061040360208761052b565b95506103d2565b5f600161041888602061052b565b61042490610100610621565b61042e919061052b565b19905080845116825285835ff097506001600160a01b0388166104835760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b6044820152606401610329565b5050505050505092915050565b5f5f602083850312156104a1575f5ffd5b823567ffffffffffffffff8111156104b7575f5ffd5b8301601f810185136104c7575f5ffd5b803567ffffffffffffffff8111156104dd575f5ffd5b8560208284010111156104ee575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610525576105256104fe565b92915050565b81810381811115610525576105256104fe565b6001815b60018411156105795780850481111561055d5761055d6104fe565b600184161561056b57908102905b60019390931c928002610542565b935093915050565b5f8261058f57506001610525565b8161059b57505f610525565b81600181146105b157600281146105bb576105d7565b6001915050610525565b60ff8411156105cc576105cc6104fe565b50506001821b610525565b5060208310610133831016604e8410600b84101617156105fa575081810a610525565b6106065f19848461053e565b805f1904821115610619576106196104fe565b029392505050565b5f61062c8383610581565b939250505056fea2646970667358221220cb5bb3c3a6888cbff08b06e22489530072b67ef62743eb6ffdf63db6316064f664736f6c634300081c0033" + }, + "sourceId": "OldCWIA.sol", + "sourcemap": "29372:564:0:-:0;;;1601:26;;;-1:-1:-1;;1601:26:0;1623:4;1601:26;;;29372:564;;;;;;;;;;;;;;;;", + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/SequenceFactory.json b/tests/functional/data/contracts/ethereum/local/SequenceFactory.json new file mode 100644 index 0000000000..5af0480c35 --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/SequenceFactory.json @@ -0,0 +1,91 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_mainModule", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + } + ], + "name": "DeployFailed", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_mainModule", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + } + ], + "name": "deploy", + "outputs": [ + { + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + } + ], + "contractName": "Factory", + "deploymentBytecode": { + "bytecode": "0x608060405234801561001057600080fd5b506101ee806100206000396000f3fe60806040526004361061001e5760003560e01c806332c02a1414610023575b600080fd5b610036610031366004610126565b610052565b6040516001600160a01b03909116815260200160405180910390f35b60008060405180606001604052806028815260200161019160289139846001600160a01b031660405160200161008992919061015e565b60405160208183030381529060405290508281516020830134f56040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a16001600160a01b03821661011f57604051638caac80560e01b81526001600160a01b03851660048201526024810184905260440160405180910390fd5b5092915050565b6000806040838503121561013957600080fd5b82356001600160a01b038116811461015057600080fd5b946020939093013593505050565b6000835160005b8181101561017f5760208187018101518583015201610165565b50919091019182525060200191905056fe603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3a264697066735822122090d0695a8157fb7127653f68740c9d4e6a4521102eaae59315987f42d91b11ac64736f6c63430008120033" + }, + "devdoc": { + "kind": "dev", + "methods": { + "deploy(address,bytes32)": { + "details": "It is recommended to not have more than 200 signers as opcode repricing could make transactions impossible to execute as all the signers must be passed for each transaction.", + "params": { + "_mainModule": "Address of the main module to be used by the wallet", + "_salt": "Salt used to generate the wallet, which is the imageHash of the wallet's configuration." + } + } + }, + "version": 1 + }, + "methodIdentifiers": { + "deploy(address,bytes32)": "0x32c02a14" + }, + "runtimeBytecode": { + "bytecode": "0x60806040526004361061001e5760003560e01c806332c02a1414610023575b600080fd5b610036610031366004610126565b610052565b6040516001600160a01b03909116815260200160405180910390f35b60008060405180606001604052806028815260200161019160289139846001600160a01b031660405160200161008992919061015e565b60405160208183030381529060405290508281516020830134f56040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a16001600160a01b03821661011f57604051638caac80560e01b81526001600160a01b03851660048201526024810184905260440160405180910390fd5b5092915050565b6000806040838503121561013957600080fd5b82356001600160a01b038116811461015057600080fd5b946020939093013593505050565b6000835160005b8181101561017f5760208187018101518583015201610165565b50919091019182525060200191905056fe603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3a264697066735822122090d0695a8157fb7127653f68740c9d4e6a4521102eaae59315987f42d91b11ac64736f6c63430008120033" + }, + "sourceId": "Sequence.sol", + "sourcemap": "3045:955:0:-:0;;;;;;;;;;;;;;;;;;;", + "userdoc": { + "kind": "user", + "methods": { + "deploy(address,bytes32)": { + "notice": "Will deploy a new wallet instance" + } + }, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/SoladyFactory.json b/tests/functional/data/contracts/ethereum/local/SoladyFactory.json new file mode 100644 index 0000000000..c5ff53835c --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/SoladyFactory.json @@ -0,0 +1,78 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "name": "deploySoladyCWIA", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "name": "deploySoladyPush", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "contractName": "SoladyFactory", + "deploymentBytecode": { + "bytecode": "0x6080604052348015600e575f5ffd5b506102408061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063a822303314610038578063f699e19a14610067575b5f5ffd5b61004b6100463660046101e4565b61007a565b6040516001600160a01b03909116815260200160405180910390f35b61004b6100753660046101e4565b6100c9565b5f6100855f83610118565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1919050565b5f6100855f837fce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f75f1b60405160200161010491815260200190565b60405160208183030381529060405261016a565b5f6f5af43d5f5f3e6029573d5ffd5b3d5ff36024528160145271602d5f8160095f39f35f5f365f5f37365f735f526036600e84f09050806101605763301164255f526004601cfd5b5f60245292915050565b5f60405182518060438301826020870160045afa506e5af43d82803e903d91602b57fd5bf360238301528460148301528060881b74fe61002d3d81600a3d39f3363d3d373d3d3d363d730182526037810161ffd38210600b01830187f092505050806101dd5763301164255f526004601cfd5b9392505050565b5f602082840312156101f4575f5ffd5b81356001600160a01b03811681146101dd575f5ffdfea26469706673582212206561f281eea05bea7e3df39cc20ddf3de62a0efb4c88cf92a6f33c4f6ec0579564736f6c634300081c0033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "deploySoladyCWIA(address)": "0xf699e19a", + "deploySoladyPush(address)": "0xa8223033" + }, + "runtimeBytecode": { + "bytecode": "0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063a822303314610038578063f699e19a14610067575b5f5ffd5b61004b6100463660046101e4565b61007a565b6040516001600160a01b03909116815260200160405180910390f35b61004b6100753660046101e4565b6100c9565b5f6100855f83610118565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1919050565b5f6100855f837fce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f75f1b60405160200161010491815260200190565b60405160208183030381529060405261016a565b5f6f5af43d5f5f3e6029573d5ffd5b3d5ff36024528160145271602d5f8160095f39f35f5f365f5f37365f735f526036600e84f09050806101605763301164255f526004601cfd5b5f60245292915050565b5f60405182518060438301826020870160045afa506e5af43d82803e903d91602b57fd5bf360238301528460148301528060881b74fe61002d3d81600a3d39f3363d3d373d3d3d363d730182526037810161ffd38210600b01830187f092505050806101dd5763301164255f526004601cfd5b9392505050565b5f602082840312156101f4575f5ffd5b81356001600160a01b03811681146101dd575f5ffdfea26469706673582212206561f281eea05bea7e3df39cc20ddf3de62a0efb4c88cf92a6f33c4f6ec0579564736f6c634300081c0033" + }, + "sourceId": "SoladyFactory.sol", + "sourcemap": "156501:510:0:-:0;;;;;;;;;;;;;;;;;;;", + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/SplitsCWIA.json b/tests/functional/data/contracts/ethereum/local/SplitsCWIA.json new file mode 100644 index 0000000000..564e7f939f --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/SplitsCWIA.json @@ -0,0 +1,188 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ExampleClone", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CreateFail", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + } + ], + "name": "createClone", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "createDeterministicClone", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "clone", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "contract ExampleClone", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "param1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "param2", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "param3", + "type": "uint64" + }, + { + "internalType": "uint8", + "name": "param4", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "predictDeterministicCloneAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "contractName": "SplitsCloneFactory", + "deploymentBytecode": { + "bytecode": "0x6080604052348015600e575f5ffd5b506040516105dd3803806105dd833981016040819052602b91604e565b5f80546001600160a01b0319166001600160a01b03929092169190911790556079565b5f60208284031215605d575f5ffd5b81516001600160a01b03811681146072575f5ffd5b9392505050565b610557806100865f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063427af71a1461004e57806351979e2d146100855780635c60da1b146100b0578063684fbe55146100c2575b5f5ffd5b61006161005c36600461043d565b6100d5565b604080516001600160a01b0390931683529015156020830152015b60405180910390f35b61009861009336600461043d565b610127565b6040516001600160a01b03909116815260200161007c565b5f54610098906001600160a01b031681565b6100986100d036600461048e565b610174565b5f5f5f878787876040516020016100ef94939291906104d8565b60408051601f198184030181529190525f54909150610118906001600160a01b031685836101fd565b92509250509550959350505050565b5f5f8686868660405160200161014094939291906104d8565b60408051601f198184030181529190525f54909150610169906001600160a01b0316848361027b565b979650505050505050565b5f5f8585858560405160200161018d94939291906104d8565b60408051601f198184030181529190525f549091506101b5906001600160a01b0316826102c3565b6040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a150949350505050565b5f5f5f5f61020b8786610309565b9020604080516001600160f81b03196020808301919091523060601b6bffffffffffffffffffffffff1916602183015260358201999099526055808201939093528151808203909301835260750190528051960195909520966001600160a01b0388163b15159650945050505050565b5f5f5f6102888685610309565b915091508481835ff592506001600160a01b0383166102ba57604051631d7fde3160e31b815260040160405180910390fd5b50509392505050565b5f5f5f6102d08585610309565b9150915080825ff092506001600160a01b03831661030157604051631d7fde3160e31b815260040160405180910390fd5b505092915050565b80516040516067820160e81b716100003d81600a3d39f336602f57343d527f60701b1781527f9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff601282015260028201606881901b7f60203da13d3df35b363d3d373d3d3d3d610000806065363936013d7300000000176032830152606085901b604e8301526c5af43d3d93803e606357fd5bf360981b60628301528351919260710191606f8401602086015b602083106103d45780518252601f1990920191602091820191016103b5565b515f1960086020859003021b16815260f09290921b91015281810160405290939092509050565b80356001600160a01b0381168114610411575f5ffd5b919050565b803567ffffffffffffffff81168114610411575f5ffd5b803560ff81168114610411575f5ffd5b5f5f5f5f5f60a08688031215610451575f5ffd5b61045a866103fb565b94506020860135935061046f60408701610416565b925061047d6060870161042d565b949793965091946080013592915050565b5f5f5f5f608085870312156104a1575f5ffd5b6104aa856103fb565b9350602085013592506104bf60408601610416565b91506104cd6060860161042d565b905092959194509250565b60609490941b6bffffffffffffffffffffffff19168452601484019290925260c01b6001600160c01b031916603483015260f81b6001600160f81b031916603c820152603d019056fea264697066735822122066d0e18b326f34f7f444598fbac04e79ce7eb9589b47680c106ecf89580e1ff764736f6c634300081c0033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "createClone(address,uint256,uint64,uint8)": "0x684fbe55", + "createDeterministicClone(address,uint256,uint64,uint8,bytes32)": "0x51979e2d", + "implementation()": "0x5c60da1b", + "predictDeterministicCloneAddress(address,uint256,uint64,uint8,bytes32)": "0x427af71a" + }, + "runtimeBytecode": { + "bytecode": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063427af71a1461004e57806351979e2d146100855780635c60da1b146100b0578063684fbe55146100c2575b5f5ffd5b61006161005c36600461043d565b6100d5565b604080516001600160a01b0390931683529015156020830152015b60405180910390f35b61009861009336600461043d565b610127565b6040516001600160a01b03909116815260200161007c565b5f54610098906001600160a01b031681565b6100986100d036600461048e565b610174565b5f5f5f878787876040516020016100ef94939291906104d8565b60408051601f198184030181529190525f54909150610118906001600160a01b031685836101fd565b92509250509550959350505050565b5f5f8686868660405160200161014094939291906104d8565b60408051601f198184030181529190525f54909150610169906001600160a01b0316848361027b565b979650505050505050565b5f5f8585858560405160200161018d94939291906104d8565b60408051601f198184030181529190525f549091506101b5906001600160a01b0316826102c3565b6040516001600160a01b03821681529092507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a150949350505050565b5f5f5f5f61020b8786610309565b9020604080516001600160f81b03196020808301919091523060601b6bffffffffffffffffffffffff1916602183015260358201999099526055808201939093528151808203909301835260750190528051960195909520966001600160a01b0388163b15159650945050505050565b5f5f5f6102888685610309565b915091508481835ff592506001600160a01b0383166102ba57604051631d7fde3160e31b815260040160405180910390fd5b50509392505050565b5f5f5f6102d08585610309565b9150915080825ff092506001600160a01b03831661030157604051631d7fde3160e31b815260040160405180910390fd5b505092915050565b80516040516067820160e81b716100003d81600a3d39f336602f57343d527f60701b1781527f9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff601282015260028201606881901b7f60203da13d3df35b363d3d373d3d3d3d610000806065363936013d7300000000176032830152606085901b604e8301526c5af43d3d93803e606357fd5bf360981b60628301528351919260710191606f8401602086015b602083106103d45780518252601f1990920191602091820191016103b5565b515f1960086020859003021b16815260f09290921b91015281810160405290939092509050565b80356001600160a01b0381168114610411575f5ffd5b919050565b803567ffffffffffffffff81168114610411575f5ffd5b803560ff81168114610411575f5ffd5b5f5f5f5f5f60a08688031215610451575f5ffd5b61045a866103fb565b94506020860135935061046f60408701610416565b925061047d6060870161042d565b949793965091946080013592915050565b5f5f5f5f608085870312156104a1575f5ffd5b6104aa856103fb565b9350602085013592506104bf60408601610416565b91506104cd6060860161042d565b905092959194509250565b60609490941b6bffffffffffffffffffffffff19168452601484019290925260c01b6001600160c01b031916603483015260f81b6001600160f81b031916603c820152603d019056fea264697066735822122066d0e18b326f34f7f444598fbac04e79ce7eb9589b47680c106ecf89580e1ff764736f6c634300081c0033" + }, + "sourceId": "SplitsCWIA.sol", + "sourcemap": "17301:1424:0:-:0;;;17457:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;17509:14;:32;;-1:-1:-1;;;;;;17509:32:0;-1:-1:-1;;;;;17509:32:0;;;;;;;;;;17301:1424;;14:310:1;104:6;157:2;145:9;136:7;132:23;128:32;125:52;;;173:1;170;163:12;125:52;199:16;;-1:-1:-1;;;;;244:31:1;;234:42;;224:70;;290:1;287;280:12;224:70;313:5;14:310;-1:-1:-1;;;14:310:1:o;:::-;17301:1424:0;;;;;;", + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/contracts/ethereum/local/VyperFactory.json b/tests/functional/data/contracts/ethereum/local/VyperFactory.json index 7a4093795c..c82af5d9c3 100644 --- a/tests/functional/data/contracts/ethereum/local/VyperFactory.json +++ b/tests/functional/data/contracts/ethereum/local/VyperFactory.json @@ -1 +1,1222 @@ -{"abi":[{"anonymous":false,"inputs":[{"indexed":false,"name":"target","type":"address"}],"name":"Deployment","type":"event"},{"inputs":[{"name":"target","type":"address"},{"name":"_num","type":"uint256"}],"name":"create_contract","outputs":[{"name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"}],"ast":{"ast_type":"Module","children":[{"ast_type":"EventDef","children":[{"ast_type":"AnnAssign","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":12,"end_col_offset":19,"end_lineno":2,"lineno":2,"src":{"contract_id":4,"jump_code":"","length":7,"start":30}},{"ast_type":"Name","children":[],"classification":1,"col_offset":4,"end_col_offset":10,"end_lineno":2,"lineno":2,"src":{"contract_id":4,"jump_code":"","length":6,"start":22}}],"classification":0,"col_offset":4,"end_col_offset":19,"end_lineno":2,"lineno":2,"src":{"contract_id":4,"jump_code":"","length":15,"start":22}}],"classification":0,"col_offset":0,"end_col_offset":19,"end_lineno":2,"lineno":1,"name":"Deployment","src":{"contract_id":4,"jump_code":"","length":37}},{"ast_type":"FunctionDef","children":[{"ast_type":"arguments","children":[{"ast_type":"arg","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":28,"end_col_offset":35,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":7,"start":78}}],"classification":0,"col_offset":20,"end_col_offset":35,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":15,"start":70}},{"ast_type":"arg","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":43,"end_col_offset":50,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":7,"start":93}}],"classification":0,"col_offset":37,"end_col_offset":50,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":13,"start":87}}],"classification":1,"col_offset":20,"end_col_offset":50,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":30,"start":70}},{"ast_type":"AnnAssign","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":12,"end_col_offset":19,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":7,"start":126}},{"ast_type":"Name","children":[],"classification":1,"col_offset":4,"end_col_offset":10,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":6,"start":118}},{"ast_type":"Call","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":44,"end_col_offset":50,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":6,"start":158}},{"ast_type":"Name","children":[],"classification":1,"col_offset":52,"end_col_offset":56,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":4,"start":166}},{"ast_type":"Name","children":[],"classification":1,"col_offset":22,"end_col_offset":43,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":21,"start":136}},{"ast_type":"keyword","children":[{"ast_type":"Int","children":[],"classification":0,"col_offset":70,"end_col_offset":71,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":1,"start":184}}],"classification":0,"col_offset":58,"end_col_offset":71,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":13,"start":172}}],"classification":0,"col_offset":22,"end_col_offset":72,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":50,"start":136}}],"classification":0,"col_offset":4,"end_col_offset":72,"end_lineno":7,"lineno":7,"src":{"contract_id":4,"jump_code":"","length":68,"start":118}},{"ast_type":"Log","children":[{"ast_type":"Call","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":19,"end_col_offset":25,"end_lineno":8,"lineno":8,"src":{"contract_id":4,"jump_code":"","length":6,"start":206}},{"ast_type":"Name","children":[],"classification":1,"col_offset":8,"end_col_offset":18,"end_lineno":8,"lineno":8,"src":{"contract_id":4,"jump_code":"","length":10,"start":195}}],"classification":0,"col_offset":8,"end_col_offset":26,"end_lineno":8,"lineno":8,"src":{"contract_id":4,"jump_code":"","length":18,"start":195}}],"classification":0,"col_offset":4,"end_col_offset":26,"end_lineno":8,"lineno":8,"src":{"contract_id":4,"jump_code":"","length":22,"start":191}},{"ast_type":"Return","children":[{"ast_type":"Name","children":[],"classification":1,"col_offset":11,"end_col_offset":17,"end_lineno":9,"lineno":9,"src":{"contract_id":4,"jump_code":"","length":6,"start":225}}],"classification":0,"col_offset":4,"end_col_offset":17,"end_lineno":9,"lineno":9,"src":{"contract_id":4,"jump_code":"","length":13,"start":218}},{"ast_type":"Name","children":[],"classification":1,"col_offset":1,"end_col_offset":9,"end_lineno":5,"lineno":5,"src":{"contract_id":4,"jump_code":"","length":8,"start":41}},{"ast_type":"Name","children":[],"classification":1,"col_offset":55,"end_col_offset":62,"end_lineno":6,"lineno":6,"src":{"contract_id":4,"jump_code":"","length":7,"start":105}}],"classification":1,"col_offset":0,"end_col_offset":17,"end_lineno":9,"lineno":6,"name":"create_contract","src":{"contract_id":4,"jump_code":"","length":181,"start":50}}],"classification":0,"col_offset":0,"end_col_offset":17,"end_lineno":9,"lineno":1,"name":"VyperFactory.vy","src":{"contract_id":4,"jump_code":"","length":231}},"contractName":"VyperFactory","deploymentBytecode":{"bytecode":"0x6100ca61000f6000396100ca6000f36003361161000c576100b2565b60003560e01c346100b857633599384881186100b057604436106100b8576004358060a01c6100b85760405260405160243560805260805160a05260206003823b0359600182126100b85781600382863c81810160a051815250828201816000f080156100b85790509050905090506060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b505b60006000fd5b600080fda165767970657283000307000b"},"dev_messages":{},"devdoc":{},"pcmap":{"11":{"dev":"dev: Fallback not defined","location":null},"120":{"location":[7,22,7,72]},"121":{"location":[7,22,7,72]},"122":{"location":[7,22,7,72]},"123":{"location":[7,22,7,72]},"124":{"location":[7,4,7,72]},"126":{"location":[7,4,7,72]},"127":{"location":[8,4,8,26]},"160":{"location":[8,19,8,25]},"168":{"location":[8,4,8,26]},"170":{"location":[8,4,8,26]},"173":{"dev":"dev: USER_ASSERT","location":[9,11,9,17]},"20":{"dev":"dev: USER_ASSERT","location":null},"54":{"location":[6,20,6,35]},"56":{"location":[6,20,6,35]},"57":{"location":[7,44,7,50]},"59":{"location":[7,22,7,72]},"60":{"location":[7,52,7,56]},"74":{"location":[7,70,7,71]},"76":{"location":[7,22,7,72]},"89":{"location":[7,70,7,71]},"92":{"location":[7,22,7,72]}},"runtimeBytecode":{"bytecode":"0x6003361161000c576100b2565b60003560e01c346100b857633599384881186100b057604436106100b8576004358060a01c6100b85760405260405160243560805260805160a05260206003823b0359600182126100b85781600382863c81810160a051815250828201816000f080156100b85790509050905090506060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b505b60006000fd5b600080fda165767970657283000307000b"},"sourceId":"VyperFactory.vy","sourcemap":"-1:-1:4:-;;;;:::-;;:::-;:::-;;;;;;;:::-;;;;;:::-;;;;;:::-;;;;;;;:::-;70:15;;158:6;136:50;166:4;-1:-1;;;;;;;;184:1;136:50;-1:-1;;;;;;;:::-;;184:1;-1:-1;136:50;-1:-1;;;;;;;;;;;;;;;;;;:::-;;;;;136:50;;;;118:68;;191:22;206:6;-1:-1;;;;191:22;;-1:-1;225:6;-1:-1;:::-;;:::-;;;;:::-;;;","userdoc":{}} \ No newline at end of file +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "target", + "type": "address" + } + ], + "name": "Deployment", + "type": "event" + }, + { + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "_num", + "type": "uint256" + } + ], + "name": "create_contract", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_masterCopy", + "type": "address" + } + ], + "name": "create_proxy", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "ast": { + "ast_type": "Module", + "children": [ + { + "ast_type": "EventDef", + "children": [ + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 2, + "lineno": 2, + "src": { + "jump_code": "", + "length": 7, + "start": 30 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 2, + "lineno": 2, + "src": { + "jump_code": "", + "length": 6, + "start": 22 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 19, + "end_lineno": 2, + "lineno": 2, + "src": { + "jump_code": "", + "length": 15, + "start": 22 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 19, + "end_lineno": 2, + "lineno": 1, + "name": "Deployment", + "src": { + "jump_code": "", + "length": 37 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 28, + "end_col_offset": 35, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 7, + "start": 78 + } + } + ], + "classification": 0, + "col_offset": 20, + "end_col_offset": 35, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 15, + "start": 70 + } + }, + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 43, + "end_col_offset": 50, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 7, + "start": 93 + } + } + ], + "classification": 0, + "col_offset": 37, + "end_col_offset": 50, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 13, + "start": 87 + } + } + ], + "classification": 1, + "col_offset": 20, + "end_col_offset": 50, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 30, + "start": 70 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 7, + "start": 126 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 6, + "start": 118 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 44, + "end_col_offset": 50, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 6, + "start": 158 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 52, + "end_col_offset": 56, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 4, + "start": 166 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 22, + "end_col_offset": 43, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 21, + "start": 136 + } + }, + { + "ast_type": "keyword", + "children": [ + { + "ast_type": "Int", + "children": [], + "classification": 0, + "col_offset": 70, + "end_col_offset": 71, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 1, + "start": 184 + } + } + ], + "classification": 0, + "col_offset": 58, + "end_col_offset": 71, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 13, + "start": 172 + } + } + ], + "classification": 0, + "col_offset": 22, + "end_col_offset": 72, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 50, + "start": 136 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 72, + "end_lineno": 7, + "lineno": 7, + "src": { + "jump_code": "", + "length": 68, + "start": 118 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 6, + "start": 206 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 18, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 10, + "start": 195 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 26, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 18, + "start": 195 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 8, + "lineno": 8, + "src": { + "jump_code": "", + "length": 22, + "start": 191 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 9, + "lineno": 9, + "src": { + "jump_code": "", + "length": 6, + "start": 225 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 9, + "lineno": 9, + "src": { + "jump_code": "", + "length": 13, + "start": 218 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 5, + "lineno": 5, + "src": { + "jump_code": "", + "length": 8, + "start": 41 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 55, + "end_col_offset": 62, + "end_lineno": 6, + "lineno": 6, + "src": { + "jump_code": "", + "length": 7, + "start": 105 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 9, + "lineno": 6, + "name": "create_contract", + "src": { + "jump_code": "", + "length": 181, + "start": 50 + } + }, + { + "ast_type": "FunctionDef", + "children": [ + { + "ast_type": "arguments", + "children": [ + { + "ast_type": "arg", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 30, + "end_col_offset": 37, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 7, + "start": 273 + } + } + ], + "classification": 0, + "col_offset": 17, + "end_col_offset": 37, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 20, + "start": 260 + } + } + ], + "classification": 1, + "col_offset": 17, + "end_col_offset": 37, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 20, + "start": 260 + } + }, + { + "ast_type": "AnnAssign", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 12, + "end_col_offset": 19, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 7, + "start": 305 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 4, + "end_col_offset": 10, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 6, + "start": 297 + } + }, + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 41, + "end_col_offset": 52, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 11, + "start": 334 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 21, + "end_col_offset": 40, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 19, + "start": 314 + } + } + ], + "classification": 0, + "col_offset": 21, + "end_col_offset": 53, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 32, + "start": 314 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 53, + "end_lineno": 13, + "lineno": 13, + "src": { + "jump_code": "", + "length": 49, + "start": 297 + } + }, + { + "ast_type": "Log", + "children": [ + { + "ast_type": "Call", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 19, + "end_col_offset": 25, + "end_lineno": 14, + "lineno": 14, + "src": { + "jump_code": "", + "length": 6, + "start": 366 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 8, + "end_col_offset": 18, + "end_lineno": 14, + "lineno": 14, + "src": { + "jump_code": "", + "length": 10, + "start": 355 + } + } + ], + "classification": 0, + "col_offset": 8, + "end_col_offset": 26, + "end_lineno": 14, + "lineno": 14, + "src": { + "jump_code": "", + "length": 18, + "start": 355 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 26, + "end_lineno": 14, + "lineno": 14, + "src": { + "jump_code": "", + "length": 22, + "start": 351 + } + }, + { + "ast_type": "Return", + "children": [ + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 11, + "end_col_offset": 17, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 6, + "start": 385 + } + } + ], + "classification": 0, + "col_offset": 4, + "end_col_offset": 17, + "end_lineno": 15, + "lineno": 15, + "src": { + "jump_code": "", + "length": 13, + "start": 378 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 1, + "end_col_offset": 9, + "end_lineno": 11, + "lineno": 11, + "src": { + "jump_code": "", + "length": 8, + "start": 234 + } + }, + { + "ast_type": "Name", + "children": [], + "classification": 1, + "col_offset": 41, + "end_col_offset": 48, + "end_lineno": 12, + "lineno": 12, + "src": { + "jump_code": "", + "length": 7, + "start": 284 + } + } + ], + "classification": 1, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 15, + "lineno": 12, + "name": "create_proxy", + "src": { + "jump_code": "", + "length": 148, + "start": 243 + } + } + ], + "classification": 0, + "col_offset": 0, + "end_col_offset": 17, + "end_lineno": 15, + "lineno": 1, + "src": { + "jump_code": "", + "length": 391 + } + }, + "contractName": "VyperFactory", + "deploymentBytecode": { + "bytecode": "0x61017561001161000039610175610000f35f3560e01c60026003820660011b61016f01601e395f51565b633599384881186101675760443610341761016b576004358060a01c61016b5760405260405160243560805260805160a05260206003823b03596001821261016b5781600382863c81810160a051815250828201815ff0801561016b5790509050905090506060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b63ad8facbe81186101675760243610341761016b576004358060a01c61016b576040527f602d3d8160093d39f3363d3d373d3d3d363d730000000000000000000000000060805260405160601b6093527f5af43d82803e903d91602b57fd5bf3000000000000000000000000000000000060a752603660805ff0801561016b576060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b5f5ffd5b5f80fd00b20018016784190175810600a1657679706572830004000014" + }, + "dev_messages": {}, + "devdoc": {}, + "methodIdentifiers": { + "create_contract(address,uint256)": "0x35993848", + "create_proxy(address)": "0xad8facbe" + }, + "pcmap": { + "0": { + "location": [ + 1, + 0, + 15, + 17 + ] + }, + "106": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "107": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "108": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "109": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "110": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "111": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "112": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "113": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "114": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "117": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "122": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "123": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "124": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "125": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "126": { + "location": [ + 7, + 4, + 7, + 72 + ] + }, + "128": { + "location": [ + 7, + 4, + 7, + 72 + ] + }, + "129": { + "location": [ + 8, + 4, + 8, + 26 + ] + }, + "162": { + "location": [ + 8, + 19, + 8, + 25 + ] + }, + "170": { + "location": [ + 8, + 4, + 8, + 26 + ] + }, + "172": { + "location": [ + 8, + 4, + 8, + 26 + ] + }, + "173": { + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "175": { + "location": [ + 9, + 11, + 9, + 17 + ] + }, + "177": { + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "196": { + "dev": "dev: Invalid calldata or value", + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "199": { + "dev": "dev: Invalid calldata or value", + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "207": { + "dev": "dev: Integer overflow", + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "210": { + "dev": "dev: Integer overflow", + "location": [ + 6, + 0, + 9, + 17 + ] + }, + "211": { + "location": [ + 12, + 17, + 12, + 37 + ] + }, + "213": { + "location": [ + 12, + 17, + 12, + 37 + ] + }, + "247": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "249": { + "location": [ + 13, + 21, + 13, + 53 + ] + }, + "250": { + "location": [ + 13, + 41, + 13, + 52 + ] + }, + "258": { + "location": [ + 13, + 21, + 13, + 53 + ] + }, + "294": { + "location": [ + 13, + 21, + 13, + 53 + ] + }, + "295": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "297": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "299": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "300": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "301": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "302": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "303": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "306": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "307": { + "location": [ + 13, + 4, + 13, + 53 + ] + }, + "309": { + "location": [ + 13, + 4, + 13, + 53 + ] + }, + "310": { + "location": [ + 14, + 4, + 14, + 26 + ] + }, + "343": { + "location": [ + 14, + 19, + 14, + 25 + ] + }, + "351": { + "location": [ + 14, + 4, + 14, + 26 + ] + }, + "353": { + "location": [ + 14, + 4, + 14, + 26 + ] + }, + "354": { + "location": [ + 12, + 0, + 15, + 17 + ] + }, + "356": { + "location": [ + 15, + 11, + 15, + 17 + ] + }, + "358": { + "location": [ + 12, + 0, + 15, + 17 + ] + }, + "362": { + "dev": "dev: Fallback not defined", + "location": null + }, + "42": { + "dev": "dev: Invalid calldata or value", + "location": [ + 1, + 0, + 15, + 17 + ] + }, + "45": { + "dev": "dev: Invalid calldata or value", + "location": [ + 1, + 0, + 15, + 17 + ] + }, + "53": { + "dev": "dev: Integer overflow", + "location": [ + 1, + 0, + 15, + 17 + ] + }, + "56": { + "dev": "dev: Integer overflow", + "location": [ + 1, + 0, + 15, + 17 + ] + }, + "57": { + "location": [ + 6, + 20, + 6, + 35 + ] + }, + "59": { + "location": [ + 6, + 20, + 6, + 35 + ] + }, + "60": { + "location": [ + 7, + 44, + 7, + 50 + ] + }, + "62": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "63": { + "location": [ + 7, + 52, + 7, + 56 + ] + }, + "77": { + "location": [ + 7, + 70, + 7, + 71 + ] + }, + "79": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "85": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "87": { + "dev": "dev: EMPTY_TARGET_(CREATE_FROM_BLUEPRINT)", + "location": null + }, + "90": { + "dev": "dev: EMPTY_TARGET_(CREATE_FROM_BLUEPRINT)", + "location": null + }, + "91": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "92": { + "location": [ + 7, + 70, + 7, + 71 + ] + }, + "94": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "95": { + "location": [ + 7, + 22, + 7, + 72 + ] + }, + "97": { + "dev": "dev: CREATE_FAILED", + "location": null + }, + "98": { + "dev": "dev: CREATE_FAILED", + "location": null + } + }, + "runtimeBytecode": { + "bytecode": "0x5f3560e01c60026003820660011b61016f01601e395f51565b633599384881186101675760443610341761016b576004358060a01c61016b5760405260405160243560805260805160a05260206003823b03596001821261016b5781600382863c81810160a051815250828201815ff0801561016b5790509050905090506060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b63ad8facbe81186101675760243610341761016b576004358060a01c61016b576040527f602d3d8160093d39f3363d3d373d3d3d363d730000000000000000000000000060805260405160601b6093527f5af43d82803e903d91602b57fd5bf3000000000000000000000000000000000060a752603660805ff0801561016b576060527f8caefb0c150c4d097593deb07f0591b0225f67392b0ae76f2df209b8be37b82a60605160805260206080a160206060f35b5f5ffd5b5f80fd00b200180167" + }, + "sourceId": "VyperFactory.vy", + "sourcemap": "0:391:0:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;70:15:0;-1:-1:-1;70:15:0;158:6:0;-1:-1:-1;136:50:0;166:4:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;184:1:0;-1:-1:-1;136:50:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;184:1:0;-1:-1:-1;-1:-1:-1;136:50:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;136:50:0;136:50:0;136:50:0;136:50:0;118:68:0;-1:-1:-1;118:68:0;191:22:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;206:6:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;191:22:0;-1:-1:-1;191:22:0;50:181:0;-1:-1:-1;225:6:0;-1:-1:-1;50:181:0;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;260:20:0;-1:-1:-1;260:20:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;314:32:0;334:11:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;314:32:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;314:32:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;297:49:0;-1:-1:-1;297:49:0;351:22:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;366:6:0;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;351:22:0;-1:-1:-1;351:22:0;243:148:0;-1:-1:-1;385:6:0;-1:-1:-1;243:148:0;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1:-;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1;-1:-1:-1", + "userdoc": {} +} diff --git a/tests/functional/data/sources/CWIA.sol b/tests/functional/data/sources/CWIA.sol new file mode 100644 index 0000000000..d1a08da9f7 --- /dev/null +++ b/tests/functional/data/sources/CWIA.sol @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: BSD +pragma solidity ^0.8.4; + +// src/Clone.sol + +/// @title Clone +/// @author zefram.eth +/// @notice Provides helper functions for reading immutable args from calldata +contract Clone { + /// @notice Reads an immutable arg with type address + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgAddress( + uint256 argOffset + ) internal pure returns (address arg) { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0x60, calldataload(add(offset, argOffset))) + } + } + + /// @notice Reads an immutable arg with type uint256 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint256( + uint256 argOffset + ) internal pure returns (uint256 arg) { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := calldataload(add(offset, argOffset)) + } + } + + /// @notice Reads a uint256 array stored in the immutable args. + /// @param argOffset The offset of the arg in the packed data + /// @param arrLen Number of elements in the array + /// @return arr The array + function _getArgUint256Array( + uint256 argOffset, + uint64 arrLen + ) internal pure returns (uint256[] memory arr) { + uint256 offset = _getImmutableArgsOffset(); + uint256 el; + arr = new uint256[](arrLen); + for (uint64 i = 0; i < arrLen; i++) { + // solhint-disable-next-line no-inline-assembly + assembly { + el := calldataload(add(add(offset, argOffset), mul(i, 32))) + } + arr[i] = el; + } + return arr; + } + + /// @notice Reads an immutable arg with type uint64 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint64( + uint256 argOffset + ) internal pure returns (uint64 arg) { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0xc0, calldataload(add(offset, argOffset))) + } + } + + /// @notice Reads an immutable arg with type uint8 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0xf8, calldataload(add(offset, argOffset))) + } + } + + /// @return offset The offset of the packed immutable args in calldata + function _getImmutableArgsOffset() internal pure returns (uint256 offset) { + // solhint-disable-next-line no-inline-assembly + assembly { + offset := sub( + calldatasize(), + add(shr(240, calldataload(sub(calldatasize(), 2))), 2) + ) + } + } +} + +// src/ClonesWithImmutableArgs.sol + +/// @title ClonesWithImmutableArgs +/// @author wighawag, zefram.eth, nick.eth +/// @notice Enables creating clone contracts with immutable args +library ClonesWithImmutableArgs { + /// @dev The CREATE3 proxy bytecode. + uint256 private constant _CREATE3_PROXY_BYTECODE = + 0x67363d3d37363d34f03d5260086018f3; + + /// @dev Hash of the `_CREATE3_PROXY_BYTECODE`. + /// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`. + bytes32 private constant _CREATE3_PROXY_BYTECODE_HASH = + 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f; + + error CreateFail(); + error InitializeFail(); + + enum CloneType { + CREATE, + CREATE2, + PREDICT_CREATE2 + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return instance The address of the created clone + function clone( + address implementation, + bytes memory data + ) internal returns (address payable instance) { + return clone(implementation, data, 0); + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @param value The amount of wei to transfer to the created clone + /// @return instance The address of the created clone + function clone( + address implementation, + bytes memory data, + uint256 value + ) internal returns (address payable instance) { + bytes memory creationcode = getCreationBytecode(implementation, data); + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create( + value, + add(creationcode, 0x20), + mload(creationcode) + ) + } + if (instance == address(0)) { + revert CreateFail(); + } + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args, + /// using CREATE2 + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return instance The address of the created clone + function clone2( + address implementation, + bytes memory data + ) internal returns (address payable instance) { + return clone2(implementation, data, 0); + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args, + /// using CREATE2 + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @param value The amount of wei to transfer to the created clone + /// @return instance The address of the created clone + function clone2( + address implementation, + bytes memory data, + uint256 value + ) internal returns (address payable instance) { + bytes memory creationcode = getCreationBytecode(implementation, data); + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create2( + value, + add(creationcode, 0x20), + mload(creationcode), + 0 + ) + } + if (instance == address(0)) { + revert CreateFail(); + } + } + + /// @notice Computes the address of a clone created using CREATE2 + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return instance The address of the clone + function addressOfClone2( + address implementation, + bytes memory data + ) internal view returns (address payable instance) { + bytes memory creationcode = getCreationBytecode(implementation, data); + bytes32 bytecodeHash = keccak256(creationcode); + instance = payable( + address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + address(this), + bytes32(0), + bytecodeHash + ) + ) + ) + ) + ) + ); + } + + /// @notice Computes bytecode for a clone + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return ret Creation bytecode for the clone contract + function getCreationBytecode( + address implementation, + bytes memory data + ) internal pure returns (bytes memory ret) { + // unrealistic for memory ptr or data length to exceed 256 bits + unchecked { + uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call + uint256 creationSize = 0x41 + extraLength; + uint256 runSize = creationSize - 10; + uint256 dataPtr; + uint256 ptr; + + // solhint-disable-next-line no-inline-assembly + assembly { + ret := mload(0x40) + mstore(ret, creationSize) + mstore(0x40, add(ret, creationSize)) + ptr := add(ret, 0x20) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (10 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 61 runtime | PUSH2 runtime (r) | r | – + mstore( + ptr, + 0x6100000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits) + + // creation size = 0a + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (55 bytes + extraLength) + // ------------------------------------------------------------------------------------------------------------- + + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 61 | PUSH2 extra | extra 0 0 0 0 | [0, cds) = calldata + mstore( + add(ptr, 0x03), + 0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000 + ) + mstore(add(ptr, 0x13), shl(240, extraLength)) + + // 60 0x37 | PUSH1 0x37 | 0x37 extra 0 0 0 0 | [0, cds) = calldata // 0x37 (55) is runtime size - data + // 36 | CALLDATASIZE | cds 0x37 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 61 extra | PUSH2 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + mstore( + add(ptr, 0x15), + 0x6037363936610000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x1b), shl(240, extraLength)) + + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + mstore( + add(ptr, 0x1d), + 0x013d730000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x20), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+extra) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+extra) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x35 | PUSH1 0x35 | 0x35 sucess 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore( + add(ptr, 0x34), + 0x5af43d3d93803e603557fd5bf300000000000000000000000000000000000000 + ) + } + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // (but also send as appended data to the delegatecall) + // ------------------------------------------------------------------------------------------------------------- + + extraLength -= 2; + uint256 counter = extraLength; + uint256 copyPtr = ptr + 0x41; + // solhint-disable-next-line no-inline-assembly + assembly { + dataPtr := add(data, 32) + } + for (; counter >= 32; counter -= 32) { + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, mload(dataPtr)) + } + + copyPtr += 32; + dataPtr += 32; + } + uint256 mask = ~(256 ** (32 - counter) - 1); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, and(mload(dataPtr), mask)) + } + copyPtr += counter; + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, shl(240, extraLength)) + } + } + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args. Uses CREATE3 + /// to implement deterministic deployment. + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @param salt The salt used by the CREATE3 deployment + /// @return deployed The address of the created clone + function clone3( + address implementation, + bytes memory data, + bytes32 salt + ) internal returns (address deployed) { + return clone3(implementation, data, salt, 0); + } + + /// @notice Creates a clone proxy of the implementation contract, with immutable args. Uses CREATE3 + /// to implement deterministic deployment. + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @param salt The salt used by the CREATE3 deployment + /// @param value The amount of wei to transfer to the created clone + /// @return deployed The address of the created clone + function clone3( + address implementation, + bytes memory data, + bytes32 salt, + uint256 value + ) internal returns (address deployed) { + // unrealistic for memory ptr or data length to exceed 256 bits + unchecked { + uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call + uint256 creationSize = 0x43 + extraLength; + uint256 ptr; + // solhint-disable-next-line no-inline-assembly + assembly { + ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (11 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 3d | RETURNDATASIZE | 0 | – + // 61 runtime | PUSH2 runtime (r) | r 0 | – + mstore( + ptr, + 0x3d61000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x02), shl(240, sub(creationSize, 11))) // size of the contract running bytecode (16 bits) + + // creation size = 0b + // 80 | DUP1 | r r 0 | – + // 60 creation | PUSH1 creation (c) | c r r 0 | – + // 3d | RETURNDATASIZE | 0 c r r 0 | – + // 39 | CODECOPY | r 0 | [0-2d]: runtime code + // 81 | DUP2 | 0 c 0 | [0-2d]: runtime code + // f3 | RETURN | 0 | [0-2d]: runtime code + mstore( + add(ptr, 0x04), + 0x80600b3d3981f300000000000000000000000000000000000000000000000000 + ) + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME + // ------------------------------------------------------------------------------------------------------------- + + // 36 | CALLDATASIZE | cds | – + // 3d | RETURNDATASIZE | 0 cds | – + // 3d | RETURNDATASIZE | 0 0 cds | – + // 37 | CALLDATACOPY | – | [0, cds] = calldata + // 61 | PUSH2 extra | extra | [0, cds] = calldata + mstore( + add(ptr, 0x0b), + 0x363d3d3761000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x10), shl(240, extraLength)) + + // 60 0x38 | PUSH1 0x38 | 0x38 extra | [0, cds] = calldata // 0x38 (56) is runtime size - data + // 36 | CALLDATASIZE | cds 0x38 extra | [0, cds] = calldata + // 39 | CODECOPY | _ | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata + // 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata + // 61 extra | PUSH2 extra | extra cds 0 0 0 | [0, cds] = calldata + mstore( + add(ptr, 0x12), + 0x603836393d3d3d36610000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x1b), shl(240, extraLength)) + + // 01 | ADD | cds+extra 0 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata + mstore( + add(ptr, 0x1d), + 0x013d730000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x20), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata + // f4 | DELEGATECALL | success 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata + // 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata + // 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata + // 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds) + // 90 | SWAP1 | 0 success | [0, rds] = return data + // 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data + // 91 | SWAP2 | success 0 rds | [0, rds] = return data + // 60 0x36 | PUSH1 0x36 | 0x36 sucess 0 rds | [0, rds] = return data + // 57 | JUMPI | 0 rds | [0, rds] = return data + // fd | REVERT | – | [0, rds] = return data + // 5b | JUMPDEST | 0 rds | [0, rds] = return data + // f3 | RETURN | – | [0, rds] = return data + + mstore( + add(ptr, 0x34), + 0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000 + ) + } + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // (but also send as appended data to the delegatecall) + // ------------------------------------------------------------------------------------------------------------- + + extraLength -= 2; + uint256 counter = extraLength; + uint256 copyPtr = ptr + 0x43; + uint256 dataPtr; + // solhint-disable-next-line no-inline-assembly + assembly { + dataPtr := add(data, 32) + } + for (; counter >= 32; counter -= 32) { + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, mload(dataPtr)) + } + + copyPtr += 32; + dataPtr += 32; + } + uint256 mask = ~(256 ** (32 - counter) - 1); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, and(mload(dataPtr), mask)) + } + copyPtr += counter; + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, shl(240, extraLength)) + } + + /// @solidity memory-safe-assembly + // solhint-disable-next-line no-inline-assembly + assembly { + // Store the `_PROXY_BYTECODE` into scratch space. + mstore(0x00, _CREATE3_PROXY_BYTECODE) + // Deploy a new contract with our pre-made bytecode via CREATE2. + let proxy := create2(0, 0x10, 0x10, salt) + + // If the result of `create2` is the zero address, revert. + if iszero(proxy) { + // Store the function selector of `CreateFail()`. + mstore(0x00, 0xebfef188) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + + // Store the proxy's address. + mstore(0x14, proxy) + // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01). + // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex). + mstore(0x00, 0xd694) + // Nonce of the proxy contract (1). + mstore8(0x34, 0x01) + + deployed := and( + keccak256(0x1e, 0x17), + 0xffffffffffffffffffffffffffffffffffffffff + ) + + // If the `call` fails or the code size of `deployed` is zero, revert. + // The second argument of the or() call is evaluated first, which is important + // here because extcodesize(deployed) is only non-zero after the call() to the proxy + // is made and the contract is successfully deployed. + if or( + iszero(extcodesize(deployed)), + iszero( + call( + gas(), // Gas remaining. + proxy, // Proxy's address. + value, // Ether value. + ptr, // Pointer to the creation code + creationSize, // Size of the creation code + 0x00, // Offset of output. + 0x00 // Length of output. + ) + ) + ) { + // Store the function selector of `InitializeFail()`. + mstore(0x00, 0x8f86d2f1) + // Revert with (offset, size). + revert(0x1c, 0x04) + } + } + } + } + + /// @notice Returns the CREATE3 deterministic address of the contract deployed via cloneDeterministic(). + /// @dev Forked from https://github.com/Vectorized/solady/blob/main/src/utils/CREATE3.sol + /// @param salt The salt used by the CREATE3 deployment + function addressOfClone3( + bytes32 salt + ) internal view returns (address deployed) { + /// @solidity memory-safe-assembly + // solhint-disable-next-line no-inline-assembly + assembly { + // Cache the free memory pointer. + let m := mload(0x40) + // Store `address(this)`. + mstore(0x00, address()) + // Store the prefix. + mstore8(0x0b, 0xff) + // Store the salt. + mstore(0x20, salt) + // Store the bytecode hash. + mstore(0x40, _CREATE3_PROXY_BYTECODE_HASH) + + // Store the proxy's address. + mstore(0x14, keccak256(0x0b, 0x55)) + // Restore the free memory pointer. + mstore(0x40, m) + // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01). + // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex). + mstore(0x00, 0xd694) + // Nonce of the proxy contract (1). + mstore8(0x34, 0x01) + + deployed := and( + keccak256(0x1e, 0x17), + 0xffffffffffffffffffffffffffffffffffffffff + ) + } + } +} + +// src/ExampleClone.sol + +contract ExampleClone is Clone { + function param1() public pure returns (address) { + return _getArgAddress(0); + } + + function param2() public pure returns (uint256) { + return _getArgUint256(20); + } + + function param3() public pure returns (uint64) { + return _getArgUint64(52); + } + + function param4() public pure returns (uint8) { + return _getArgUint8(60); + } +} + +// src/ExampleCloneFactory.sol + +contract ExampleCloneFactory { + using ClonesWithImmutableArgs for address; + + ExampleClone public implementation; + + event Target(address addr); + + constructor(ExampleClone implementation_) { + implementation = implementation_; + } + + function createClone( + address param1, + uint256 param2, + uint64 param3, + uint8 param4 + ) external payable returns (ExampleClone clone) { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = ExampleClone(address(implementation).clone(data, msg.value)); + emit Target(address(clone)); + } + + function createClone2( + address param1, + uint256 param2, + uint64 param3, + uint8 param4 + ) external payable returns (ExampleClone clone) { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = ExampleClone(address(implementation).clone2(data, msg.value)); + } + + function addressOfClone2( + address param1, + uint256 param2, + uint64 param3, + uint8 param4 + ) external view returns (address clone) { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = address(implementation).addressOfClone2(data); + } + + function createClone3( + address param1, + uint256 param2, + uint64 param3, + uint8 param4, + bytes32 salt + ) external payable returns (ExampleClone clone) { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = ExampleClone( + address(implementation).clone3(data, salt, msg.value) + ); + } + + function addressOfClone3(bytes32 salt) external view returns (address) { + return ClonesWithImmutableArgs.addressOfClone3(salt); + } +} + diff --git a/tests/functional/data/sources/ClonesFactory.sol b/tests/functional/data/sources/ClonesFactory.sol new file mode 100644 index 0000000000..3176a9bd64 --- /dev/null +++ b/tests/functional/data/sources/ClonesFactory.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.4; + +/// @notice create opcode failed +error CreateError(); +/// @notice create2 opcode failed +error Create2Error(); + +library Clones { + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` + * except when someone calls `receive()` and then it emits an event matching + * `SplitWallet.ReceiveETH(indexed address, amount)` + * Inspired by OZ & 0age's minimal clone implementations based on eip 1167 found at + * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.3.0/contracts/proxy/Clones.sol + * and https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48 + * + * This function uses the create2 opcode and a `salt` to deterministically deploy + * the clone. Using the same `implementation` and `salt` multiple time will revert, since + * the clones cannot be deployed twice at the same address. + * + * init: 0x3d605d80600a3d3981f3 + * 3d returndatasize 0 + * 605d push1 0x5d 0x5d 0 + * 80 dup1 0x5d 0x5d 0 + * 600a push1 0x0a 0x0a 0x5d 0x5d 0 + * 3d returndatasize 0 0x0a 0x5d 0x5d 0 + * 39 codecopy 0x5d 0 destOffset offset length memory[destOffset:destOffset+length] = address(this).code[offset:offset+length] copy executing contracts bytecode + * 81 dup2 0 0x5d 0 + * f3 return 0 offset length return memory[offset:offset+length] returns from this contract call + * + * contract: 0x36603057343d52307f830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b160203da23d3df35b3d3d3d3d363d3d37363d73bebebebebebebebebebebebebebebebebebebebe5af43d3d93803e605b57fd5bf3 + * 0x000 36 calldatasize cds + * 0x001 6030 push1 0x30 0x30 cds + * ,=< 0x003 57 jumpi + * | 0x004 34 callvalue cv + * | 0x005 3d returndatasize 0 cv + * | 0x006 52 mstore + * | 0x007 30 address addr + * | 0x008 7f830d.. push32 0x830d.. id addr + * | 0x029 6020 push1 0x20 0x20 id addr + * | 0x02b 3d returndatasize 0 0x20 id addr + * | 0x02c a2 log2 + * | 0x02d 3d returndatasize 0 + * | 0x02e 3d returndatasize 0 0 + * | 0x02f f3 return + * `-> 0x030 5b jumpdest + * 0x031 3d returndatasize 0 + * 0x032 3d returndatasize 0 0 + * 0x033 3d returndatasize 0 0 0 + * 0x034 3d returndatasize 0 0 0 0 + * 0x035 36 calldatasize cds 0 0 0 0 + * 0x036 3d returndatasize 0 cds 0 0 0 0 + * 0x037 3d returndatasize 0 0 cds 0 0 0 0 + * 0x038 37 calldatacopy 0 0 0 0 + * 0x039 36 calldatasize cds 0 0 0 0 + * 0x03a 3d returndatasize 0 cds 0 0 0 0 + * 0x03b 73bebe.. push20 0xbebe.. 0xbebe 0 cds 0 0 0 0 + * 0x050 5a gas gas 0xbebe 0 cds 0 0 0 0 + * 0x051 f4 delegatecall suc 0 0 + * 0x052 3d returndatasize rds suc 0 0 + * 0x053 3d returndatasize rds rds suc 0 0 + * 0x054 93 swap4 0 rds suc 0 rds + * 0x055 80 dup1 0 0 rds suc 0 rds + * 0x056 3e returndatacopy suc 0 rds + * 0x057 605b push1 0x5b 0x5b suc 0 rds + * ,=< 0x059 57 jumpi 0 rds + * | 0x05a fd revert + * `-> 0x05b 5b jumpdest 0 rds + * 0x05c f3 return + * + */ + function clone(address implementation) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + mstore( + ptr, + 0x3d605d80600a3d3981f336603057343d52307f00000000000000000000000000 + ) + mstore( + add(ptr, 0x13), + 0x830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b1 + ) + mstore( + add(ptr, 0x33), + 0x60203da23d3df35b3d3d3d3d363d3d37363d7300000000000000000000000000 + ) + mstore(add(ptr, 0x46), shl(0x60, implementation)) + mstore( + add(ptr, 0x5a), + 0x5af43d3d93803e605b57fd5bf300000000000000000000000000000000000000 + ) + instance := create(0, ptr, 0x67) + } + if (instance == address(0)) revert CreateError(); + } + + function cloneDeterministic(address implementation, bytes32 salt) + internal + returns (address instance) + { + assembly { + let ptr := mload(0x40) + mstore( + ptr, + 0x3d605d80600a3d3981f336603057343d52307f00000000000000000000000000 + ) + mstore( + add(ptr, 0x13), + 0x830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b1 + ) + mstore( + add(ptr, 0x33), + 0x60203da23d3df35b3d3d3d3d363d3d37363d7300000000000000000000000000 + ) + mstore(add(ptr, 0x46), shl(0x60, implementation)) + mstore( + add(ptr, 0x5a), + 0x5af43d3d93803e605b57fd5bf300000000000000000000000000000000000000 + ) + instance := create2(0, ptr, 0x67, salt) + } + if (instance == address(0)) revert Create2Error(); + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + assembly { + let ptr := mload(0x40) + mstore( + ptr, + 0x3d605d80600a3d3981f336603057343d52307f00000000000000000000000000 + ) + mstore( + add(ptr, 0x13), + 0x830d2d700a97af574b186c80d40429385d24241565b08a7c559ba283a964d9b1 + ) + mstore( + add(ptr, 0x33), + 0x60203da23d3df35b3d3d3d3d363d3d37363d7300000000000000000000000000 + ) + mstore(add(ptr, 0x46), shl(0x60, implementation)) + mstore( + add(ptr, 0x5a), + 0x5af43d3d93803e605b57fd5bf3ff000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x68), shl(0x60, deployer)) + mstore(add(ptr, 0x7c), salt) + mstore(add(ptr, 0x9c), keccak256(ptr, 0x67)) + predicted := keccak256(add(ptr, 0x67), 0x55) + } + } + + /** + * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. + */ + function predictDeterministicAddress(address implementation, bytes32 salt) + internal + view + returns (address predicted) + { + return predictDeterministicAddress(implementation, salt, address(this)); + } +} + +contract ClonesFactory { + event Target(address addr); + + function deployClonesProxy(address _implementation) external returns (address split){ + split = Clones.clone(_implementation); + emit Target(split); + } +} \ No newline at end of file diff --git a/tests/functional/data/sources/LSSVMPairFactory.sol b/tests/functional/data/sources/LSSVMPairFactory.sol new file mode 100644 index 0000000000..9d7da53a44 --- /dev/null +++ b/tests/functional/data/sources/LSSVMPairFactory.sol @@ -0,0 +1,5732 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.0 ^0.8.0 ^0.8.1 ^0.8.2 ^0.8.4; + +// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + +// lib/openzeppelin-contracts/contracts/utils/Address.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling + * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * + * _Available since v4.8._ + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata, + string memory errorMessage + ) internal view returns (bytes memory) { + if (success) { + if (returndata.length == 0) { + // only check isContract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + require(isContract(target), "Address: call to non-contract"); + } + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + /** + * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason or using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + function _revert(bytes memory returndata, string memory errorMessage) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// lib/openzeppelin-contracts-upgradeable/contracts/access/IAccessControlUpgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) + +/** + * @dev External interface of AccessControl declared to support ERC165 detection. + */ +interface IAccessControlUpgradeable { + /** + * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + * + * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite + * {RoleAdminChanged} not being emitted signaling this. + * + * _Available since v3.1._ + */ + event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); + + /** + * @dev Emitted when `account` is granted `role`. + * + * `sender` is the account that originated the contract call, an admin role + * bearer except when using {AccessControl-_setupRole}. + */ + event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Emitted when `account` is revoked `role`. + * + * `sender` is the account that originated the contract call: + * - if using `revokeRole`, it is the admin role bearer + * - if using `renounceRole`, it is the role bearer (i.e. `account`) + */ + event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); + + /** + * @dev Returns `true` if `account` has been granted `role`. + */ + function hasRole(bytes32 role, address account) external view returns (bool); + + /** + * @dev Returns the admin role that controls `role`. See {grantRole} and + * {revokeRole}. + * + * To change a role's admin, use {AccessControl-_setRoleAdmin}. + */ + function getRoleAdmin(bytes32 role) external view returns (bytes32); + + /** + * @dev Grants `role` to `account`. + * + * If `account` had not been already granted `role`, emits a {RoleGranted} + * event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function grantRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from `account`. + * + * If `account` had been granted `role`, emits a {RoleRevoked} event. + * + * Requirements: + * + * - the caller must have ``role``'s admin role. + */ + function revokeRole(bytes32 role, address account) external; + + /** + * @dev Revokes `role` from the calling account. + * + * Roles are often managed via {grantRole} and {revokeRole}: this function's + * purpose is to provide a mechanism for accounts to lose their privileges + * if they are compromised (such as when a trusted device is misplaced). + * + * If the calling account had been granted `role`, emits a {RoleRevoked} + * event. + * + * Requirements: + * + * - the caller must be `account`. + */ + function renounceRole(bytes32 role, address account) external; +} + +// lib/openzeppelin-contracts-upgradeable/contracts/utils/AddressUpgradeable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) + +/** + * @dev Collection of functions related to the address type + */ +library AddressUpgradeable { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling + * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * + * _Available since v4.8._ + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata, + string memory errorMessage + ) internal view returns (bytes memory) { + if (success) { + if (returndata.length == 0) { + // only check isContract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + require(isContract(target), "Address: call to non-contract"); + } + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + /** + * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason or using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + function _revert(bytes memory returndata, string memory errorMessage) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } +} + +// lib/solmate/src/auth/Owned.sol + +/// @notice Simple single owner authorization mixin. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) +abstract contract Owned { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event OwnershipTransferred(address indexed user, address indexed newOwner); + + /*////////////////////////////////////////////////////////////// + OWNERSHIP STORAGE + //////////////////////////////////////////////////////////////*/ + + address public owner; + + modifier onlyOwner() virtual { + require(msg.sender == owner, "UNAUTHORIZED"); + + _; + } + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor(address _owner) { + owner = _owner; + + emit OwnershipTransferred(address(0), _owner); + } + + /*////////////////////////////////////////////////////////////// + OWNERSHIP LOGIC + //////////////////////////////////////////////////////////////*/ + + function transferOwnership(address newOwner) public virtual onlyOwner { + owner = newOwner; + + emit OwnershipTransferred(msg.sender, newOwner); + } +} + +// lib/solmate/src/tokens/ERC20.sol + +/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) +/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) +/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. +abstract contract ERC20 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public name; + + string public symbol; + + uint8 public immutable decimals; + + /*////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*////////////////////////////////////////////////////////////// + EIP-2612 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 internal immutable INITIAL_CHAIN_ID; + + bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + + mapping(address => uint256) public nonces; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + + INITIAL_CHAIN_ID = block.chainid; + INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); + } + + /*////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + return true; + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } + + /*////////////////////////////////////////////////////////////// + EIP-2612 LOGIC + //////////////////////////////////////////////////////////////*/ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + address recoveredAddress = ecrecover( + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ), + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ), + v, + r, + s + ); + + require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + + allowance[recoveredAddress][spender] = value; + } + + emit Approval(owner, spender, value); + } + + function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { + return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + } + + function computeDomainSeparator() internal view virtual returns (bytes32) { + return + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name)), + keccak256("1"), + block.chainid, + address(this) + ) + ); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint(address to, uint256 amount) internal virtual { + totalSupply += amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(address(0), to, amount); + } + + function _burn(address from, uint256 amount) internal virtual { + balanceOf[from] -= amount; + + // Cannot underflow because a user's balance + // will never be larger than the total supply. + unchecked { + totalSupply -= amount; + } + + emit Transfer(from, address(0), amount); + } +} + +// src/bonding-curves/CurveErrorCodes.sol + +contract CurveErrorCodes { + enum Error { + OK, // No error + INVALID_NUMITEMS, // The numItem value is 0 + SPOT_PRICE_OVERFLOW, // The updated spot price doesn't fit into 128 bits + DELTA_OVERFLOW, // The updated delta doesn't fit into 128 bits + SPOT_PRICE_UNDERFLOW, // The updated spot price goes too low + AUCTION_ENDED // The auction has ended + } +} + +// src/lib/IOwnershipTransferReceiver.sol + +interface IOwnershipTransferReceiver { + function onOwnershipTransferred(address oldOwner, bytes memory data) external payable; +} + +// src/property-checking/IPropertyChecker.sol + +interface IPropertyChecker { + function hasProperties(uint256[] calldata ids, bytes calldata params) external returns (bool); +} + +// src/royalty-auth/IArtBlocks.sol + +/** + * @dev Art Blocks nfts + */ +interface IArtBlocks { + // document getter function of public variable + function admin() external view returns (address); +} + +// src/royalty-auth/IDigitalax.sol + +/** + * @dev Digitalax nfts + */ +interface IDigitalax { + function accessControls() external view returns (address); +} + +/** + * @dev Digitalax Access Controls Simple + */ +interface IDigitalaxAccessControls { + function hasAdminRole(address _account) external view returns (bool); +} + +// src/royalty-auth/IFoundation.sol + +interface IFoundation { + /* + * bytes4(keccak256('getFees(uint256)')) == 0xd5a06d4c + * + * => 0xd5a06d4c = 0xd5a06d4c + */ + function getFees(uint256 tokenId) external view returns (address payable[] memory, uint256[] memory); +} + +interface IFoundationTreasuryNode { + function getFoundationTreasury() external view returns (address payable); +} + +interface IFoundationTreasury { + function isAdmin(address account) external view returns (bool); +} + +// src/royalty-auth/INiftyGateway.sol + +/** + * @dev Nifty builder instance + */ +interface INiftyBuilderInstance { + function niftyRegistryContract() external view returns (address); +} + +/** + * @dev Nifty registry + */ +interface INiftyRegistry { + /** + * @dev function to see if sending key is valid + */ + function isValidNiftySender(address sending_key) external view returns (bool); +} + +// src/settings/ISettings.sol + +interface ISettings { + struct PairInfo { + address prevOwner; + uint96 unlockTime; + address prevFeeRecipient; + } + + function getFeeSplitBps() external pure returns (uint64); + + function getRoyaltyInfo(address pairAddress) external view returns (bool, uint96); + + function settingsFeeRecipient() external returns (address payable); + + function getPrevFeeRecipientForPair(address pairAddress) external returns (address); +} + +// lib/libraries-solidity/contracts/access/IAdminControl.sol + +/// @author: manifold.xyz + +/** + * @dev Interface for admin control + */ +interface IAdminControl is IERC165 { + + event AdminApproved(address indexed account, address indexed sender); + event AdminRevoked(address indexed account, address indexed sender); + + /** + * @dev gets address of all admins + */ + function getAdmins() external view returns (address[] memory); + + /** + * @dev add an admin. Can only be called by contract owner. + */ + function approveAdmin(address admin) external; + + /** + * @dev remove an admin. Can only be called by contract owner. + */ + function revokeAdmin(address admin) external; + + /** + * @dev checks whether or not given address is an admin + * Returns True if they are + */ + function isAdmin(address admin) external view returns (bool); + +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol) + +/** + * @dev Required interface of an ERC1155 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1155[EIP]. + * + * _Available since v3.1._ + */ +interface IERC1155 is IERC165 { + /** + * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the amount of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory); + + /** + * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) external; +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + +// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 + * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must + * understand this adds an external call which potentially creates a reentrancy vulnerability. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +} + +// lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol) + +/** + * @dev Implementation of the {IERC721Receiver} interface. + * + * Accepts all token transfers. + * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. + */ +contract ERC721Holder is IERC721Receiver { + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol) + +/** + * @dev Library used to query support of an interface declared via {IERC165}. + * + * Note that these functions return the actual result of the query: they do not + * `revert` if an interface is not supported. It is up to the caller to decide + * what to do in these cases. + */ +library ERC165Checker { + // As per the EIP-165 spec, no interface should ever match 0xffffffff + bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; + + /** + * @dev Returns true if `account` supports the {IERC165} interface. + */ + function supportsERC165(address account) internal view returns (bool) { + // Any contract that implements ERC165 must explicitly indicate support of + // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid + return + supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && + !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID); + } + + /** + * @dev Returns true if `account` supports the interface defined by + * `interfaceId`. Support for {IERC165} itself is queried automatically. + * + * See {IERC165-supportsInterface}. + */ + function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { + // query support of both ERC165 as per the spec and support of _interfaceId + return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); + } + + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + * + * _Available since v3.4._ + */ + function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) + internal + view + returns (bool[] memory) + { + // an array of booleans corresponding to interfaceIds and whether they're supported or not + bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + + /** + * @dev Returns true if `account` supports all the interfaces defined in + * `interfaceIds`. Support for {IERC165} itself is queried automatically. + * + * Batch-querying can lead to gas savings by skipping repeated checks for + * {IERC165} support. + * + * See {IERC165-supportsInterface}. + */ + function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { + // query support of ERC165 itself + if (!supportsERC165(account)) { + return false; + } + + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { + return false; + } + } + + // all interfaces supported + return true; + } + + /** + * @notice Query if a contract implements an interface, does not check ERC165 support + * @param account The address of the contract to query for support of an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @return true if the contract at account indicates support of the interface with + * identifier interfaceId, false otherwise + * @dev Assumes that account contains a contract that supports ERC165, otherwise + * the behavior of this method is undefined. This precondition can be checked + * with {supportsERC165}. + * Interface identification is specified in ERC-165. + */ + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { + // prepare call + bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); + + // perform static call + bool success; + uint256 returnSize; + uint256 returnValue; + assembly { + success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) + returnSize := returndatasize() + returnValue := mload(0x00) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } +} + +// lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ``` + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 private _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a + * constructor. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) { + _initializing = true; + } + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: setting the version to 255 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint8 version) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized != type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } + + /** + * @dev Internal function that returns the initialized version. Returns `_initialized` + */ + function _getInitializedVersion() internal view returns (uint8) { + return _initialized; + } + + /** + * @dev Internal function that returns the initialized version. Returns `_initializing` + */ + function _isInitializing() internal view returns (bool) { + return _initializing; + } +} + +// lib/royalty-registry-solidity/contracts/IRoyaltyEngineV1.sol + +/// @author: manifold.xyz + +/** + * @dev Lookup engine interface + */ +interface IRoyaltyEngineV1 is IERC165 { + /** + * Get the royalty for a given token (address, id) and value amount. Does not cache the bps/amounts. Caches the spec for a given token address + * + * @param tokenAddress - The address of the token + * @param tokenId - The id of the token + * @param value - The value you wish to get the royalty of + * + * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get + */ + function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value) + external + returns (address payable[] memory recipients, uint256[] memory amounts); + + /** + * View only version of getRoyalty + * + * @param tokenAddress - The address of the token + * @param tokenId - The id of the token + * @param value - The value you wish to get the royalty of + * + * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get + */ + function getRoyaltyView(address tokenAddress, uint256 tokenId, uint256 value) + external + view + returns (address payable[] memory recipients, uint256[] memory amounts); +} + +// lib/solmate/src/utils/SafeTransferLib.sol + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. +/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. +library SafeTransferLib { + /*////////////////////////////////////////////////////////////// + ETH OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferETH(address to, uint256 amount) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Transfer the ETH and store if it succeeded or not. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + + require(success, "ETH_TRANSFER_FAILED"); + } + + /*////////////////////////////////////////////////////////////// + ERC20 OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferFrom( + ERC20 token, + address from, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. + mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) + ) + } + + require(success, "TRANSFER_FROM_FAILED"); + } + + function safeTransfer( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "TRANSFER_FAILED"); + } + + function safeApprove( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "APPROVE_FAILED"); + } +} + +// src/bonding-curves/ICurve.sol + +interface ICurve { + /** + * @notice Validates if a delta value is valid for the curve. The criteria for + * validity can be different for each type of curve, for instance ExponentialCurve + * requires delta to be greater than 1. + * @param delta The delta value to be validated + * @return valid True if delta is valid, false otherwise + */ + function validateDelta(uint128 delta) external pure returns (bool valid); + + /** + * @notice Validates if a new spot price is valid for the curve. Spot price is generally assumed to be the immediate sell price of 1 NFT to the pool, in units of the pool's paired token. + * @param newSpotPrice The new spot price to be set + * @return valid True if the new spot price is valid, false otherwise + */ + function validateSpotPrice(uint128 newSpotPrice) external view returns (bool valid); + + /** + * @notice Given the current state of the pair and the trade, computes how much the user + * should pay to purchase an NFT from the pair, the new spot price, and other values. + * @param spotPrice The current selling spot price of the pair, in tokens + * @param delta The delta parameter of the pair, what it means depends on the curve + * @param numItems The number of NFTs the user is buying from the pair + * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals + * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals + * @return error Any math calculation errors, only Error.OK means the returned values are valid + * @return newSpotPrice The updated selling spot price, in tokens + * @return newDelta The updated delta, used to parameterize the bonding curve + * @return inputValue The amount that the user should pay, in tokens + * @return tradeFee The amount that is sent to the trade fee recipient + * @return protocolFee The amount of fee to send to the protocol, in tokens + */ + function getBuyInfo( + uint128 spotPrice, + uint128 delta, + uint256 numItems, + uint256 feeMultiplier, + uint256 protocolFeeMultiplier + ) + external + view + returns ( + CurveErrorCodes.Error error, + uint128 newSpotPrice, + uint128 newDelta, + uint256 inputValue, + uint256 tradeFee, + uint256 protocolFee + ); + + /** + * @notice Given the current state of the pair and the trade, computes how much the user + * should receive when selling NFTs to the pair, the new spot price, and other values. + * @param spotPrice The current selling spot price of the pair, in tokens + * @param delta The delta parameter of the pair, what it means depends on the curve + * @param numItems The number of NFTs the user is selling to the pair + * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals + * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals + * @return error Any math calculation errors, only Error.OK means the returned values are valid + * @return newSpotPrice The updated selling spot price, in tokens + * @return newDelta The updated delta, used to parameterize the bonding curve + * @return outputValue The amount that the user should receive, in tokens + * @return tradeFee The amount that is sent to the trade fee recipient + * @return protocolFee The amount of fee to send to the protocol, in tokens + */ + function getSellInfo( + uint128 spotPrice, + uint128 delta, + uint256 numItems, + uint256 feeMultiplier, + uint256 protocolFeeMultiplier + ) + external + view + returns ( + CurveErrorCodes.Error error, + uint128 newSpotPrice, + uint128 newDelta, + uint256 outputValue, + uint256 tradeFee, + uint256 protocolFee + ); +} + +// lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract ContextUpgradeable is Initializable { + function __Context_init() internal onlyInitializing { + } + + function __Context_init_unchained() internal onlyInitializing { + } + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[50] private __gap; +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Receiver.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol) + +/** + * @dev _Available since v3.1._ + */ +abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } +} + +// lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init() internal onlyInitializing { + __Ownable_init_unchained(); + } + + function __Ownable_init_unchained() internal onlyInitializing { + _transferOwnership(_msgSender()); + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[49] private __gap; +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol) + +/** + * Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. + * + * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be + * stuck. + * + * @dev _Available since v3.1._ + */ +contract ERC1155Holder is ERC1155Receiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} + +// src/lib/OwnableWithTransferCallback.sol + +abstract contract OwnableWithTransferCallback { + using ERC165Checker for address; + using Address for address; + + bytes4 constant TRANSFER_CALLBACK = type(IOwnershipTransferReceiver).interfaceId; + + error Ownable_NotOwner(); + error Ownable_NewOwnerZeroAddress(); + + address private _owner; + + event OwnershipTransferred(address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init(address initialOwner) internal { + _owner = initialOwner; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + if (owner() != msg.sender) revert Ownable_NotOwner(); + _; + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * @param newOwner The new address to become owner + * @param data Any additional data to send to the ownership received callback. + * Disallows setting to the zero address as a way to more gas-efficiently avoid reinitialization. + * When ownership is transferred, if the new owner implements IOwnershipTransferCallback, we make a callback. + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner, bytes calldata data) public payable virtual onlyOwner { + if (newOwner == address(0)) revert Ownable_NewOwnerZeroAddress(); + _transferOwnership(newOwner); + + if (newOwner.isContract()) { + try IOwnershipTransferReceiver(newOwner).onOwnershipTransferred{value: msg.value}(msg.sender, data) {} + // If revert... + catch (bytes memory reason) { + // If we just transferred to a contract w/ no callback, this is fine + if (reason.length == 0) { + // i.e., no need to revert + } + // Otherwise, the callback had an error, and we should revert + else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + /** + * @notice Transfers ownership of the contract to a new account (`newOwner`). + * @dev Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + _owner = newOwner; + emit OwnershipTransferred(newOwner); + } +} + +// src/ILSSVMPairFactoryLike.sol + +interface ILSSVMPairFactoryLike { + struct Settings { + uint96 bps; + address pairAddress; + } + + enum PairNFTType { + ERC721, + ERC1155 + } + + enum PairTokenType { + ETH, + ERC20 + } + + enum PairVariant { + ERC721_ETH, + ERC721_ERC20, + ERC1155_ETH, + ERC1155_ERC20 + } + + function protocolFeeMultiplier() external view returns (uint256); + + function protocolFeeRecipient() external view returns (address payable); + + function callAllowed(address target) external view returns (bool); + + function authAllowedForToken(address tokenAddress, address proposedAuthAddress) external view returns (bool); + + function getSettingsForPair(address pairAddress) external view returns (bool settingsEnabled, uint96 bps); + + function enableSettingsForPair(address settings, address pairAddress) external; + + function disableSettingsForPair(address settings, address pairAddress) external; + + function routerStatus(LSSVMRouter router) external view returns (bool allowed, bool wasEverTouched); + + function isValidPair(address pairAddress) external view returns (bool); + + function getPairNFTType(address pairAddress) external pure returns (PairNFTType); + + function getPairTokenType(address pairAddress) external pure returns (PairTokenType); + + function openLock() external; + + function closeLock() external; +} + +// src/LSSVMPair.sol + +/** + * @title The base contract for an NFT/TOKEN AMM pair + * @author boredGenius, 0xmons, 0xCygaar + * @notice This implements the core swap logic from NFT to TOKEN + */ +abstract contract LSSVMPair is OwnableWithTransferCallback, ERC721Holder, ERC1155Holder { + /** + * Library usage ** + */ + + using Address for address; + + /** + * Enums ** + */ + + enum PoolType { + TOKEN, + NFT, + TRADE + } + + /** + * Constants ** + */ + + /** + * @dev 50%, must <= 1 - MAX_PROTOCOL_FEE (set in LSSVMPairFactory) + */ + uint256 internal constant MAX_TRADE_FEE = 0.5e18; + + /** + * Immutable params ** + */ + + /** + * @notice Sudoswap Royalty Engine + */ + IRoyaltyEngineV1 public immutable ROYALTY_ENGINE; + + /** + * Storage variables ** + */ + + /** + * @dev This is generally used to mean the immediate sell price for the next marginal NFT. + * However, this should NOT be assumed, as bonding curves may use spotPrice in different ways. + * Use getBuyNFTQuote and getSellNFTQuote for accurate pricing info. + */ + uint128 public spotPrice; + + /** + * @notice The parameter for the pair's bonding curve. + * Units and meaning are bonding curve dependent. + */ + uint128 public delta; + + /** + * @notice The spread between buy and sell prices, set to be a multiplier we apply to the buy price + * Fee is only relevant for TRADE pools. Units are in base 1e18. + */ + uint96 public fee; + + /** + * @notice The address that swapped assets are sent to. + * For TRADE pools, assets are always sent to the pool, so this is used to track trade fee. + * If set to address(0), will default to owner() for NFT and TOKEN pools. + */ + address payable internal assetRecipient; + + /** + * Events + */ + + event SwapNFTInPair(uint256 amountOut, uint256[] ids); + event SwapNFTInPair(uint256 amountOut, uint256 numNFTs); + event SwapNFTOutPair(uint256 amountIn, uint256[] ids); + event SwapNFTOutPair(uint256 amountIn, uint256 numNFTs); + event SpotPriceUpdate(uint128 newSpotPrice); + event TokenDeposit(uint256 amount); + event TokenWithdrawal(uint256 amount); + event NFTWithdrawal(uint256[] ids); + event NFTWithdrawal(uint256 numNFTs); + event DeltaUpdate(uint128 newDelta); + event FeeUpdate(uint96 newFee); + event AssetRecipientChange(address indexed a); + + /** + * Errors + */ + + error LSSVMPair__NotRouter(); + error LSSVMPair__CallFailed(); + error LSSVMPair__InvalidDelta(); + error LSSVMPair__WrongPoolType(); + error LSSVMPair__OutputTooSmall(); + error LSSVMPair__ZeroSwapAmount(); + error LSSVMPair__RoyaltyTooLarge(); + error LSSVMPair__TradeFeeTooLarge(); + error LSSVMPair__InvalidSpotPrice(); + error LSSVMPair__TargetNotAllowed(); + error LSSVMPair__NftNotTransferred(); + error LSSVMPair__AlreadyInitialized(); + error LSSVMPair__FunctionNotAllowed(); + error LSSVMPair__DemandedInputTooLarge(); + error LSSVMPair__NonTradePoolWithTradeFee(); + error LSSVMPair__BondingCurveError(CurveErrorCodes.Error error); + + constructor(IRoyaltyEngineV1 royaltyEngine) { + ROYALTY_ENGINE = royaltyEngine; + } + + /** + * @notice Called during pair creation to set initial parameters + * @dev Only called once by factory to initialize. + * We verify this by making sure that the current owner is address(0). + * The Ownable library we use disallows setting the owner to be address(0), so this condition + * should only be valid before the first initialize call. + * @param _owner The owner of the pair + * @param _assetRecipient The address that will receive the TOKEN or NFT sent to this pair during swaps. NOTE: If set to address(0), they will go to the pair itself. + * @param _delta The initial delta of the bonding curve + * @param _fee The initial % fee taken, if this is a trade pair + * @param _spotPrice The initial price to sell an asset into the pair + */ + function initialize( + address _owner, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice + ) external { + if (owner() != address(0)) revert LSSVMPair__AlreadyInitialized(); + __Ownable_init(_owner); + + ICurve _bondingCurve = bondingCurve(); + PoolType _poolType = poolType(); + if (_poolType != PoolType.TRADE) { + if (_fee != 0) revert LSSVMPair__NonTradePoolWithTradeFee(); + } else { + if (_fee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); + fee = _fee; + } + + assetRecipient = _assetRecipient; + + if (!_bondingCurve.validateDelta(_delta)) revert LSSVMPair__InvalidDelta(); + if (!_bondingCurve.validateSpotPrice(_spotPrice)) revert LSSVMPair__InvalidSpotPrice(); + delta = _delta; + spotPrice = _spotPrice; + } + + /** + * External state-changing functions + */ + + /** + * @notice Sends token to the pair in exchange for a specific set of NFTs + * @dev To compute the amount of token to send, call bondingCurve.getBuyInfo + * This swap is meant for users who want specific IDs. Also higher chance of + * reverting if some of the specified IDs leave the pool before the swap goes through. + * @param nftIds The list of IDs of the NFTs to purchase + * @param maxExpectedTokenInput The maximum acceptable cost from the sender. If the actual + * amount is greater than this value, the transaction will be reverted. + * @param nftRecipient The recipient of the NFTs + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. + * @return - The amount of token used for purchase + */ + function swapTokenForSpecificNFTs( + uint256[] calldata nftIds, + uint256 maxExpectedTokenInput, + address nftRecipient, + bool isRouter, + address routerCaller + ) external payable virtual returns (uint256); + + /** + * @notice Sends a set of NFTs to the pair in exchange for token + * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. + * @param nftIds The list of IDs of the NFTs to sell to the pair + * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual + * amount is less than this value, the transaction will be reverted. + * @param tokenRecipient The recipient of the token output + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for + * ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for + * ETH pairs. + * @return outputAmount The amount of token received + */ + function swapNFTsForToken( + uint256[] calldata nftIds, + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller + ) external virtual returns (uint256 outputAmount); + + /** + * View functions + */ + + /** + * @dev Used as read function to query the bonding curve for buy pricing info + * @param numNFTs The number of NFTs to buy from the pair + */ + function getBuyNFTQuote(uint256 assetId, uint256 numNFTs) + external + view + returns ( + CurveErrorCodes.Error error, + uint256 newSpotPrice, + uint256 newDelta, + uint256 inputAmount, + uint256 protocolFee, + uint256 royaltyAmount + ) + { + uint256 tradeFee; + (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = + bondingCurve().getBuyInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); + + if (numNFTs != 0) { + // Calculate the inputAmount minus tradeFee and protocolFee + uint256 inputAmountMinusFees = inputAmount - tradeFee - protocolFee; + + // Compute royalties + (,, royaltyAmount) = calculateRoyaltiesView(assetId, inputAmountMinusFees); + + inputAmount += royaltyAmount; + } + } + + /** + * @dev Used as read function to query the bonding curve for sell pricing info including royalties + * @param numNFTs The number of NFTs to sell to the pair + */ + function getSellNFTQuote(uint256 assetId, uint256 numNFTs) + external + view + returns ( + CurveErrorCodes.Error error, + uint256 newSpotPrice, + uint256 newDelta, + uint256 outputAmount, + uint256 protocolFee, + uint256 royaltyAmount + ) + { + (error, newSpotPrice, newDelta, outputAmount, /* tradeFee */, protocolFee) = + bondingCurve().getSellInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); + + if (numNFTs != 0) { + // Compute royalties + (,, royaltyAmount) = calculateRoyaltiesView(assetId, outputAmount); + + // Deduct royalties from outputAmount + unchecked { + // Safe because we already require outputAmount >= royaltyAmount in _calculateRoyalties() + outputAmount -= royaltyAmount; + } + } + } + + /** + * @notice Returns the pair's variant (Pair uses ETH or ERC20) + */ + function pairVariant() public pure virtual returns (ILSSVMPairFactoryLike.PairVariant); + + function factory() public pure returns (ILSSVMPairFactoryLike _factory) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _factory := shr(0x60, calldataload(sub(calldatasize(), paramsLength))) + } + } + + /** + * @notice Returns the type of bonding curve that parameterizes the pair + */ + function bondingCurve() public pure returns (ICurve _bondingCurve) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _bondingCurve := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 20))) + } + } + + /** + * @notice Returns the address of NFT collection that parameterizes the pair + */ + function nft() public pure returns (address _nft) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _nft := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 40))) + } + } + + /** + * @notice Returns the pair's type (TOKEN/NFT/TRADE) + */ + function poolType() public pure returns (PoolType _poolType) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _poolType := shr(0xf8, calldataload(add(sub(calldatasize(), paramsLength), 60))) + } + } + + /** + * @notice Returns the address that receives assets when a swap is done with this pair + * Can be set to another address by the owner, but has no effect on TRADE pools + * If set to address(0), defaults to owner() for NFT/TOKEN pools + */ + function getAssetRecipient() public view returns (address payable) { + // TRADE pools will always receive the asset themselves + if (poolType() == PoolType.TRADE) { + return payable(address(this)); + } + + address payable _assetRecipient = assetRecipient; + + // Otherwise, we return the recipient if it's been set + // Or, we replace it with owner() if it's address(0) + if (_assetRecipient == address(0)) { + return payable(owner()); + } + return _assetRecipient; + } + + /** + * @notice Returns the address that receives trade fees when a swap is done with this pair + * Only relevant for TRADE pools + * If set to address(0), defaults to the pair itself + */ + function getFeeRecipient() public view returns (address payable _feeRecipient) { + _feeRecipient = assetRecipient; + if (_feeRecipient == address(0)) { + _feeRecipient = payable(address(this)); + } + } + + /** + * Internal functions + */ + + /** + * @notice Calculates the amount needed to be sent into the pair for a buy and adjusts spot price or delta if necessary + * @param numNFTs The amount of NFTs to purchase from the pair + * @param _bondingCurve The bonding curve to use for price calculation + * @param _factory The factory to use for protocol fee lookup + * @return tradeFee The amount of tokens to send as trade fee + * @return protocolFee The amount of tokens to send as protocol fee + * @return inputAmount The amount of tokens total tokens receive + */ + function _calculateBuyInfoAndUpdatePoolParams(uint256 numNFTs, ICurve _bondingCurve, ILSSVMPairFactoryLike _factory) + internal + returns (uint256 tradeFee, uint256 protocolFee, uint256 inputAmount) + { + CurveErrorCodes.Error error; + // Save on 2 SLOADs by caching + uint128 currentSpotPrice = spotPrice; + uint128 currentDelta = delta; + uint128 newDelta; + uint128 newSpotPrice; + (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = + _bondingCurve.getBuyInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); + + // Revert if bonding curve had an error + if (error != CurveErrorCodes.Error.OK) { + revert LSSVMPair__BondingCurveError(error); + } + + // Consolidate writes to save gas + if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { + spotPrice = newSpotPrice; + delta = newDelta; + } + + // Emit spot price update if it has been updated + if (currentSpotPrice != newSpotPrice) { + emit SpotPriceUpdate(newSpotPrice); + } + + // Emit delta update if it has been updated + if (currentDelta != newDelta) { + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Calculates the amount needed to be sent by the pair for a sell and adjusts spot price or delta if necessary + * @param numNFTs The amount of NFTs to send to the the pair + * @param _bondingCurve The bonding curve to use for price calculation + * @param _factory The factory to use for protocol fee lookup + * @return protocolFee The amount of tokens to send as protocol fee + * @return outputAmount The amount of tokens total tokens receive + */ + function _calculateSellInfoAndUpdatePoolParams( + uint256 numNFTs, + ICurve _bondingCurve, + ILSSVMPairFactoryLike _factory + ) internal returns (uint256 protocolFee, uint256 outputAmount) { + CurveErrorCodes.Error error; + // Save on 2 SLOADs by caching + uint128 currentSpotPrice = spotPrice; + uint128 currentDelta = delta; + uint128 newSpotPrice; + uint128 newDelta; + (error, newSpotPrice, newDelta, outputAmount, /*tradeFee*/, protocolFee) = + _bondingCurve.getSellInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); + + // Revert if bonding curve had an error + if (error != CurveErrorCodes.Error.OK) { + revert LSSVMPair__BondingCurveError(error); + } + + // Consolidate writes to save gas + if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { + spotPrice = newSpotPrice; + delta = newDelta; + } + + // Emit spot price update if it has been updated + if (currentSpotPrice != newSpotPrice) { + emit SpotPriceUpdate(newSpotPrice); + } + + // Emit delta update if it has been updated + if (currentDelta != newDelta) { + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Pulls the token input of a trade from the trader (including all royalties and fees) + * @param inputAmountExcludingRoyalty The amount of tokens to be sent, excluding the royalty (includes protocol fee) + * @param royaltyAmounts The amounts of tokens to be sent as royalties + * @param royaltyRecipients The recipients of the royalties + * @param royaltyTotal The sum of all royaltyAmounts + * @param tradeFeeAmount The amount of tokens to be sent as trade fee (if applicable) + * @param isRouter Whether or not the caller is LSSVMRouter + * @param routerCaller If called from LSSVMRouter, store the original caller + * @param protocolFee The protocol fee to be paid + */ + function _pullTokenInputs( + uint256 inputAmountExcludingRoyalty, + uint256[] memory royaltyAmounts, + address payable[] memory royaltyRecipients, + uint256 royaltyTotal, + uint256 tradeFeeAmount, + bool isRouter, + address routerCaller, + uint256 protocolFee + ) internal virtual; + + /** + * @notice Sends excess tokens back to the caller (if applicable) + * @dev Swap callers interacting with an ETH pair must be able to receive ETH (e.g. if the caller sends too much ETH) + */ + function _refundTokenToSender(uint256 inputAmount) internal virtual; + + /** + * @notice Sends tokens to a recipient + * @param tokenRecipient The address receiving the tokens + * @param outputAmount The amount of tokens to send + */ + function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal virtual; + + /** + * @dev Used internally to grab pair parameters from calldata, see LSSVMPairCloner for technical details + */ + function _immutableParamsLength() internal pure virtual returns (uint256); + + /** + * Royalty support functions + */ + + function _calculateRoyalties(uint256 assetId, uint256 saleAmount) + internal + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + (address payable[] memory recipients, uint256[] memory amounts) = + ROYALTY_ENGINE.getRoyalty(nft(), assetId, saleAmount); + return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); + } + + /** + * @dev Same as _calculateRoyalties, but uses getRoyaltyView to avoid state mutations and is public for external callers + */ + function calculateRoyaltiesView(uint256 assetId, uint256 saleAmount) + public + view + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + (address payable[] memory recipients, uint256[] memory amounts) = + ROYALTY_ENGINE.getRoyaltyView(nft(), assetId, saleAmount); + return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); + } + + /** + * @dev Common logic used by _calculateRoyalties() and calculateRoyaltiesView() + */ + function _calculateRoyaltiesLogic(address payable[] memory recipients, uint256[] memory amounts, uint256 saleAmount) + internal + view + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + // Cache to save gas + uint256 numRecipients = recipients.length; + + if (numRecipients != 0) { + // If a pair has custom Settings, use the overridden royalty amount and only use the first receiver + try factory().getSettingsForPair(address(this)) returns (bool settingsEnabled, uint96 bps) { + if (settingsEnabled) { + royaltyRecipients = new address payable[](1); + royaltyRecipients[0] = recipients[0]; + royaltyAmounts = new uint256[](1); + royaltyAmounts[0] = (saleAmount * bps) / 10000; + + // Update numRecipients to match new recipients list + numRecipients = 1; + } else { + royaltyRecipients = recipients; + royaltyAmounts = amounts; + } + } catch { + // Use the input values to calculate royalties if factory call fails + royaltyRecipients = recipients; + royaltyAmounts = amounts; + } + } + + for (uint256 i; i < numRecipients;) { + royaltyTotal += royaltyAmounts[i]; + unchecked { + ++i; + } + } + + // Ensure royalty total is at most 25% of the sale amount + // This defends against a rogue Manifold registry that charges extremely high royalties + if (royaltyTotal > saleAmount >> 2) { + revert LSSVMPair__RoyaltyTooLarge(); + } + } + + /** + * Owner functions + */ + + /** + * @notice Rescues a specified set of NFTs owned by the pair to the owner address. (onlyOwnable modifier is in the implemented function) + * @param a The NFT to transfer + * @param nftIds The list of IDs of the NFTs to send to the owner + */ + function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual; + + /** + * @notice Rescues ERC20 tokens from the pair to the owner. Only callable by the owner (onlyOwnable modifier is in the implemented function). + * @param a The token to transfer + * @param amount The amount of tokens to send to the owner + */ + function withdrawERC20(ERC20 a, uint256 amount) external virtual; + + /** + * @notice Rescues ERC1155 tokens from the pair to the owner. Only callable by the owner. + * @param a The NFT to transfer + * @param ids The NFT ids to transfer + * @param amounts The amounts of each id to transfer + */ + function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) external virtual; + + /** + * @notice Updates the selling spot price. Only callable by the owner. + * @param newSpotPrice The new selling spot price value, in Token + */ + function changeSpotPrice(uint128 newSpotPrice) external onlyOwner { + ICurve _bondingCurve = bondingCurve(); + if (!_bondingCurve.validateSpotPrice(newSpotPrice)) revert LSSVMPair__InvalidSpotPrice(); + if (spotPrice != newSpotPrice) { + spotPrice = newSpotPrice; + emit SpotPriceUpdate(newSpotPrice); + } + } + + /** + * @notice Updates the delta parameter. Only callable by the owner. + * @param newDelta The new delta parameter + */ + function changeDelta(uint128 newDelta) external onlyOwner { + ICurve _bondingCurve = bondingCurve(); + if (!_bondingCurve.validateDelta(newDelta)) revert LSSVMPair__InvalidDelta(); + if (delta != newDelta) { + delta = newDelta; + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Updates the fee taken by the LP. Only callable by the owner. + * Only callable if the pool is a Trade pool. Reverts if the fee is >= MAX_FEE. + * @param newFee The new LP fee percentage, 18 decimals + */ + function changeFee(uint96 newFee) external onlyOwner { + PoolType _poolType = poolType(); + if (_poolType != PoolType.TRADE) revert LSSVMPair__NonTradePoolWithTradeFee(); + if (newFee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); + if (fee != newFee) { + fee = newFee; + emit FeeUpdate(newFee); + } + } + + /** + * @notice Changes the address that will receive assets received from + * trades. Only callable by the owner. + * @param newRecipient The new asset recipient + */ + function changeAssetRecipient(address payable newRecipient) external onlyOwner { + if (assetRecipient != newRecipient) { + assetRecipient = newRecipient; + emit AssetRecipientChange(newRecipient); + } + } + + function _preCallCheck(address target) internal virtual; + + /** + * @notice Allows the pair to make arbitrary external calls to contracts + * whitelisted by the protocol. Only callable by the owner. + * @param target The contract to call + * @param data The calldata to pass to the contract + */ + function call(address payable target, bytes calldata data) external onlyOwner { + ILSSVMPairFactoryLike _factory = factory(); + if (!_factory.callAllowed(target)) revert LSSVMPair__TargetNotAllowed(); + + // Ensure the call isn't calling a banned function + bytes4 sig = bytes4(data[:4]); + if ( + sig == IOwnershipTransferReceiver.onOwnershipTransferred.selector + || sig == LSSVMRouter.pairTransferERC20From.selector || sig == LSSVMRouter.pairTransferNFTFrom.selector + || sig == LSSVMRouter.pairTransferERC1155From.selector || sig == ILSSVMPairFactoryLike.openLock.selector + || sig == ILSSVMPairFactoryLike.closeLock.selector + ) { + revert LSSVMPair__FunctionNotAllowed(); + } + + // Prevent calling the pair's underlying nft + // (We ban calling the underlying NFT/ERC20 to avoid maliciously transferring assets approved for the pair to spend) + if (target == nft()) revert LSSVMPair__TargetNotAllowed(); + + _preCallCheck(target); + + (bool success,) = target.call{value: 0}(data); + if (!success) revert LSSVMPair__CallFailed(); + } + + /** + * @notice Allows owner to batch multiple calls, forked from: https://github.com/boringcrypto/BoringSolidity/blob/master/contracts/BoringBatchable.sol + * @notice The revert handling is forked from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c239e1af8d1a1296577108dd6989a17b57434f8e/contracts/utils/Address.sol#L201 + * @dev Intended for withdrawing/altering pool pricing in one tx, only callable by owner, cannot change owner + * @param calls The calldata for each call to make + * @param revertOnFail Whether or not to revert the entire tx if any of the calls fail. Calls to transferOwnership will revert regardless. + */ + function multicall(bytes[] calldata calls, bool revertOnFail) external onlyOwner { + for (uint256 i; i < calls.length;) { + bytes4 sig = bytes4(calls[i][:4]); + // We ban calling transferOwnership when ownership + if (sig == transferOwnership.selector) revert LSSVMPair__FunctionNotAllowed(); + + (bool success, bytes memory result) = address(this).delegatecall(calls[i]); + if (!success && revertOnFail) { + assembly { + revert(add(0x20, result), mload(result)) + } + } + + unchecked { + ++i; + } + } + } +} + +// src/LSSVMRouter.sol + +contract LSSVMRouter { + using SafeTransferLib for address payable; + using SafeTransferLib for ERC20; + + struct PairSwapSpecific { + LSSVMPair pair; + uint256[] nftIds; + } + + struct RobustPairSwapSpecific { + PairSwapSpecific swapInfo; + uint256 maxCost; + } + + struct RobustPairSwapSpecificForToken { + PairSwapSpecific swapInfo; + uint256 minOutput; + } + + struct NFTsForSpecificNFTsTrade { + PairSwapSpecific[] nftToTokenTrades; + PairSwapSpecific[] tokenToNFTTrades; + } + + struct RobustPairNFTsFoTokenAndTokenforNFTsTrade { + RobustPairSwapSpecific[] tokenToNFTTrades; + RobustPairSwapSpecificForToken[] nftToTokenTrades; + uint256 inputAmount; + address payable tokenRecipient; + address nftRecipient; + } + + modifier checkDeadline(uint256 deadline) { + _checkDeadline(deadline); + _; + } + + ILSSVMPairFactoryLike public immutable factory; + + constructor(ILSSVMPairFactoryLike _factory) { + factory = _factory; + } + + /** + * ETH swaps + */ + + /** + * @notice Swaps ETH into specific NFTs using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param ethRecipient The address that will receive the unspent ETH input + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent ETH amount + */ + function swapETHForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) external payable checkDeadline(deadline) returns (uint256 remainingValue) { + return _swapETHForSpecificNFTs(swapList, msg.value, ethRecipient, nftRecipient); + } + + /** + * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using + * ETH as the intermediary. + * @param trade The struct containing all NFT-to-ETH swaps and ETH-to-NFT swaps. + * @param minOutput The minimum acceptable total excess ETH received + * @param ethRecipient The address that will receive the ETH output + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ETH received + */ + function swapNFTsForSpecificNFTsThroughETH( + NFTsForSpecificNFTsTrade calldata trade, + uint256 minOutput, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) external payable checkDeadline(deadline) returns (uint256 outputAmount) { + // Swap NFTs for ETH + // minOutput of swap set to 0 since we're doing an aggregate slippage check + outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(address(this))); + + // Add extra value to buy NFTs + outputAmount += msg.value; + + // Swap ETH for specific NFTs + // cost <= inputValue = outputAmount - minOutput, so outputAmount' = (outputAmount - minOutput - cost) + minOutput >= minOutput + outputAmount = _swapETHForSpecificNFTs( + trade.tokenToNFTTrades, outputAmount - minOutput, ethRecipient, nftRecipient + ) + minOutput; + } + + /** + * ERC20 swaps + * + * Note: All ERC20 swaps assume that a single ERC20 token is used for all the pairs involved. + * Swapping using multiple tokens in the same transaction is possible, but the slippage checks + * & the return values will be meaningless, and may lead to undefined behavior. + * + * Note: The sender should ideally grant infinite token approval to the router in order for NFT-to-NFT + * swaps to work smoothly. + */ + + /** + * @notice Swaps ERC20 tokens into specific NFTs using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function swapERC20ForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address nftRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 remainingValue) { + return _swapERC20ForSpecificNFTs(swapList, inputAmount, nftRecipient); + } + + /** + * @notice Swaps NFTs into ETH/ERC20 using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. + * @param minOutput The minimum acceptable total tokens received + * @param tokenRecipient The address that will receive the token output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total tokens received + */ + function swapNFTsForToken( + PairSwapSpecific[] calldata swapList, + uint256 minOutput, + address tokenRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 outputAmount) { + return _swapNFTsForToken(swapList, minOutput, payable(tokenRecipient)); + } + + /** + * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using + * an ERC20 token as the intermediary. + * @param trade The struct containing all NFT-to-ERC20 swaps and ERC20-to-NFT swaps. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param minOutput The minimum acceptable total excess tokens received + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ERC20 tokens received + */ + function swapNFTsForSpecificNFTsThroughERC20( + NFTsForSpecificNFTsTrade calldata trade, + uint256 inputAmount, + uint256 minOutput, + address nftRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 outputAmount) { + // Swap NFTs for ERC20 + // minOutput of swap set to 0 since we're doing an aggregate slippage check + // output tokens are sent to msg.sender + outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(msg.sender)); + + // Add extra value to buy NFTs + outputAmount += inputAmount; + + // Swap ERC20 for specific NFTs + // cost <= maxCost = outputAmount - minOutput, so outputAmount' = outputAmount - cost >= minOutput + // input tokens are taken directly from msg.sender + outputAmount = + _swapERC20ForSpecificNFTs(trade.tokenToNFTTrades, outputAmount - minOutput, nftRecipient) + minOutput; + } + + /** + * Robust Swaps + * These are "robust" versions of the NFT<>Token swap functions which will never revert due to slippage + * Instead, users specify a per-swap max cost. If the price changes more than the user specifies, no swap is attempted. This allows users to specify a batch of swaps, and execute as many of them as possible. + */ + + /** + * @dev Ensure msg.value >= sum of values in maxCostPerPair to make sure the transaction doesn't revert + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param ethRecipient The address that will receive the unspent ETH input + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function robustSwapETHForSpecificNFTs( + RobustPairSwapSpecific[] calldata swapList, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) public payable virtual checkDeadline(deadline) returns (uint256 remainingValue) { + remainingValue = msg.value; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( + swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { + // We know how much ETH to send because we already did the math above + // So we just send that much + remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( + swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + ethRecipient.safeTransferETH(remainingValue); + } + } + + /** + * @notice Swaps as many ERC20 tokens for specific NFTs as possible, respecting the per-swap max cost. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function robustSwapERC20ForSpecificNFTs( + RobustPairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address nftRecipient, + uint256 deadline + ) public virtual checkDeadline(deadline) returns (uint256 remainingValue) { + remainingValue = inputAmount; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( + swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { + remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs( + swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + + /** + * @notice Swaps as many NFTs for tokens as possible, respecting the per-swap min output + * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. + * @param tokenRecipient The address that will receive the token output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ETH/ERC20 received + */ + function robustSwapNFTsForToken( + RobustPairSwapSpecificForToken[] calldata swapList, + address payable tokenRecipient, + uint256 deadline + ) public virtual checkDeadline(deadline) returns (uint256 outputAmount) { + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256[] memory nftIds = swapList[i].swapInfo.nftIds; + if (nftIds.length == 0) { + unchecked { + ++i; + } + continue; + } + (error,,, pairOutput,,) = swapList[i].swapInfo.pair.getSellNFTQuote(nftIds[0], nftIds.length); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= swapList[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += swapList[i].swapInfo.pair.swapNFTsForToken( + swapList[i].swapInfo.nftIds, 0, tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + + /** + * @notice Buys NFTs with ETH and sells them for tokens in one transaction + * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: + * - ethToNFTSwapList The list of NFTs to buy + * - nftToTokenSwapList The list of NFTs to sell + * - inputAmount The max amount of tokens to send (if ERC20) + * - tokenRecipient The address that receives tokens from the NFTs sold + * - nftRecipient The address that receives NFTs + * - deadline UNIX timestamp deadline for the swap + */ + function robustSwapETHForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) + external + payable + virtual + returns (uint256 remainingValue, uint256 outputAmount) + { + { + remainingValue = msg.value; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = params.tokenToNFTTrades.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( + params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { + // We know how much ETH to send because we already did the math above + // So we just send that much + remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( + params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + params.tokenRecipient.safeTransferETH(remainingValue); + } + } + { + // Try doing each swap + uint256 numSwaps = params.nftToTokenTrades.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; + (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( + assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length + ); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= params.nftToTokenTrades[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( + params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + } + + /** + * @notice Buys NFTs with ERC20, and sells them for tokens in one transaction + * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: + * - ethToNFTSwapList The list of NFTs to buy + * - nftToTokenSwapList The list of NFTs to sell + * - inputAmount The max amount of tokens to send (if ERC20) + * - tokenRecipient The address that receives tokens from the NFTs sold + * - nftRecipient The address that receives NFTs + * - deadline UNIX timestamp deadline for the swap + */ + function robustSwapERC20ForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) + external + virtual + returns (uint256 remainingValue, uint256 outputAmount) + { + { + remainingValue = params.inputAmount; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = params.tokenToNFTTrades.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( + params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { + remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs( + params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + { + // Try doing each swap + uint256 numSwaps = params.nftToTokenTrades.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; + (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( + assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length + ); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= params.nftToTokenTrades[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( + params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + } + + receive() external payable {} + + /** + * Restricted functions + */ + + /** + * @dev Allows an ERC20 pair contract to transfer ERC20 tokens directly from + * the sender, in order to minimize the number of token transfers. Only callable by an ERC20 pair. + * @param token The ERC20 token to transfer + * @param from The address to transfer tokens from + * @param to The address to transfer tokens to + * @param amount The amount of tokens to transfer + */ + function pairTransferERC20From(ERC20 token, address from, address to, uint256 amount) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + // verify caller is an ERC20 pair + require(factory.getPairTokenType(msg.sender) == ILSSVMPairFactoryLike.PairTokenType.ERC20, "Not ERC20 pair"); + + // transfer tokens to pair + token.safeTransferFrom(from, to, amount); + } + + /** + * @dev Allows a pair contract to transfer ERC721 NFTs directly from + * the sender, in order to minimize the number of token transfers. Only callable by a pair. + * @param nft The ERC721 NFT to transfer + * @param from The address to transfer tokens from + * @param to The address to transfer tokens to + * @param id The ID of the NFT to transfer + */ + function pairTransferNFTFrom(IERC721 nft, address from, address to, uint256 id) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + + // transfer NFTs to pair + nft.transferFrom(from, to, id); + } + + function pairTransferERC1155From( + IERC1155 nft, + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts + ) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + + nft.safeBatchTransferFrom(from, to, ids, amounts, bytes("")); + } + + /** + * Internal functions + */ + + /** + * @param deadline The last valid time for a swap + */ + function _checkDeadline(uint256 deadline) internal view { + require(block.timestamp <= deadline, "Deadline passed"); + } + + /** + * @notice Internal function used to swap ETH for a specific set of NFTs + * @param swapList The list of pairs and swap calldata + * @param inputAmount The total amount of ETH to send + * @param ethRecipient The address receiving excess ETH + * @param nftRecipient The address receiving the NFTs from the pairs + * @return remainingValue The unspent token amount + */ + function _swapETHForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address payable ethRecipient, + address nftRecipient + ) internal virtual returns (uint256 remainingValue) { + remainingValue = inputAmount; + + uint256 pairCost; + CurveErrorCodes.Error error; + + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate the cost per swap first to send exact amount of ETH over, saves gas by avoiding the need to send back excess ETH + (error,,, pairCost,,) = swapList[i].pair.getBuyNFTQuote(swapList[i].nftIds[0], swapList[i].nftIds.length); + + // Require no errors + require(error == CurveErrorCodes.Error.OK, "Bonding curve error"); + + // Total ETH taken from sender cannot exceed inputAmount + // because otherwise the deduction from remainingValue will fail + remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs{value: pairCost}( + swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender + ); + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + ethRecipient.safeTransferETH(remainingValue); + } + } + + /** + * @notice Internal function used to swap an ERC20 token for specific NFTs + * @dev Note that we don't need to query the pair's bonding curve first for pricing data because + * we just calculate and take the required amount from the caller during swap time. + * However, we can't "pull" ETH, which is why for the ETH->NFT swaps, we need to calculate the pricing info + * to figure out how much the router should send to the pool. + * @param swapList The list of pairs and swap calldata + * @param inputAmount The total amount of ERC20 tokens to send + * @param nftRecipient The address receiving the NFTs from the pairs + * @return remainingValue The unspent token amount + */ + function _swapERC20ForSpecificNFTs(PairSwapSpecific[] calldata swapList, uint256 inputAmount, address nftRecipient) + internal + virtual + returns (uint256 remainingValue) + { + remainingValue = inputAmount; + + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Tokens are transferred in by the pair calling router.pairTransferERC20From + // Total tokens taken from sender cannot exceed inputAmount + // because otherwise the deduction from remainingValue will fail + remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs( + swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender + ); + + unchecked { + ++i; + } + } + } + + /** + * @notice Swaps NFTs for tokens, designed to be used for 1 token at a time + * @dev Calling with multiple tokens is permitted, BUT minOutput will be + * far from enough of a safety check because different tokens almost certainly have different unit prices. + * @param swapList The list of pairs and swap calldata + * @param minOutput The minimum number of tokens to be receieved from the swaps + * @param tokenRecipient The address that receives the tokens + * @return outputAmount The number of tokens to be received + */ + function _swapNFTsForToken(PairSwapSpecific[] calldata swapList, uint256 minOutput, address payable tokenRecipient) + internal + virtual + returns (uint256 outputAmount) + { + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Do the swap for token and then update outputAmount + // Note: minExpectedTokenOutput is set to 0 since we're doing an aggregate slippage check below + outputAmount += swapList[i].pair.swapNFTsForToken(swapList[i].nftIds, 0, tokenRecipient, true, msg.sender); + + unchecked { + ++i; + } + } + + // Aggregate slippage check + require(outputAmount >= minOutput, "outputAmount too low"); + } +} + +// src/LSSVMPairERC20.sol + +/** + * @title An NFT/Token pair where the token is an ERC20 + * @author boredGenius, 0xmons, 0xCygaar + */ +abstract contract LSSVMPairERC20 is LSSVMPair { + using SafeTransferLib for ERC20; + + error LSSVMPairERC20__RoyaltyNotPaid(); + error LSSVMPairERC20__MsgValueNotZero(); + error LSSVMPairERC20__AssetRecipientNotPaid(); + + /** + * @notice Returns the ERC20 token associated with the pair + * @dev See LSSVMPairCloner for an explanation on how this works + * @dev The last 20 bytes of the immutable data contain the ERC20 token address + */ + function token() public pure returns (ERC20 _token) { + assembly { + _token := shr(0x60, calldataload(sub(calldatasize(), 20))) + } + } + + /** + * @inheritdoc LSSVMPair + */ + function _pullTokenInputs( + uint256 inputAmountExcludingRoyalty, + uint256[] memory royaltyAmounts, + address payable[] memory royaltyRecipients, + uint256, /* royaltyTotal */ + uint256 tradeFeeAmount, + bool isRouter, + address routerCaller, + uint256 protocolFee + ) internal override { + address _assetRecipient = getAssetRecipient(); + + // Transfer tokens + if (isRouter) { + // Verify if router is allowed + // Locally scoped to avoid stack too deep + { + (bool routerAllowed,) = factory().routerStatus(LSSVMRouter(payable(msg.sender))); + if (!routerAllowed) revert LSSVMPair__NotRouter(); + } + + // Cache state and then call router to transfer tokens from user + uint256 beforeBalance = token().balanceOf(_assetRecipient); + LSSVMRouter(payable(msg.sender)).pairTransferERC20From( + token(), routerCaller, _assetRecipient, inputAmountExcludingRoyalty - protocolFee + ); + + // Verify token transfer (protect pair against malicious router) + ERC20 token_ = token(); + if (token_.balanceOf(_assetRecipient) - beforeBalance != (inputAmountExcludingRoyalty - protocolFee)) { + revert LSSVMPairERC20__AssetRecipientNotPaid(); + } + + // Transfer royalties (if they exist) + for (uint256 i; i < royaltyRecipients.length;) { + beforeBalance = token_.balanceOf(royaltyRecipients[i]); + LSSVMRouter(payable(msg.sender)).pairTransferERC20From( + token_, routerCaller, royaltyRecipients[i], royaltyAmounts[i] + ); + if (token_.balanceOf(royaltyRecipients[i]) - beforeBalance != royaltyAmounts[i]) { + revert LSSVMPairERC20__RoyaltyNotPaid(); + } + unchecked { + ++i; + } + } + + // Take protocol fee (if it exists) + if (protocolFee != 0) { + LSSVMRouter(payable(msg.sender)).pairTransferERC20From( + token_, routerCaller, address(factory()), protocolFee + ); + } + } else { + // Transfer tokens directly (sans the protocol fee) + ERC20 token_ = token(); + token_.safeTransferFrom(msg.sender, _assetRecipient, inputAmountExcludingRoyalty - protocolFee); + + // Transfer royalties (if they exists) + for (uint256 i; i < royaltyRecipients.length;) { + token_.safeTransferFrom(msg.sender, royaltyRecipients[i], royaltyAmounts[i]); + unchecked { + ++i; + } + } + + // Take protocol fee (if it exists) + if (protocolFee != 0) { + token_.safeTransferFrom(msg.sender, address(factory()), protocolFee); + } + } + // Send trade fee if it exists, is TRADE pool, and fee recipient != pool address + // @dev: (note that tokens are sent from the pool and not the caller) + if (poolType() == PoolType.TRADE && tradeFeeAmount != 0) { + address payable _feeRecipient = getFeeRecipient(); + if (_feeRecipient != _assetRecipient) { + token().safeTransfer(_feeRecipient, tradeFeeAmount); + } + } + } + + /** + * @inheritdoc LSSVMPair + */ + function _refundTokenToSender(uint256 inputAmount) internal override { + // Do nothing since we transferred the exact input amount + } + + /** + * @inheritdoc LSSVMPair + */ + function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal override { + // Send tokens to caller + if (outputAmount != 0) { + token().safeTransfer(tokenRecipient, outputAmount); + } + } + + /** + * @inheritdoc LSSVMPair + */ + function withdrawERC20(ERC20 a, uint256 amount) external override onlyOwner { + a.safeTransfer(msg.sender, amount); + + if (a == token()) { + // emit event since it is the pair token + emit TokenWithdrawal(amount); + } + } + + function _preCallCheck(address target) internal pure override { + if (target == address(token())) revert LSSVMPair__TargetNotAllowed(); + } +} + +// src/LSSVMPairETH.sol + +/** + * @title An NFT/Token pair where the token is ETH + * @author boredGenius, 0xmons, 0xCygaar + */ +abstract contract LSSVMPairETH is LSSVMPair { + using SafeTransferLib for address payable; + using SafeTransferLib for ERC20; + + error LSSVMPairETH__InsufficientInput(); + + /** + * @inheritdoc LSSVMPair + */ + function _pullTokenInputs( + uint256 inputAmountExcludingRoyalty, + uint256[] memory royaltyAmounts, + address payable[] memory royaltyRecipients, + uint256 royaltyTotal, + uint256 tradeFeeAmount, + bool, /*isRouter*/ + address, /*routerCaller*/ + uint256 protocolFee + ) internal override { + // Require that the input amount is sufficient to pay for the sale amount, royalties, and fees + if (msg.value < (royaltyTotal + inputAmountExcludingRoyalty)) revert LSSVMPairETH__InsufficientInput(); + + // Transfer inputAmountExcludingRoyalty ETH to assetRecipient if it has been set + address payable _assetRecipient = getAssetRecipient(); + + // Attempt to transfer trade fees only if TRADE pool and they exist + if (poolType() == PoolType.TRADE && tradeFeeAmount != 0) { + address payable _feeRecipient = getFeeRecipient(); + + // Only send and deduct tradeFeeAmount if the fee recipient is not the asset recipient (i.e. the pool) + if (_feeRecipient != _assetRecipient) { + inputAmountExcludingRoyalty -= tradeFeeAmount; + _feeRecipient.safeTransferETH(tradeFeeAmount); + } + // In the else case, we would want to ensure that inputAmountExcludingRoyalty >= tradeFeeAmount / 2 + // to avoid underpaying the trade fee, but it is always true because the max royalty + // is 25%, the max protocol fee is 10%, and the max trade fee is 50%, meaning they can + // never add up to more than 100%. + } + + if (_assetRecipient != address(this)) { + _assetRecipient.safeTransferETH(inputAmountExcludingRoyalty - protocolFee); + } + + // Transfer royalties + for (uint256 i; i < royaltyRecipients.length;) { + royaltyRecipients[i].safeTransferETH(royaltyAmounts[i]); + unchecked { + ++i; + } + } + + // Take protocol fee + if (protocolFee != 0) { + payable(address(factory())).safeTransferETH(protocolFee); + } + } + + /** + * @inheritdoc LSSVMPair + */ + function _refundTokenToSender(uint256 inputAmount) internal override { + // Give excess ETH back to caller + if (msg.value > inputAmount) { + payable(msg.sender).safeTransferETH(msg.value - inputAmount); + } + } + + /** + * @inheritdoc LSSVMPair + */ + function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal override { + // Send ETH to caller + if (outputAmount != 0) { + tokenRecipient.safeTransferETH(outputAmount); + } + } + + /** + * @notice Withdraws all token owned by the pair to the owner address. + * @dev Only callable by the owner. + */ + function withdrawAllETH() external onlyOwner { + withdrawETH(address(this).balance); + } + + /** + * @notice Withdraws a specified amount of token owned by the pair to the owner address. + * @dev Only callable by the owner. + * @param amount The amount of token to send to the owner. If the pair's balance is less than + * this value, the transaction will be reverted. + */ + function withdrawETH(uint256 amount) public onlyOwner { + payable(msg.sender).safeTransferETH(amount); + + // emit event since ETH is the pair token + emit TokenWithdrawal(amount); + } + + /** + * @inheritdoc LSSVMPair + */ + function withdrawERC20(ERC20 a, uint256 amount) external override onlyOwner { + a.safeTransfer(msg.sender, amount); + } + + /** + * @dev All ETH transfers into the pair are accepted. This is the main method + * for the owner to top up the pair's token reserves. + */ + receive() external payable { + emit TokenDeposit(msg.value); + } + + /** + * @dev All ETH transfers into the pair are accepted. This is the main method + * for the owner to top up the pair's token reserves. + */ + fallback() external payable { + // Only allow calls without function selector + require(msg.data.length == _immutableParamsLength()); + emit TokenDeposit(msg.value); + } + + function _preCallCheck(address) internal pure override {} +} + +// src/erc1155/LSSVMPairERC1155.sol + +/** + * @title LSSVMPairERC1155 + * @author boredGenius, 0xmons, 0xCygaar + * @notice An NFT/Token pair for an ERC1155 NFT where NFTs with the same ID are considered fungible. + */ +abstract contract LSSVMPairERC1155 is LSSVMPair { + /** + * External state-changing functions + */ + + /** + * @notice Sends token to the pair in exchange for any `numNFTs` NFTs + * @dev To compute the amount of token to send, call bondingCurve.getBuyInfo. + * This swap function is meant for users who are ID agnostic + * @param numNFTs The number of NFTs to purchase + * @param maxExpectedTokenInput The maximum acceptable cost from the sender. If the actual + * amount is greater than this value, the transaction will be reverted. + * @param nftRecipient The recipient of the NFTs + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. + * @return inputAmount The amount of token used for purchase + */ + function swapTokenForSpecificNFTs( + uint256[] calldata numNFTs, + uint256 maxExpectedTokenInput, + address nftRecipient, + bool isRouter, + address routerCaller + ) external payable virtual override returns (uint256) { + // Store locally to remove extra calls + factory().openLock(); + + // Input validation + { + if (poolType() == PoolType.TOKEN) revert LSSVMPair__WrongPoolType(); + if (numNFTs.length != 1 || numNFTs[0] == 0) revert LSSVMPair__ZeroSwapAmount(); + } + + // Call bonding curve for pricing information + uint256 tradeFee; + uint256 protocolFee; + uint256 inputAmountExcludingRoyalty; + (tradeFee, protocolFee, inputAmountExcludingRoyalty) = + _calculateBuyInfoAndUpdatePoolParams(numNFTs[0], bondingCurve(), factory()); + + (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = + _calculateRoyalties(nftId(), inputAmountExcludingRoyalty - protocolFee - tradeFee); + + // Revert if the input amount is too large + if (royaltyTotal + inputAmountExcludingRoyalty > maxExpectedTokenInput) { + revert LSSVMPair__DemandedInputTooLarge(); + } + + _pullTokenInputs({ + inputAmountExcludingRoyalty: inputAmountExcludingRoyalty, + royaltyRecipients: royaltyRecipients, + royaltyAmounts: royaltyAmounts, + royaltyTotal: royaltyTotal, + tradeFeeAmount: 2 * tradeFee, + isRouter: isRouter, + routerCaller: routerCaller, + protocolFee: protocolFee + }); + + _sendAnyNFTsToRecipient(IERC1155(nft()), nftRecipient, numNFTs[0]); + + _refundTokenToSender(royaltyTotal + inputAmountExcludingRoyalty); + + factory().closeLock(); + + emit SwapNFTOutPair(royaltyTotal + inputAmountExcludingRoyalty, numNFTs[0]); + + return (royaltyTotal + inputAmountExcludingRoyalty); + } + + /** + * @notice Sends a set of NFTs to the pair in exchange for token + * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. + * @param numNFTs The number of NFTs to swap + * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual + * amount is less than this value, the transaction will be reverted. + * @param tokenRecipient The recipient of the token output + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. + * @return outputAmount The amount of token received + */ + function swapNFTsForToken( + uint256[] calldata numNFTs, // @dev this is a bit hacky, to allow for better interop w/ other pair interfaces + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller + ) external virtual override returns (uint256 outputAmount) { + // Store locally to remove extra calls + ILSSVMPairFactoryLike _factory = factory(); + + _factory.openLock(); + + ICurve _bondingCurve = bondingCurve(); + + // Input validation + { + if (poolType() == PoolType.NFT) revert LSSVMPair__WrongPoolType(); + if (numNFTs.length != 1 || numNFTs[0] == 0) revert LSSVMPair__ZeroSwapAmount(); + } + + // Call bonding curve for pricing information + uint256 protocolFee; + (protocolFee, outputAmount) = _calculateSellInfoAndUpdatePoolParams(numNFTs[0], _bondingCurve, _factory); + + // Compute royalties + (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = + _calculateRoyalties(nftId(), outputAmount); + + // Deduct royalties from outputAmount + unchecked { + // Safe because we already require outputAmount >= royaltyTotal in calculateRoyalties() + outputAmount -= royaltyTotal; + } + + if (outputAmount < minExpectedTokenOutput) revert LSSVMPair__OutputTooSmall(); + + _takeNFTsFromSender(IERC1155(nft()), numNFTs[0], _factory, isRouter, routerCaller); + + _sendTokenOutput(tokenRecipient, outputAmount); + + for (uint256 i; i < royaltyRecipients.length;) { + _sendTokenOutput(royaltyRecipients[i], royaltyAmounts[i]); + unchecked { + ++i; + } + } + + _sendTokenOutput(payable(address(_factory)), protocolFee); + + _factory.closeLock(); + + emit SwapNFTInPair(outputAmount, numNFTs[0]); + } + + /** + * View functions + */ + + /** + * @notice Returns the ERC-1155 NFT ID this pool uses + */ + function nftId() public pure returns (uint256 id) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + id := calldataload(add(sub(calldatasize(), paramsLength), 61)) + } + } + + /** + * Internal functions + */ + + /** + * @notice Sends some number of NFTs to a recipient address + * @dev Even though we specify the NFT address here, this internal function is only + * used to send NFTs associated with this specific pool. + * @param _nft The address of the NFT to send + * @param nftRecipient The receiving address for the NFTs + * @param numNFTs The number of NFTs to send + */ + function _sendAnyNFTsToRecipient(IERC1155 _nft, address nftRecipient, uint256 numNFTs) internal virtual { + _nft.safeTransferFrom(address(this), nftRecipient, nftId(), numNFTs, bytes("")); + } + + /** + * @notice Takes NFTs from the caller and sends them into the pair's asset recipient + * @dev This is used by the LSSVMPair's swapNFTForToken function. + * @param _nft The NFT collection to take from + * @param numNFTs The number of NFTs to take + * @param isRouter Whether or not to use the router pull flow + * @param routerCaller If the caller is a router, passes in which address to pull from (i.e. the router's caller) + */ + function _takeNFTsFromSender( + IERC1155 _nft, + uint256 numNFTs, + ILSSVMPairFactoryLike factory, + bool isRouter, + address routerCaller + ) internal virtual { + address _assetRecipient = getAssetRecipient(); + + if (isRouter) { + // Verify if router is allowed + LSSVMRouter router = LSSVMRouter(payable(msg.sender)); + (bool routerAllowed,) = factory.routerStatus(router); + if (!routerAllowed) revert LSSVMPair__NotRouter(); + + uint256 _nftId = nftId(); + uint256 beforeBalance = _nft.balanceOf(_assetRecipient, _nftId); + uint256[] memory ids = new uint256[](1); + ids[0] = _nftId; + uint256[] memory amounts = new uint256[](1); + amounts[0] = numNFTs; + router.pairTransferERC1155From(_nft, routerCaller, _assetRecipient, ids, amounts); + if (_nft.balanceOf(_assetRecipient, _nftId) - beforeBalance != numNFTs) { + revert LSSVMPair__NftNotTransferred(); + } + } else { + // Pull NFTs directly from sender + _nft.safeTransferFrom(msg.sender, _assetRecipient, nftId(), numNFTs, bytes("")); + } + } + + /** + * Owner functions + */ + + /** + * @notice Rescues a specified set of NFTs owned by the pair to the owner address. Only callable by the owner. + * @param a The NFT to transfer + * @param nftIds The list of IDs of the NFTs to send to the owner + */ + function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual override onlyOwner { + uint256 numNFTs = nftIds.length; + for (uint256 i; i < numNFTs;) { + a.safeTransferFrom(address(this), msg.sender, nftIds[i]); + unchecked { + ++i; + } + } + } + + /** + * @notice Transfers ERC1155 tokens from the pair to the owner. Only callable by the owner. + * @param a The NFT to transfer + * @param ids The NFT ids to transfer + * @param amounts The amounts of each id to transfer + */ + function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) + external + virtual + override + onlyOwner + { + if (a == IERC1155(nft())) { + // Check if we need to emit an event for withdrawing the NFT this pool is trading + uint256 _nftId = nftId(); + uint256 numNFTs = ids.length; + uint256 numPairNFTsWithdrawn; + for (uint256 i; i < numNFTs;) { + if (ids[i] == _nftId) { + numPairNFTsWithdrawn += amounts[i]; + } + unchecked { + ++i; + } + } + + if (numPairNFTsWithdrawn != 0) { + // Only emit for the pair's NFT + emit NFTWithdrawal(numPairNFTsWithdrawn); + } + } + + a.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, bytes("")); + } +} + +// src/lib/LSSVMPairCloner.sol + +library LSSVMPairCloner { + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC721ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 86 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 81 bytes of extra data = 134 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 51 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60863d8160093d39f33d3d3d3d363d3d37605160353639366051013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x35) when rds < cds+0x35) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (81 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) + + // ------------------------------------------------------------------------------------------------------------- + // Total length is 143 (8f) bytes + // ------------------------------------------------------------------------------------------------------------- + + instance := create(0, ptr, 0x8f) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC721ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker, + ERC20 token + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 9a + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 101 bytes of extra data = 154 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 65 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"609a3d8160093d39f33d3d3d3d363d3d37606560353639366065013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (101 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) + mstore(add(ptr, 0x8f), shl(0x60, token)) + + // ------------------------------------------------------------------------------------------------------------- + // Total length is 163 (a3) bytes + // ------------------------------------------------------------------------------------------------------------- + + instance := create(0, ptr, 0xa3) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairETH. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param factory the factory that deployed the clone + * @param implementation the LSSVMPairETH implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC721ETHPairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37605160353639366051013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC20. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param implementation the LSSVMPairERC20 implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC721ERC20PairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37606560353639366065013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC1155ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 92 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 93 bytes of extra data = 146 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 5d + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60923d8160093d39f33d3d3d3d363d3d37605d6035363936605d013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (93 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), nftId) + + instance := create(0, ptr, 0x9b) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC1155ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId, + ERC20 token + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = a6 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 113 bytes of extra data = 166 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 71 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60a63d8160093d39f33d3d3d3d363d3d37607160353639366071013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (113 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), nftId) + mstore(add(ptr, 0x9b), shl(0x60, token)) + + instance := create(0, ptr, 0xaf) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC1155ETH. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param factory the factory that deployed the clone + * @param implementation the LSSVMPairERC1155ETH implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC1155ETHPairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37605d6035363936605d013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC1155ERC20. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param implementation the LSSVMPairERC1155ERC20 implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC1155ERC20PairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37607160353639366071013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } +} + +// src/erc721/LSSVMPairERC721.sol + +/** + * @title LSSVMPairERC721 + * @author boredGenius, 0xmons, 0xCygaar + * @notice An NFT/Token pair for an ERC721 NFT + */ +abstract contract LSSVMPairERC721 is LSSVMPair { + error LSSVMPairERC721__PropertyCheckFailed(); + error LSSVMPairERC721__NeedPropertyChecking(); + + /** + * External state-changing functions + */ + + /** + * @inheritdoc LSSVMPair + */ + function swapTokenForSpecificNFTs( + uint256[] calldata nftIds, + uint256 maxExpectedTokenInput, + address nftRecipient, + bool isRouter, + address routerCaller + ) external payable virtual override returns (uint256) { + // Store locally to remove extra calls + factory().openLock(); + + // Input validation + { + PoolType _poolType = poolType(); + if (_poolType == PoolType.TOKEN) revert LSSVMPair__WrongPoolType(); + if (nftIds.length == 0) revert LSSVMPair__ZeroSwapAmount(); + } + + // Call bonding curve for pricing information + uint256 protocolFee; + uint256 tradeFee; + uint256 inputAmountExcludingRoyalty; + (tradeFee, protocolFee, inputAmountExcludingRoyalty) = + _calculateBuyInfoAndUpdatePoolParams(nftIds.length, bondingCurve(), factory()); + + // Calculate royalties + (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = + _calculateRoyalties(nftIds[0], inputAmountExcludingRoyalty - protocolFee - tradeFee); + + // Revert if the input amount is too large + if (royaltyTotal + inputAmountExcludingRoyalty > maxExpectedTokenInput) { + revert LSSVMPair__DemandedInputTooLarge(); + } + + _pullTokenInputs({ + inputAmountExcludingRoyalty: inputAmountExcludingRoyalty, + royaltyAmounts: royaltyAmounts, + royaltyRecipients: royaltyRecipients, + royaltyTotal: royaltyTotal, + tradeFeeAmount: 2 * tradeFee, + isRouter: isRouter, + routerCaller: routerCaller, + protocolFee: protocolFee + }); + + { + _sendSpecificNFTsToRecipient(IERC721(nft()), nftRecipient, nftIds); + } + + _refundTokenToSender(royaltyTotal + inputAmountExcludingRoyalty); + + factory().closeLock(); + + emit SwapNFTOutPair(royaltyTotal + inputAmountExcludingRoyalty, nftIds); + + return (royaltyTotal + inputAmountExcludingRoyalty); + } + + /** + * @inheritdoc LSSVMPair + */ + function swapNFTsForToken( + uint256[] calldata nftIds, + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller + ) external virtual override returns (uint256 outputAmount) { + if (propertyChecker() != address(0)) revert LSSVMPairERC721__NeedPropertyChecking(); + + return _swapNFTsForToken(nftIds, minExpectedTokenOutput, tokenRecipient, isRouter, routerCaller); + } + + /** + * @notice Sends a set of NFTs to the pair in exchange for token + * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. + * @param nftIds The list of IDs of the NFTs to sell to the pair + * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual + * amount is less than this value, the transaction will be reverted. + * @param tokenRecipient The recipient of the token output + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for + * ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for + * ETH pairs. + * @param propertyCheckerParams Parameters to pass into the pair's underlying property checker + * @return outputAmount The amount of token received + */ + function swapNFTsForToken( + uint256[] calldata nftIds, + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller, + bytes calldata propertyCheckerParams + ) external virtual returns (uint256 outputAmount) { + if (!IPropertyChecker(propertyChecker()).hasProperties(nftIds, propertyCheckerParams)) { + revert LSSVMPairERC721__PropertyCheckFailed(); + } + + return _swapNFTsForToken(nftIds, minExpectedTokenOutput, tokenRecipient, isRouter, routerCaller); + } + + /** + * View functions + */ + + /** + * @notice Returns the property checker address + */ + function propertyChecker() public pure returns (address _propertyChecker) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _propertyChecker := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 61))) + } + } + + /** + * Internal functions + */ + + function _swapNFTsForToken( + uint256[] calldata nftIds, + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller + ) internal virtual returns (uint256 outputAmount) { + // Store locally to remove extra calls + ILSSVMPairFactoryLike _factory = factory(); + + _factory.openLock(); + + // Input validation + { + PoolType _poolType = poolType(); + if (_poolType == PoolType.NFT) revert LSSVMPair__WrongPoolType(); + if (nftIds.length == 0) revert LSSVMPair__ZeroSwapAmount(); + } + + // Call bonding curve for pricing information + uint256 protocolFee; + (protocolFee, outputAmount) = _calculateSellInfoAndUpdatePoolParams(nftIds.length, bondingCurve(), _factory); + + // Compute royalties + (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = + _calculateRoyalties(nftIds[0], outputAmount); + + // Deduct royalties from outputAmount + unchecked { + // Safe because we already require outputAmount >= royaltyTotal in calculateRoyalties() + outputAmount -= royaltyTotal; + } + + if (outputAmount < minExpectedTokenOutput) revert LSSVMPair__OutputTooSmall(); + + _takeNFTsFromSender(IERC721(nft()), nftIds, _factory, isRouter, routerCaller); + + _sendTokenOutput(tokenRecipient, outputAmount); + for (uint256 i; i < royaltyRecipients.length;) { + _sendTokenOutput(royaltyRecipients[i], royaltyAmounts[i]); + unchecked { + ++i; + } + } + + _sendTokenOutput(payable(address(_factory)), protocolFee); + + _factory.closeLock(); + + emit SwapNFTInPair(outputAmount, nftIds); + } + + /** + * @notice Sends specific NFTs to a recipient address + * @dev Even though we specify the NFT address here, this internal function is only + * used to send NFTs associated with this specific pool. + * @param _nft The address of the NFT to send + * @param nftRecipient The receiving address for the NFTs + * @param nftIds The specific IDs of NFTs to send + */ + function _sendSpecificNFTsToRecipient(IERC721 _nft, address nftRecipient, uint256[] calldata nftIds) + internal + virtual + { + // Send NFTs to recipient + uint256 numNFTs = nftIds.length; + for (uint256 i; i < numNFTs;) { + _nft.transferFrom(address(this), nftRecipient, nftIds[i]); + + unchecked { + ++i; + } + } + } + + /** + * @notice Takes NFTs from the caller and sends them into the pair's asset recipient + * @dev This is used by the LSSVMPair's swapNFTForToken function. + * @param _nft The NFT collection to take from + * @param nftIds The specific NFT IDs to take + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. + */ + function _takeNFTsFromSender( + IERC721 _nft, + uint256[] calldata nftIds, + ILSSVMPairFactoryLike _factory, + bool isRouter, + address routerCaller + ) internal virtual { + { + address _assetRecipient = getAssetRecipient(); + uint256 numNFTs = nftIds.length; + + if (isRouter) { + // Verify if router is allowed + LSSVMRouter router = LSSVMRouter(payable(msg.sender)); + (bool routerAllowed,) = _factory.routerStatus(router); + if (!routerAllowed) revert LSSVMPair__NotRouter(); + + // Call router to pull NFTs + // If more than 1 NFT is being transfered, and there is no property checker, we can do a balance check + // instead of an ownership check, as pools are indifferent between NFTs from the same collection + if ((numNFTs > 1) && (propertyChecker() == address(0))) { + uint256 beforeBalance = _nft.balanceOf(_assetRecipient); + for (uint256 i; i < numNFTs;) { + router.pairTransferNFTFrom(_nft, routerCaller, _assetRecipient, nftIds[i]); + + unchecked { + ++i; + } + } + if (_nft.balanceOf(_assetRecipient) - beforeBalance != numNFTs) { + revert LSSVMPair__NftNotTransferred(); + } + } + // Otherwise we need to pull each asset 1 at a time and verify ownership + else { + for (uint256 i; i < numNFTs;) { + router.pairTransferNFTFrom(_nft, routerCaller, _assetRecipient, nftIds[i]); + if (_nft.ownerOf(nftIds[i]) != _assetRecipient) revert LSSVMPair__NftNotTransferred(); + unchecked { + ++i; + } + } + } + } else { + // Pull NFTs directly from sender + for (uint256 i; i < numNFTs;) { + _nft.transferFrom(msg.sender, _assetRecipient, nftIds[i]); + unchecked { + ++i; + } + } + } + } + } + + /** + * Owner functions + */ + + /** + * @notice Rescues a specified set of NFTs owned by the pair to the owner address. (onlyOwner modifier is in the implemented function) + * @param a The NFT to transfer + * @param nftIds The list of IDs of the NFTs to send to the owner + */ + function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual override onlyOwner { + uint256 numNFTs = nftIds.length; + for (uint256 i; i < numNFTs;) { + a.safeTransferFrom(address(this), msg.sender, nftIds[i]); + unchecked { + ++i; + } + } + + if (a == IERC721(nft())) { + emit NFTWithdrawal(nftIds); + } + } + + /** + * @notice Rescues ERC1155 tokens from the pair to the owner. Only callable by the owner. + * @param a The NFT to transfer + * @param ids The NFT ids to transfer + * @param amounts The amounts of each id to transfer + */ + function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) + external + virtual + override + onlyOwner + { + a.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, ""); + } +} + +// src/erc1155/LSSVMPairERC1155ERC20.sol + +/** + * @title An ERC1155 pair where the token is an ERC20 + * @author boredGenius, 0xmons, 0xCygaar + */ +contract LSSVMPairERC1155ERC20 is LSSVMPairERC1155, LSSVMPairERC20 { + uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 113; + + constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} + + /** + * Public functions + */ + + /** + * @inheritdoc LSSVMPair + */ + function pairVariant() public pure virtual override returns (ILSSVMPairFactoryLike.PairVariant) { + return ILSSVMPairFactoryLike.PairVariant.ERC1155_ERC20; + } + + /** + * Internal functions + */ + + /** + * @inheritdoc LSSVMPair + * @dev see LSSVMPairCloner for params length calculation + */ + function _immutableParamsLength() internal pure override returns (uint256) { + return IMMUTABLE_PARAMS_LENGTH; + } +} + +// src/erc1155/LSSVMPairERC1155ETH.sol + +/** + * @title An ERC1155 pair where the token is an ETH + * @author boredGenius, 0xmons, 0xCygaar + */ +contract LSSVMPairERC1155ETH is LSSVMPairERC1155, LSSVMPairETH { + uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 93; + + constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} + + /** + * Public functions + */ + + /** + * @inheritdoc LSSVMPair + */ + function pairVariant() public pure virtual override returns (ILSSVMPairFactoryLike.PairVariant) { + return ILSSVMPairFactoryLike.PairVariant.ERC1155_ETH; + } + + /** + * Internal functions + */ + + /** + * @inheritdoc LSSVMPair + * @dev see LSSVMPairCloner for params length calculation + */ + function _immutableParamsLength() internal pure override returns (uint256) { + return IMMUTABLE_PARAMS_LENGTH; + } +} + +// src/erc721/LSSVMPairERC721ERC20.sol + +/** + * @title An NFT/Token pair where the token is an ERC20 + * @author boredGenius, 0xmons, 0xCygaar + */ +contract LSSVMPairERC721ERC20 is LSSVMPairERC721, LSSVMPairERC20 { + uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 101; + + constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} + + /** + * Public functions + */ + + /** + * @inheritdoc LSSVMPair + */ + function pairVariant() public pure override returns (ILSSVMPairFactoryLike.PairVariant) { + return ILSSVMPairFactoryLike.PairVariant.ERC721_ERC20; + } + + /** + * Internal functions + */ + + /** + * @inheritdoc LSSVMPair + * @dev see LSSVMPairCloner for params length calculation + */ + function _immutableParamsLength() internal pure override returns (uint256) { + return IMMUTABLE_PARAMS_LENGTH; + } +} + +// src/erc721/LSSVMPairERC721ETH.sol + +/** + * @title An NFT/Token pair where the token is ETH + * @author boredGenius, 0xmons, 0xCygaar + */ +contract LSSVMPairERC721ETH is LSSVMPairERC721, LSSVMPairETH { + uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 81; + + constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} + + /** + * Public functions + */ + + /** + * @inheritdoc LSSVMPair + */ + function pairVariant() public pure override returns (ILSSVMPairFactoryLike.PairVariant) { + return ILSSVMPairFactoryLike.PairVariant.ERC721_ETH; + } + + /** + * Internal functions + */ + + /** + * @inheritdoc LSSVMPair + * @dev see LSSVMPairCloner for params length calculation + */ + function _immutableParamsLength() internal pure override returns (uint256) { + return IMMUTABLE_PARAMS_LENGTH; + } +} + +// src/LSSVMPairFactory.sol + +/** + * @notice Imports for authAllowedForToken (forked from manifold.xyz Royalty Registry) + */ + +/** + * @title The factory contract used to deploy new pairs + * @author boredGenius, 0xmons, 0xCygaar + */ +contract LSSVMPairFactory is Owned, ILSSVMPairFactoryLike { + using LSSVMPairCloner for address; + using AddressUpgradeable for address; + using SafeTransferLib for address payable; + using SafeTransferLib for ERC20; + + uint256 internal constant MAX_PROTOCOL_FEE = 0.1e18; // 10%, must <= 1 - MAX_FEE + + LSSVMPairERC721ETH public immutable erc721ETHTemplate; + LSSVMPairERC721ERC20 public immutable erc721ERC20Template; + LSSVMPairERC1155ETH public immutable erc1155ETHTemplate; + LSSVMPairERC1155ERC20 public immutable erc1155ERC20Template; + address payable public override protocolFeeRecipient; + + // Units are in base 1e18 + uint256 public override protocolFeeMultiplier; + + mapping(ICurve => bool) public bondingCurveAllowed; + mapping(address => bool) public override callAllowed; + + // Data structures for settings logic + mapping(address => mapping(address => bool)) public settingsForCollection; + mapping(address => address) public settingsForPair; + + struct RouterStatus { + bool allowed; + bool wasEverTouched; + } + + mapping(LSSVMRouter => RouterStatus) public override routerStatus; + + address private constant _NOT_ENTERED = address(1); + address private _caller; + + event NewERC721Pair(address indexed poolAddress, uint256[] initialIds); + event NewERC1155Pair(address indexed poolAddress, uint256 initialBalance); + event ERC20Deposit(address indexed poolAddress, uint256 amount); + event NFTDeposit(address indexed poolAddress, uint256[] ids); + event ERC1155Deposit(address indexed poolAddress, uint256 indexed id, uint256 amount); + event ProtocolFeeRecipientUpdate(address indexed recipientAddress); + event ProtocolFeeMultiplierUpdate(uint256 newMultiplier); + event BondingCurveStatusUpdate(ICurve indexed bondingCurve, bool isAllowed); + event CallTargetStatusUpdate(address indexed target, bool isAllowed); + event RouterStatusUpdate(LSSVMRouter indexed router, bool isAllowed); + + error LSSVMPairFactory__FeeTooLarge(); + error LSSVMPairFactory__BondingCurveNotWhitelisted(); + error LSSVMPairFactory__ReentrantCall(); + error LSSVMPairFactory__ZeroAddress(); + error LSSVMPairFactory__CannotCallRouter(); + error LSSVMPairFactory__UnauthorizedCaller(); + error LSSVMPairFactory__InvalidPair(); + error LSSVMPairFactory__SettingsNotEnabledForCollection(); + error LSSVMPairFactory__SettingsNotEnabledForPair(); + + constructor( + LSSVMPairERC721ETH _erc721ETHTemplate, + LSSVMPairERC721ERC20 _erc721ERC20Template, + LSSVMPairERC1155ETH _erc1155ETHTemplate, + LSSVMPairERC1155ERC20 _erc1155ERC20Template, + address payable _protocolFeeRecipient, + uint256 _protocolFeeMultiplier, + address _owner + ) Owned(_owner) { + erc721ETHTemplate = _erc721ETHTemplate; + erc721ERC20Template = _erc721ERC20Template; + erc1155ETHTemplate = _erc1155ETHTemplate; + erc1155ERC20Template = _erc1155ERC20Template; + protocolFeeRecipient = _protocolFeeRecipient; + if (_protocolFeeMultiplier > MAX_PROTOCOL_FEE) revert LSSVMPairFactory__FeeTooLarge(); + protocolFeeMultiplier = _protocolFeeMultiplier; + _caller = _NOT_ENTERED; + } + + /** + * External functions + */ + + /** + * @notice Creates a pair contract using EIP-1167. + * @param _nft The NFT contract of the collection the pair trades + * @param _bondingCurve The bonding curve for the pair to price NFTs, must be whitelisted + * @param _assetRecipient The address that will receive the assets traders give during trades. + * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. + * @param _poolType TOKEN, NFT, or TRADE + * @param _delta The delta value used by the bonding curve. The meaning of delta depends on the specific curve. + * @param _fee The fee taken by the LP in each trade. Can only be non-zero if _poolType is Trade. + * @param _spotPrice The initial selling spot price + * @param _propertyChecker The contract to use for verifying properties of IDs sent in + * @param _initialNFTIDs The list of IDs of NFTs to transfer from the sender to the pair + * @return pair The new pair + */ + function createPairERC721ETH( + IERC721 _nft, + ICurve _bondingCurve, + address payable _assetRecipient, + LSSVMPair.PoolType _poolType, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + address _propertyChecker, + uint256[] calldata _initialNFTIDs + ) external payable returns (LSSVMPairERC721ETH pair) { + if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); + + pair = LSSVMPairERC721ETH( + payable( + address(erc721ETHTemplate).cloneERC721ETHPair( + this, _bondingCurve, _nft, uint8(_poolType), _propertyChecker + ) + ) + ); + + _initializePairERC721ETH(pair, _nft, _assetRecipient, _delta, _fee, _spotPrice, _initialNFTIDs); + emit NewERC721Pair(address(pair), _initialNFTIDs); + } + + struct CreateERC721ERC20PairParams { + ERC20 token; + IERC721 nft; + ICurve bondingCurve; + address payable assetRecipient; + LSSVMPair.PoolType poolType; + uint128 delta; + uint96 fee; + uint128 spotPrice; + address propertyChecker; + uint256[] initialNFTIDs; + uint256 initialTokenBalance; + } + + /** + * @notice Creates a pair contract using EIP-1167. + * @param params The info used to create a new pair. This includes: + * - token: The ERC20 token the pair trades + * - nft: The NFT contract of the collection the pair trades + * - bondingCurve: The bonding curve for the pair to price NFTs, must be whitelisted + * - assetRecipient: The address that will receive the assets traders give during trades. + * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. + * - poolType: TOKEN, NFT, or TRADE + * - delta: The delta value used by the bonding curve. The meaning of delta depends on the specific curve. + * - fee: The fee taken by the LP in each trade. Can only be non-zero if poolType is Trade. + * - spotPrice: Param 1 for the bonding curve, usually used for start price + * - delta: Param 2 for the bonding curve, usually used for dynamic adjustment + * - propertyChecker: The contract to use for verifying properties of IDs sent in + * - initialNFTIDs: The list of IDs of NFTs to transfer from the sender to the pair + * - initialTokenBalance: The initial token balance sent from the sender to the new pair + * @return pair The new pair + */ + function createPairERC721ERC20(CreateERC721ERC20PairParams calldata params) + external + returns (LSSVMPairERC721ERC20 pair) + { + if (!bondingCurveAllowed[params.bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); + + pair = LSSVMPairERC721ERC20( + payable( + address(erc721ERC20Template).cloneERC721ERC20Pair( + this, params.bondingCurve, params.nft, uint8(params.poolType), params.propertyChecker, params.token + ) + ) + ); + + _initializePairERC721ERC20( + pair, + params.token, + params.nft, + params.assetRecipient, + params.delta, + params.fee, + params.spotPrice, + params.initialNFTIDs, + params.initialTokenBalance + ); + emit NewERC721Pair(address(pair), params.initialNFTIDs); + } + /** + * @notice Creates a pair contract using EIP-1167. + * @param _nft The NFT contract of the collection the pair trades + * @param _bondingCurve The bonding curve for the pair to price NFTs, must be whitelisted + * @param _assetRecipient The address that will receive the assets traders give during trades. + * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. + * @param _poolType TOKEN, NFT, or TRADE + * @param _delta The delta value used by the bonding curve. The meaning of delta depends on the specific curve. + * @param _fee The fee taken by the LP in each trade. Can only be non-zero if _poolType is Trade. + * @param _spotPrice The initial selling spot price + * @param _nftId The ID of the NFT to trade + * @param _initialNFTBalance The amount of NFTs to transfer from the sender to the pair + * @return pair The new pair + */ + + function createPairERC1155ETH( + IERC1155 _nft, + ICurve _bondingCurve, + address payable _assetRecipient, + LSSVMPair.PoolType _poolType, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + uint256 _nftId, + uint256 _initialNFTBalance + ) external payable returns (LSSVMPairERC1155ETH pair) { + if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); + + pair = LSSVMPairERC1155ETH( + payable( + address(erc1155ETHTemplate).cloneERC1155ETHPair(this, _bondingCurve, _nft, uint8(_poolType), _nftId) + ) + ); + + _initializePairERC1155ETH(pair, _nft, _assetRecipient, _delta, _fee, _spotPrice, _nftId, _initialNFTBalance); + emit NewERC1155Pair(address(pair), _initialNFTBalance); + } + + struct CreateERC1155ERC20PairParams { + ERC20 token; + IERC1155 nft; + ICurve bondingCurve; + address payable assetRecipient; + LSSVMPair.PoolType poolType; + uint128 delta; + uint96 fee; + uint128 spotPrice; + uint256 nftId; + uint256 initialNFTBalance; + uint256 initialTokenBalance; + } + + /** + * @notice Creates a pair contract using EIP-1167. + * @param params The info used to create a new pair. This includes: + * - token: The ERC20 token the pair trades + * - nft: The NFT contract of the collection the pair trades + * - bondingCurve: The bonding curve for the pair to price NFTs, must be whitelisted + * - assetRecipient: The address that will receive the assets traders give during trades. + * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. + * - poolType: TOKEN, NFT, or TRADE + * - delta: The delta value used by the bonding curve. The meaning of delta depends on the specific curve. + * - fee: The fee taken by the LP in each trade. Can only be non-zero if poolType is Trade. + * - spotPrice: Param 1 for the bonding curve, usually used for start price + * - nftId: The ERC1155 nft id that this pair trades + * - initialNFTBalance: The initial NFT balance sent from the sender to the new pair + * - initialTokenBalance: The initial token balance sent from the sender to the new pair + * @return pair The new pair + */ + function createPairERC1155ERC20(CreateERC1155ERC20PairParams calldata params) + external + returns (LSSVMPairERC1155ERC20 pair) + { + if (!bondingCurveAllowed[params.bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); + + pair = LSSVMPairERC1155ERC20( + payable( + address(erc1155ERC20Template).cloneERC1155ERC20Pair( + this, params.bondingCurve, params.nft, uint8(params.poolType), params.nftId, params.token + ) + ) + ); + + _initializePairERC1155ERC20( + pair, + params.token, + params.nft, + params.assetRecipient, + params.delta, + params.fee, + params.spotPrice, + params.nftId, + params.initialNFTBalance, + params.initialTokenBalance + ); + emit NewERC1155Pair(address(pair), params.initialNFTBalance); + } + + function isValidPair(address pairAddress) public view returns (bool) { + PairVariant variant = LSSVMPair(pairAddress).pairVariant(); + if (variant == PairVariant.ERC721_ETH) { + return LSSVMPairCloner.isERC721ETHPairClone(address(this), address(erc721ETHTemplate), pairAddress); + } else if (variant == PairVariant.ERC721_ERC20) { + return LSSVMPairCloner.isERC721ERC20PairClone(address(this), address(erc721ERC20Template), pairAddress); + } else if (variant == PairVariant.ERC1155_ETH) { + return LSSVMPairCloner.isERC1155ETHPairClone(address(this), address(erc1155ETHTemplate), pairAddress); + } else if (variant == PairVariant.ERC1155_ERC20) { + return LSSVMPairCloner.isERC1155ERC20PairClone(address(this), address(erc1155ERC20Template), pairAddress); + } else { + return false; + } + } + + function getPairNFTType(address pairAddress) public pure returns (PairNFTType) { + PairVariant variant = LSSVMPair(pairAddress).pairVariant(); + return PairNFTType(uint8(variant) / 2); + } + + function getPairTokenType(address pairAddress) public pure returns (PairTokenType) { + PairVariant variant = LSSVMPair(pairAddress).pairVariant(); + return PairTokenType(uint8(variant) % 2); + } + + function openLock() public { + if (_caller == msg.sender) revert LSSVMPairFactory__ReentrantCall(); + _caller = msg.sender; + } + + function closeLock() public { + if (_caller != msg.sender) revert LSSVMPairFactory__ReentrantCall(); + _caller = _NOT_ENTERED; + } + + /** + * @notice Checks if an address is an allowed auth for a token + * @param tokenAddress The token address to check + * @param proposedAuthAddress The auth address to check + * @return True if the proposedAuthAddress is a valid auth for the tokenAddress, false otherwise. + */ + function authAllowedForToken(address tokenAddress, address proposedAuthAddress) public view returns (bool) { + // Check for admin interface + if ( + ERC165Checker.supportsInterface(tokenAddress, type(IAdminControl).interfaceId) + && IAdminControl(tokenAddress).isAdmin(proposedAuthAddress) + ) { + return true; + } + // Check for owner + try OwnableUpgradeable(tokenAddress).owner() returns (address owner) { + if (owner == proposedAuthAddress) return true; + + if (owner.isContract()) { + try OwnableUpgradeable(owner).owner() returns (address passThroughOwner) { + if (passThroughOwner == proposedAuthAddress) return true; + } catch {} + } + } catch {} + // Check for default OZ auth role + try IAccessControlUpgradeable(tokenAddress).hasRole(0x00, proposedAuthAddress) returns (bool hasRole) { + if (hasRole) return true; + } catch {} + // Nifty Gateway overrides + try INiftyBuilderInstance(tokenAddress).niftyRegistryContract() returns (address niftyRegistry) { + try INiftyRegistry(niftyRegistry).isValidNiftySender(proposedAuthAddress) returns (bool valid) { + if (valid) return true; + } catch {} + } catch {} + // Foundation overrides + try IFoundationTreasuryNode(tokenAddress).getFoundationTreasury() returns (address payable foundationTreasury) { + try IFoundationTreasury(foundationTreasury).isAdmin(proposedAuthAddress) returns (bool isAdmin) { + if (isAdmin) return true; + } catch {} + } catch {} + // DIGITALAX overrides + try IDigitalax(tokenAddress).accessControls() returns (address externalAccessControls) { + try IDigitalaxAccessControls(externalAccessControls).hasAdminRole(proposedAuthAddress) returns ( + bool hasRole + ) { + if (hasRole) return true; + } catch {} + } catch {} + // Art Blocks overrides + try IArtBlocks(tokenAddress).admin() returns (address admin) { + if (admin == proposedAuthAddress) return true; + } catch {} + return false; + } + + /** + * @notice Allows receiving ETH in order to receive protocol fees + */ + receive() external payable {} + + /** + * Admin functions + */ + + /** + * @notice Withdraws the ETH balance to the protocol fee recipient. + * Only callable by the owner. + */ + function withdrawETHProtocolFees() external onlyOwner { + protocolFeeRecipient.safeTransferETH(address(this).balance); + } + + /** + * @notice Withdraws ERC20 tokens to the protocol fee recipient. Only callable by the owner. + * @param token The token to transfer + * @param amount The amount of tokens to transfer + */ + function withdrawERC20ProtocolFees(ERC20 token, uint256 amount) external onlyOwner { + token.safeTransfer(protocolFeeRecipient, amount); + } + + /** + * @notice Changes the protocol fee recipient address. Only callable by the owner. + * @param _protocolFeeRecipient The new fee recipient + */ + function changeProtocolFeeRecipient(address payable _protocolFeeRecipient) external onlyOwner { + if (_protocolFeeRecipient == address(0)) revert LSSVMPairFactory__ZeroAddress(); + protocolFeeRecipient = _protocolFeeRecipient; + emit ProtocolFeeRecipientUpdate(_protocolFeeRecipient); + } + + /** + * @notice Changes the protocol fee multiplier. Only callable by the owner. + * @param _protocolFeeMultiplier The new fee multiplier, 18 decimals + */ + function changeProtocolFeeMultiplier(uint256 _protocolFeeMultiplier) external onlyOwner { + if (_protocolFeeMultiplier > MAX_PROTOCOL_FEE) revert LSSVMPairFactory__FeeTooLarge(); + protocolFeeMultiplier = _protocolFeeMultiplier; + emit ProtocolFeeMultiplierUpdate(_protocolFeeMultiplier); + } + + /** + * @notice Sets the whitelist status of a bonding curve contract. Only callable by the owner. + * @param bondingCurve The bonding curve contract + * @param isAllowed True to whitelist, false to remove from whitelist + */ + function setBondingCurveAllowed(ICurve bondingCurve, bool isAllowed) external onlyOwner { + bondingCurveAllowed[bondingCurve] = isAllowed; + emit BondingCurveStatusUpdate(bondingCurve, isAllowed); + } + + /** + * @notice Sets the whitelist status of a contract to be called arbitrarily by a pair. + * Only callable by the owner. + * @param target The target contract + * @param isAllowed True to whitelist, false to remove from whitelist + */ + function setCallAllowed(address payable target, bool isAllowed) external onlyOwner { + // Ensure target is not / was not ever a router + if (isAllowed) { + if (routerStatus[LSSVMRouter(target)].wasEverTouched) revert LSSVMPairFactory__CannotCallRouter(); + } + + callAllowed[target] = isAllowed; + emit CallTargetStatusUpdate(target, isAllowed); + } + + /** + * @notice Updates the router whitelist. Only callable by the owner. + * @param _router The router + * @param isAllowed True to whitelist, false to remove from whitelist + */ + function setRouterAllowed(LSSVMRouter _router, bool isAllowed) external onlyOwner { + // Ensure target is not arbitrarily callable by pairs + if (isAllowed) { + if (callAllowed[address(_router)]) revert LSSVMPairFactory__CannotCallRouter(); + } + routerStatus[_router] = RouterStatus({allowed: isAllowed, wasEverTouched: true}); + + emit RouterStatusUpdate(_router, isAllowed); + } + + /** + * @notice Returns the Settings for a pair if it currently has Settings + * @param pairAddress The address of the pair to look up + * @return settingsEnabled Whether or not the pair has custom settings + * @return bps The royalty basis points from the custom settings, 0 if there is no custom settings + */ + function getSettingsForPair(address pairAddress) public view returns (bool settingsEnabled, uint96 bps) { + address settingsAddress = settingsForPair[pairAddress]; + if (settingsAddress == address(0)) { + return (false, 0); + } + return ISettings(settingsAddress).getRoyaltyInfo(pairAddress); + } + + /** + * @notice Enables or disables an settings for a given NFT collection + * @param settings The address of the Settings contract + * @param collectionAddress The NFT project that the settings is toggled for + * @param enable Bool to determine whether to disable or enable the settings + */ + function toggleSettingsForCollection(address settings, address collectionAddress, bool enable) public { + if (!authAllowedForToken(collectionAddress, msg.sender)) revert LSSVMPairFactory__UnauthorizedCaller(); + if (enable) { + settingsForCollection[collectionAddress][settings] = true; + } else { + delete settingsForCollection[collectionAddress][settings]; + } + } + + /** + * @notice Enables an Settings for a given Pair + * @notice Only the owner of the Pair can call this function + * @notice The Settings must be enabled for the Pair's collection + * @param settings The address of the Settings contract + * @param pairAddress The address of the Pair contract + */ + function enableSettingsForPair(address settings, address pairAddress) public { + if (!isValidPair(pairAddress)) revert LSSVMPairFactory__InvalidPair(); + LSSVMPair pair = LSSVMPair(pairAddress); + if (pair.owner() != msg.sender) revert LSSVMPairFactory__UnauthorizedCaller(); + if (!settingsForCollection[address(pair.nft())][settings]) { + revert LSSVMPairFactory__SettingsNotEnabledForCollection(); + } + settingsForPair[pairAddress] = settings; + } + + /** + * @notice Disables an Settings for a given Pair + * @notice Only the owner of the Pair can call this function + * @notice The Settings must already be enabled for the Pair + * @param settings The address of the Settings contract + * @param pairAddress The address of the Pair contract + */ + function disableSettingsForPair(address settings, address pairAddress) public { + if (!isValidPair(pairAddress)) revert LSSVMPairFactory__InvalidPair(); + if (settingsForPair[pairAddress] != settings) revert LSSVMPairFactory__SettingsNotEnabledForPair(); + LSSVMPair pair = LSSVMPair(pairAddress); + if (pair.owner() != msg.sender) revert LSSVMPairFactory__UnauthorizedCaller(); + delete settingsForPair[pairAddress]; + } + + /** + * Internal functions + */ + + function _initializePairERC721ETH( + LSSVMPairERC721ETH _pair, + IERC721 _nft, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + uint256[] calldata _initialNFTIDs + ) internal { + // Initialize pair + _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); + + // Transfer initial ETH to pair + if (msg.value != 0) payable(address(_pair)).safeTransferETH(msg.value); + + // Transfer initial NFTs from sender to pair + uint256 numNFTs = _initialNFTIDs.length; + for (uint256 i; i < numNFTs;) { + _nft.transferFrom(msg.sender, address(_pair), _initialNFTIDs[i]); + + unchecked { + ++i; + } + } + } + + function _initializePairERC721ERC20( + LSSVMPairERC721ERC20 _pair, + ERC20 _token, + IERC721 _nft, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + uint256[] calldata _initialNFTIDs, + uint256 _initialTokenBalance + ) internal { + // Initialize pair + _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); + + // Transfer initial tokens to pair (if != 0) + if (_initialTokenBalance != 0) { + _token.safeTransferFrom(msg.sender, address(_pair), _initialTokenBalance); + } + + // Transfer initial NFTs from sender to pair + uint256 numNFTs = _initialNFTIDs.length; + for (uint256 i; i < numNFTs;) { + _nft.transferFrom(msg.sender, address(_pair), _initialNFTIDs[i]); + + unchecked { + ++i; + } + } + } + + function _initializePairERC1155ETH( + LSSVMPairERC1155ETH _pair, + IERC1155 _nft, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + uint256 _nftId, + uint256 _initialNFTBalance + ) internal { + // Initialize pair + _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); + + // Transfer initial ETH to pair + if (msg.value != 0) payable(address(_pair)).safeTransferETH(msg.value); + + // Transfer initial NFTs from sender to pair + if (_initialNFTBalance != 0) { + _nft.safeTransferFrom(msg.sender, address(_pair), _nftId, _initialNFTBalance, bytes("")); + } + } + + function _initializePairERC1155ERC20( + LSSVMPairERC1155ERC20 _pair, + ERC20 _token, + IERC1155 _nft, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice, + uint256 _nftId, + uint256 _initialNFTBalance, + uint256 _initialTokenBalance + ) internal { + // Initialize pair + _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); + + // Transfer initial tokens to pair + if (_initialTokenBalance != 0) { + _token.safeTransferFrom(msg.sender, address(_pair), _initialTokenBalance); + } + + // Transfer initial NFTs from sender to pair + if (_initialNFTBalance != 0) { + _nft.safeTransferFrom(msg.sender, address(_pair), _nftId, _initialNFTBalance, bytes("")); + } + } + + /** + * @dev Used to deposit NFTs into a pair after creation and emit an event for indexing (if recipient is indeed a pair) + */ + function depositNFTs(IERC721 _nft, uint256[] calldata ids, address recipient) external { + uint256 numNFTs = ids.length; + + // Early return for trivial transfers + if (numNFTs == 0) return; + + // Transfer NFTs from caller to recipient + for (uint256 i; i < numNFTs;) { + _nft.transferFrom(msg.sender, recipient, ids[i]); + + unchecked { + ++i; + } + } + if (isValidPair(recipient) && (address(_nft) == LSSVMPair(recipient).nft())) { + emit NFTDeposit(recipient, ids); + } + } + + /** + * @dev Used to deposit ERC20s into a pair after creation and emit an event for indexing (if recipient is indeed an ERC20 pair and the token matches) + */ + function depositERC20(ERC20 token, address recipient, uint256 amount) external { + // Early return for trivial transfers + if (amount == 0) return; + + token.safeTransferFrom(msg.sender, recipient, amount); + if ( + isValidPair(recipient) && getPairTokenType(recipient) == PairTokenType.ERC20 + && token == LSSVMPairERC20(recipient).token() + ) { + emit ERC20Deposit(recipient, amount); + } + } + + /** + * @dev Used to deposit ERC1155 NFTs into a pair after creation and emit an event for indexing (if recipient is indeed a pair) + */ + function depositERC1155(IERC1155 nft, uint256 id, address recipient, uint256 amount) external { + if (amount == 0) return; + + nft.safeTransferFrom(msg.sender, recipient, id, amount, bytes("")); + + if ( + isValidPair(recipient) && getPairNFTType(recipient) == PairNFTType.ERC1155 + && address(nft) == LSSVMPair(recipient).nft() && id == LSSVMPairERC1155(recipient).nftId() + ) { + emit ERC1155Deposit(recipient, id, amount); + } + } +} + diff --git a/tests/functional/data/sources/OldCWIA.sol b/tests/functional/data/sources/OldCWIA.sol new file mode 100644 index 0000000000..98531def3d --- /dev/null +++ b/tests/functional/data/sources/OldCWIA.sol @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity >=0.4.23 ^0.8.0; + +// lib/ds-test/src/test.sol + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +contract DSTest { + event log (string); + event logs (bytes); + + event log_address (address); + event log_bytes32 (bytes32); + event log_int (int); + event log_uint (uint); + event log_bytes (bytes); + event log_string (string); + + event log_named_address (string key, address val); + event log_named_bytes32 (string key, bytes32 val); + event log_named_decimal_int (string key, int val, uint decimals); + event log_named_decimal_uint (string key, uint val, uint decimals); + event log_named_int (string key, int val); + event log_named_uint (string key, uint val); + event log_named_bytes (string key, bytes val); + event log_named_string (string key, string val); + + bool public IS_TEST = true; + bool public failed; + + address constant HEVM_ADDRESS = + address(bytes20(uint160(uint256(keccak256('hevm cheat code'))))); + + modifier mayRevert() { _; } + modifier testopts(string memory) { _; } + + function fail() internal { + failed = true; + } + + modifier logs_gas() { + uint startGas = gasleft(); + _; + uint endGas = gasleft(); + emit log_named_uint("gas", startGas - endGas); + } + + function assertTrue(bool condition) internal { + if (!condition) { + emit log("Error: Assertion Failed"); + fail(); + } + } + + function assertTrue(bool condition, string memory err) internal { + if (!condition) { + emit log_named_string("Error", err); + assertTrue(condition); + } + } + + function assertEq(address a, address b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [address]"); + emit log_named_address(" Expected", b); + emit log_named_address(" Actual", a); + fail(); + } + } + function assertEq(address a, address b, string memory err) internal { + if (a != b) { + emit log_named_string ("Error", err); + assertEq(a, b); + } + } + + function assertEq(bytes32 a, bytes32 b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [bytes32]"); + emit log_named_bytes32(" Expected", b); + emit log_named_bytes32(" Actual", a); + fail(); + } + } + function assertEq(bytes32 a, bytes32 b, string memory err) internal { + if (a != b) { + emit log_named_string ("Error", err); + assertEq(a, b); + } + } + function assertEq32(bytes32 a, bytes32 b) internal { + assertEq(a, b); + } + function assertEq32(bytes32 a, bytes32 b, string memory err) internal { + assertEq(a, b, err); + } + + function assertEq(int a, int b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [int]"); + emit log_named_int(" Expected", b); + emit log_named_int(" Actual", a); + fail(); + } + } + function assertEq(int a, int b, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + function assertEq(uint a, uint b) internal { + if (a != b) { + emit log("Error: a == b not satisfied [uint]"); + emit log_named_uint(" Expected", b); + emit log_named_uint(" Actual", a); + fail(); + } + } + function assertEq(uint a, uint b, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + function assertEqDecimal(int a, int b, uint decimals) internal { + if (a != b) { + emit log("Error: a == b not satisfied [decimal int]"); + emit log_named_decimal_int(" Expected", b, decimals); + emit log_named_decimal_int(" Actual", a, decimals); + fail(); + } + } + function assertEqDecimal(int a, int b, uint decimals, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEqDecimal(a, b, decimals); + } + } + function assertEqDecimal(uint a, uint b, uint decimals) internal { + if (a != b) { + emit log("Error: a == b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Expected", b, decimals); + emit log_named_decimal_uint(" Actual", a, decimals); + fail(); + } + } + function assertEqDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a != b) { + emit log_named_string("Error", err); + assertEqDecimal(a, b, decimals); + } + } + + function assertGt(uint a, uint b) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertGt(uint a, uint b, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGt(a, b); + } + } + function assertGt(int a, int b) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertGt(int a, int b, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGt(a, b); + } + } + function assertGtDecimal(int a, int b, uint decimals) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertGtDecimal(int a, int b, uint decimals, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGtDecimal(a, b, decimals); + } + } + function assertGtDecimal(uint a, uint b, uint decimals) internal { + if (a <= b) { + emit log("Error: a > b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertGtDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a <= b) { + emit log_named_string("Error", err); + assertGtDecimal(a, b, decimals); + } + } + + function assertGe(uint a, uint b) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertGe(uint a, uint b, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGe(a, b); + } + } + function assertGe(int a, int b) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertGe(int a, int b, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGe(a, b); + } + } + function assertGeDecimal(int a, int b, uint decimals) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertGeDecimal(int a, int b, uint decimals, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + function assertGeDecimal(uint a, uint b, uint decimals) internal { + if (a < b) { + emit log("Error: a >= b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertGeDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a < b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + + function assertLt(uint a, uint b) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertLt(uint a, uint b, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLt(a, b); + } + } + function assertLt(int a, int b) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertLt(int a, int b, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLt(a, b); + } + } + function assertLtDecimal(int a, int b, uint decimals) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertLtDecimal(int a, int b, uint decimals, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLtDecimal(a, b, decimals); + } + } + function assertLtDecimal(uint a, uint b, uint decimals) internal { + if (a >= b) { + emit log("Error: a < b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertLtDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a >= b) { + emit log_named_string("Error", err); + assertLtDecimal(a, b, decimals); + } + } + + function assertLe(uint a, uint b) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [uint]"); + emit log_named_uint(" Value a", a); + emit log_named_uint(" Value b", b); + fail(); + } + } + function assertLe(uint a, uint b, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLe(a, b); + } + } + function assertLe(int a, int b) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [int]"); + emit log_named_int(" Value a", a); + emit log_named_int(" Value b", b); + fail(); + } + } + function assertLe(int a, int b, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLe(a, b); + } + } + function assertLeDecimal(int a, int b, uint decimals) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [decimal int]"); + emit log_named_decimal_int(" Value a", a, decimals); + emit log_named_decimal_int(" Value b", b, decimals); + fail(); + } + } + function assertLeDecimal(int a, int b, uint decimals, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertLeDecimal(a, b, decimals); + } + } + function assertLeDecimal(uint a, uint b, uint decimals) internal { + if (a > b) { + emit log("Error: a <= b not satisfied [decimal uint]"); + emit log_named_decimal_uint(" Value a", a, decimals); + emit log_named_decimal_uint(" Value b", b, decimals); + fail(); + } + } + function assertLeDecimal(uint a, uint b, uint decimals, string memory err) internal { + if (a > b) { + emit log_named_string("Error", err); + assertGeDecimal(a, b, decimals); + } + } + + function assertEq(string memory a, string memory b) internal { + if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { + emit log("Error: a == b not satisfied [string]"); + emit log_named_string(" Value a", a); + emit log_named_string(" Value b", b); + fail(); + } + } + function assertEq(string memory a, string memory b, string memory err) internal { + if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { + emit log_named_string("Error", err); + assertEq(a, b); + } + } + + function checkEq0(bytes memory a, bytes memory b) internal pure returns (bool ok) { + ok = true; + if (a.length == b.length) { + for (uint i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + ok = false; + } + } + } else { + ok = false; + } + } + function assertEq0(bytes memory a, bytes memory b) internal { + if (!checkEq0(a, b)) { + emit log("Error: a == b not satisfied [bytes]"); + emit log_named_bytes(" Expected", a); + emit log_named_bytes(" Actual", b); + fail(); + } + } + function assertEq0(bytes memory a, bytes memory b, string memory err) internal { + if (!checkEq0(a, b)) { + emit log_named_string("Error", err); + assertEq0(a, b); + } + } +} + +// src/Utils.sol + +library Utils { + + function codeSize(address _addr) internal view returns (uint256 size) { + assembly { + size := extcodesize(_addr) + } + } + + function codeAt( + address _addr, + uint256 _start, + uint256 _end + ) internal view returns (bytes memory oCode) { + uint256 csize = codeSize(_addr); + if (csize == 0) return bytes(""); + + if (_start > csize) return bytes(""); + // if (_end < _start) revert InvalidCodeAtRange(csize, _start, _end); + + unchecked { + uint256 reqSize = _end - _start; + uint256 maxSize = csize - _start; + + uint256 size = maxSize < reqSize ? maxSize : reqSize; + + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + oCode := mload(0x40) + // new "memory end" including padding + mstore( + 0x40, + add(oCode, and(add(add(size, add(_start, 0x20)), 0x1f), not(0x1f))) + ) + // store length in memory + mstore(oCode, size) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(oCode, 0x20), _start, size) + } + } + } + + function codeAtLen( + address _addr, + uint256 _start, + uint256 _len + ) internal view returns (bytes memory oCode) { + + unchecked { + assembly { + // allocate output byte array - this could also be done without assembly + // by using o_code = new bytes(size) + oCode := mload(0x40) + // new "memory end" including padding + mstore( + 0x40, + add(oCode, and(add(add(_len, add(_start, 0x20)), 0x1f), not(0x1f))) + ) + // store length in memory + mstore(oCode, _len) + // actually retrieve the code, this needs assembly + extcodecopy(_addr, add(oCode, 0x20), _start, _len) + } + } + } +} + +// src/ClonesWithCallData.sol + +contract ClonesWithCallData is DSTest { + + function cloneWithCallDataProvision(address implementation, bytes memory data) + internal + returns (address instance) + { + uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call + uint256 creationSize = 0x43 + extraLength ; + uint256 runSize = creationSize - 11; + uint256 dataPtr; + uint256 ptr; + // solhint-disable-next-line no-inline-assembly + assembly { + ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (11 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 3d | RETURNDATASIZE | 0 | – + // 61 runtime | PUSH2 runtime (r) | r 0 | – + mstore( + ptr, + 0x3d61000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x02), shl(240, runSize)) // size of the contract running bytecode (16 bits) + + // creation size = 0b + // 80 | DUP1 | r r 0 | – + // 60 creation | PUSH1 creation (c) | c r r 0 | – + // 3d | RETURNDATASIZE | 0 c r r 0 | – + // 39 | CODECOPY | r 0 | [0-2d]: runtime code + // 81 | DUP2 | 0 c 0 | [0-2d]: runtime code + // f3 | RETURN | 0 | [0-2d]: runtime code + mstore( + add(ptr, 0x04), + 0x80600b3d3981f300000000000000000000000000000000000000000000000000 + ) + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME + // ------------------------------------------------------------------------------------------------------------- + + // 36 | CALLDATASIZE | cds | – + // 3d | RETURNDATASIZE | 0 cds | – + // 3d | RETURNDATASIZE | 0 0 cds | – + // 37 | CALLDATACOPY | – | [0, cds] = calldata + // 61 | PUSH2 extra | extra | [0, cds] = calldata + mstore( + add(ptr, 0x0b), + 0x363d3d3761000000000000000000000000000000000000000000000000000000 + ) + mstore( + add(ptr, 0x10), + shl(240, extraLength) + ) + + // 60 0x38 | PUSH1 0x38 | 0x38 extra | [0, cds] = calldata // 0x38 (56) is runtime size - data + // 36 | CALLDATASIZE | cds 0x38 extra | [0, cds] = calldata + // 39 | CODECOPY | _ | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata + // 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata + // 61 extra | PUSH2 extra | extra cds 0 0 0 | [0, cds] = calldata + mstore( + add(ptr, 0x12), + 0x603836393d3d3d36610000000000000000000000000000000000000000000000 + ) + mstore( + add(ptr, 0x1b), + shl(240, extraLength) + ) + + // 01 | ADD | cds+extra 0 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata + mstore(add(ptr, 0x1d), 0x013d730000000000000000000000000000000000000000000000000000000000) + mstore(add(ptr, 0x20), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata + // f4 | DELEGATECALL | success 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata + // 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata + // 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata + // 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds) + // 90 | SWAP1 | 0 success | [0, rds] = return data + // 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data + // 91 | SWAP2 | success 0 rds | [0, rds] = return data + // 60 0x36 | PUSH1 0x36 | 0x36 sucess 0 rds | [0, rds] = return data + // 57 | JUMPI | 0 rds | [0, rds] = return data + // fd | REVERT | – | [0, rds] = return data + // 5b | JUMPDEST | 0 rds | [0, rds] = return data + // f3 | RETURN | – | [0, rds] = return data + + mstore( + add(ptr, 0x34), + 0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000 + ) + } + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // (but also send as appended data to the delegatecall) + // ------------------------------------------------------------------------------------------------------------- + + extraLength -= 2; + uint256 counter = extraLength; + uint256 copyPtr = ptr + 0x43; + // solhint-disable-next-line no-inline-assembly + assembly { + dataPtr := add(data, 32) + } + for (; counter >= 32; counter -= 32) { + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, mload(dataPtr)) + } + copyPtr += 32; + dataPtr += 32; + } + uint256 mask = ~(256**(32 - counter) - 1); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, and(mload(dataPtr), mask)) + } + copyPtr += (32 - counter); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, shl(240, extraLength)) + } + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create(0, ptr, creationSize) + } + require(instance != address(0), "create failed"); + } + +} + +// src/ClonesWithImmutableArgs.sol + +contract ClonesWithImmutableArgs is DSTest { + + function clone(address implementation, bytes memory data) + internal + returns (address instance) + { + uint256 extraLength = data.length; + uint256 creationSize = 0x38 + extraLength; + uint256 runSize = creationSize - 11; + uint256 dataPtr; + uint256 ptr; + // solhint-disable-next-line no-inline-assembly + assembly { + ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (11 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 3d | RETURNDATASIZE | 0 | – + // 61 runtime | PUSH2 runtime (r) | r 0 | – + mstore( + ptr, + 0x3d61000000000000000000000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x02), shl(240, runSize)) // size of the contract running bytecode (16 bits) + + // creation size = 0b + // 80 | DUP1 | r r 0 | – + // 60 creation | PUSH1 creation (c) | c r r 0 | – + // 3d | RETURNDATASIZE | 0 c r r 0 | – + // 39 | CODECOPY | r 0 | [0-2d]: runtime code + // 81 | DUP2 | 0 c 0 | [0-2d]: runtime code + // f3 | RETURN | 0 | [0-2d]: runtime code + mstore( + add(ptr, 0x04), + 0x80600b3d3981f300000000000000000000000000000000000000000000000000 + ) + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME + // ------------------------------------------------------------------------------------------------------------- + + // 36 | CALLDATASIZE | cds | – + // 3d | RETURNDATASIZE | 0 cds | – + // 3d | RETURNDATASIZE | 0 0 cds | – + // 37 | CALLDATACOPY | – | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata + // 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata + mstore( + add(ptr, 0x0b), + 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000 + ) + mstore(add(ptr, 0x15), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata + // f4 | DELEGATECALL | success 0 | [0, cds] = calldata + // 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata + // 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata + // 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata + // 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds) + // 90 | SWAP1 | 0 success | [0, rds] = return data + // 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data + // 91 | SWAP2 | success 0 rds | [0, rds] = return data + // 60 dest | PUSH1 dest | dest sucess 0 rds | [0, rds] = return data + // 57 | JUMPI | 0 rds | [0, rds] = return data + // fd | REVERT | – | [0, rds] = return data + // 5b | JUMPDEST | 0 rds | [0, rds] = return data + // f3 | RETURN | – | [0, rds] = return data + + mstore( + add(ptr, 0x29), + 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 + ) + } + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // ------------------------------------------------------------------------------------------------------------- + + uint256 copyPtr = ptr + 0x38; + // solhint-disable-next-line no-inline-assembly + assembly { + dataPtr := add(data, 32) + } + for (; extraLength >= 32; extraLength -= 32) { + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, mload(dataPtr)) + } + copyPtr += 32; + dataPtr += 32; + } + uint256 mask = ~(256**(32 - extraLength) - 1); + // solhint-disable-next-line no-inline-assembly + assembly { + mstore(copyPtr, and(mload(dataPtr), mask)) + } + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create(0, ptr, creationSize) + } + require(instance != address(0), "create failed"); + } + +} + +// src/Template.sol + +contract Template is ClonesWithImmutableArgs, ClonesWithCallData { + + event Cloned(address addr); + + function clone1(bytes calldata data) external returns (Template clonedGreeter) { + clonedGreeter = Template(ClonesWithImmutableArgs.clone(address(this), data)); + emit Cloned(address(clonedGreeter)); + } + + function clone2(bytes calldata data) external returns (Template clonedGreeter) { + clonedGreeter = Template(ClonesWithCallData.cloneWithCallDataProvision(address(this), data)); + emit Cloned(address(clonedGreeter)); + } + } + diff --git a/tests/functional/data/sources/SequenceFactory.sol b/tests/functional/data/sources/SequenceFactory.sol new file mode 100644 index 0000000000..6c87279c3b --- /dev/null +++ b/tests/functional/data/sources/SequenceFactory.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity =0.8.18; + +// contracts/Wallet.sol + +/** + Minimal upgradeable proxy implementation, delegates all calls to the address + defined by the storage slot matching the wallet address. + + Inspired by EIP-1167 Implementation (https://eips.ethereum.org/EIPS/eip-1167) + + deployed code: + + 0x00 0x36 0x36 CALLDATASIZE cds + 0x01 0x3d 0x3d RETURNDATASIZE 0 cds + 0x02 0x3d 0x3d RETURNDATASIZE 0 0 cds + 0x03 0x37 0x37 CALLDATACOPY + 0x04 0x3d 0x3d RETURNDATASIZE 0 + 0x05 0x3d 0x3d RETURNDATASIZE 0 0 + 0x06 0x3d 0x3d RETURNDATASIZE 0 0 0 + 0x07 0x36 0x36 CALLDATASIZE cds 0 0 0 + 0x08 0x3d 0x3d RETURNDATASIZE 0 cds 0 0 0 + 0x09 0x30 0x30 ADDRESS addr 0 cds 0 0 0 + 0x0A 0x54 0x54 SLOAD imp 0 cds 0 0 0 + 0x0B 0x5a 0x5a GAS gas imp 0 cds 0 0 0 + 0x0C 0xf4 0xf4 DELEGATECALL suc 0 + 0x0D 0x3d 0x3d RETURNDATASIZE rds suc 0 + 0x0E 0x82 0x82 DUP3 0 rds suc 0 + 0x0F 0x80 0x80 DUP1 0 0 rds suc 0 + 0x10 0x3e 0x3e RETURNDATACOPY suc 0 + 0x11 0x90 0x90 SWAP1 0 suc + 0x12 0x3d 0x3d RETURNDATASIZE rds 0 suc + 0x13 0x91 0x91 SWAP2 suc 0 rds + 0x14 0x60 0x18 0x6018 PUSH1 0x18 suc 0 rds + /-- 0x16 0x57 0x57 JUMPI 0 rds + | 0x17 0xfd 0xfd REVERT + \-> 0x18 0x5b 0x5b JUMPDEST 0 rds + 0x19 0xf3 0xf3 RETURN + + flat deployed code: 0x363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3 + + deploy function: + + 0x00 0x60 0x3a 0x603a PUSH1 0x3a + 0x02 0x60 0x0e 0x600e PUSH1 0x0e 0x3a + 0x04 0x3d 0x3d RETURNDATASIZE 0 0x0e 0x3a + 0x05 0x39 0x39 CODECOPY + 0x06 0x60 0x1a 0x601a PUSH1 0x1a + 0x08 0x80 0x80 DUP1 0x1a 0x1a + 0x09 0x51 0x51 MLOAD imp 0x1a + 0x0A 0x30 0x30 ADDRESS addr imp 0x1a + 0x0B 0x55 0x55 SSTORE 0x1a + 0x0C 0x3d 0x3d RETURNDATASIZE 0 0x1a + 0x0D 0xf3 0xf3 RETURN + [...deployed code] + + flat deploy function: 0x603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3 +*/ +library Wallet { + bytes internal constant creationCode = hex"603a600e3d39601a805130553df3363d3d373d3d3d363d30545af43d82803e903d91601857fd5bf3"; +} + +// contracts/Factory.sol + +contract Factory { + error DeployFailed(address _mainModule, bytes32 _salt); + event Target(address addr); + + /** + * @notice Will deploy a new wallet instance + * @param _mainModule Address of the main module to be used by the wallet + * @param _salt Salt used to generate the wallet, which is the imageHash + * of the wallet's configuration. + * @dev It is recommended to not have more than 200 signers as opcode repricing + * could make transactions impossible to execute as all the signers must be + * passed for each transaction. + */ + function deploy(address _mainModule, bytes32 _salt) public payable returns (address _contract) { + bytes memory code = abi.encodePacked(Wallet.creationCode, uint256(uint160(_mainModule))); + assembly { _contract := create2(callvalue(), add(code, 32), mload(code), _salt) } + emit Target(_contract); + if (_contract == address(0)) revert DeployFailed(_mainModule, _salt); + + } +} + diff --git a/tests/functional/data/sources/SoladyFactory.sol b/tests/functional/data/sources/SoladyFactory.sol new file mode 100644 index 0000000000..28d2681a5b --- /dev/null +++ b/tests/functional/data/sources/SoladyFactory.sol @@ -0,0 +1,2877 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Minimal proxy library. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol) +/// @author Minimal proxy by 0age (https://github.com/0age) +/// @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie +/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) +/// @author Minimal ERC1967 proxy by jtriley-eth (https://github.com/jtriley-eth/minimum-viable-proxy) +/// +/// @dev Minimal proxy: +/// Although the sw0nt pattern saves 5 gas over the ERC1167 pattern during runtime, +/// it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern, +/// which saves 4 gas over the ERC1167 pattern during runtime, and has the smallest bytecode. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal proxy (PUSH0 variant): +/// This is a new minimal proxy that uses the PUSH0 opcode introduced during Shanghai. +/// It is optimized first for minimal runtime gas, then for minimal bytecode. +/// The PUSH0 clone functions are intentionally postfixed with a jarring "_PUSH0" as +/// many EVM chains may not support the PUSH0 opcode in the early months after Shanghai. +/// Please use with caution. +/// - Automatically verified on Etherscan. +/// +/// @dev Clones with immutable args (CWIA): +/// The implementation of CWIA here is does NOT append the immutable args into the calldata +/// passed into delegatecall. It is simply an ERC1167 minimal proxy with the immutable arguments +/// appended to the back of the runtime bytecode. +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// +/// @dev Minimal ERC1967 proxy: +/// An minimal ERC1967 proxy, intended to be upgraded with UUPS. +/// This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal ERC1967 proxy with immutable args: +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// - Automatically verified on Etherscan. +/// +/// @dev ERC1967I proxy: +/// An variant of the minimal ERC1967 proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// `implementation` address. The returned implementation is guaranteed to be valid if the +/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`. +/// +/// @dev ERC1967I proxy with immutable args: +/// An variant of the minimal ERC1967 proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// +/// @dev Minimal ERC1967 beacon proxy: +/// A minimal beacon proxy, intended to be upgraded with an upgradable beacon. +/// - Automatically verified on Etherscan. +/// +/// @dev Minimal ERC1967 beacon proxy with immutable args: +/// - Uses the identity precompile (0x4) to copy args during deployment. +/// - Automatically verified on Etherscan. +/// +/// @dev ERC1967I beacon proxy: +/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// `implementation` address. The returned implementation is guaranteed to be valid if the +/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`. +/// +/// @dev ERC1967I proxy with immutable args: +/// An variant of the minimal ERC1967 beacon proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// - Uses the identity precompile (0x4) to copy args during deployment. +library LibClone { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The keccak256 of deployed code for the clone proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant CLONE_CODE_HASH = + 0x48db2cfdb2853fce0b464f1f93a1996469459df3ab6c812106074c4106a1eb1f; + + /// @dev The keccak256 of deployed code for the PUSH0 proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant PUSH0_CLONE_CODE_HASH = + 0x67bc6bde1b84d66e267c718ba44cf3928a615d29885537955cb43d44b3e789dc; + + /// @dev The keccak256 of deployed code for the ERC-1167 CWIA proxy, + /// with the implementation set to `address(0)`. + bytes32 internal constant CWIA_CODE_HASH = + 0x3cf92464268225a4513da40a34d967354684c32cd0edd67b5f668dfe3550e940; + + /// @dev The keccak256 of the deployed code for the ERC1967 proxy. + bytes32 internal constant ERC1967_CODE_HASH = + 0xaaa52c8cc8a0e3fd27ce756cc6b4e70c51423e9b597b11f32d3e49f8b1fc890d; + + /// @dev The keccak256 of the deployed code for the ERC1967I proxy. + bytes32 internal constant ERC1967I_CODE_HASH = + 0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7; + + /// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy. + bytes32 internal constant ERC1967_BEACON_PROXY_CODE_HASH = + 0x14044459af17bc4f0f5aa2f658cb692add77d1302c29fe2aebab005eea9d1162; + + /// @dev The keccak256 of the deployed code for the ERC1967 beacon proxy. + bytes32 internal constant ERC1967I_BEACON_PROXY_CODE_HASH = + 0xf8c46d2793d5aa984eb827aeaba4b63aedcab80119212fce827309788735519a; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Unable to deploy the clone. + error DeploymentFailed(); + + /// @dev The salt must start with either the zero address or `by`. + error SaltDoesNotStartWith(); + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a clone of `implementation`. + function clone(address implementation) internal returns (address instance) { + instance = clone(0, implementation); + } + + /// @dev Deploys a clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + /** + * --------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (44 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 0 0 | | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | | + * 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | | + * 37 | CALLDATACOPY | 0 0 0 0 | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata | + * 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata | + * 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata | + * 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata | + * | + * 60 0x2a | PUSH1 0x2a | 0x2a success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create(value, 0x0c, 0x35) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + function cloneDeterministic(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create2(value, 0x0c, 0x35, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the clone of `implementation`. + function initCode(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x40), 0x5af43d3d93803e602a57fd5bf30000000000000000000000) + mstore(add(c, 0x28), implementation) + mstore(add(c, 0x14), 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + mstore(c, 0x35) // Store the length. + mstore(0x40, add(c, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation`. + function initCodeHash(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + hash := keccak256(0x0c, 0x35) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the clone of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(address implementation, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = initCodeHash(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS (PUSH0 VARIANT) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a PUSH0 clone of `implementation`. + function clone_PUSH0(address implementation) internal returns (address instance) { + instance = clone_PUSH0(0, implementation); + } + + /// @dev Deploys a PUSH0 clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone_PUSH0(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * --------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 5f | PUSH0 | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 5f | PUSH0 | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (45 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 5f | PUSH0 | 0 | | + * 5f | PUSH0 | 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 5f | PUSH0 | 0 cds 0 0 | | + * 5f | PUSH0 | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..cds): calldata | + * 5f | PUSH0 | 0 cds 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 0 rds success | [0..cds): calldata | + * 3e | RETURNDATACOPY | success | [0..rds): returndata | + * | + * 60 0x29 | PUSH1 0x29 | 0x29 success | [0..rds): returndata | + * 57 | JUMPI | | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..rds): returndata | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create(value, 0x0e, 0x36) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + function cloneDeterministic_PUSH0(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic_PUSH0(0, implementation, salt); + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic_PUSH0(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create2(value, 0x0e, 0x36, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the PUSH0 clone of `implementation`. + function initCode_PUSH0(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x40), 0x5af43d5f5f3e6029573d5ffd5b3d5ff300000000000000000000) // 16 + mstore(add(c, 0x26), implementation) // 20 + mstore(add(c, 0x12), 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + mstore(c, 0x36) // Store the length. + mstore(0x40, add(c, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the PUSH0 clone of `implementation`. + function initCodeHash_PUSH0(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + hash := keccak256(0x0e, 0x36) + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the PUSH0 clone of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress_PUSH0( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash_PUSH0(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CLONES WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`. + function clone(address implementation, bytes memory args) internal returns (address instance) { + instance = clone(0, implementation, args); + } + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `args`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------+ + * CREATION (10 bytes) | + * ---------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------| + * 61 runSize | PUSH2 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------| + * RUNTIME (45 bytes + extraLength) | + * ---------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract ::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 | [0..cds): calldata | + * 36 | CALLDATASIZE | cds 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata | + * | + * ::: copy return data to memory ::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 | [0..cds): calldata | + * 82 | DUP3 | 0 rds success 0 | [0..cds): calldata | + * 80 | DUP1 | 0 0 rds success 0 | [0..cds): calldata | + * 3e | RETURNDATACOPY | success 0 | [0..rds): returndata | + * 90 | SWAP1 | 0 success | [0..rds): returndata | + * 3d | RETURNDATASIZE | rds 0 success | [0..rds): returndata | + * 91 | SWAP2 | success 0 rds | [0..rds): returndata | + * | + * 60 0x2b | PUSH1 0x2b | 0x2b success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * ---------------------------------------------------------------------------+ + */ + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + instance := create(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + function cloneDeterministic(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + function cloneDeterministic( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + instance := create2(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + /// This method does not revert if the clone has already been deployed. + function createDeterministicClone(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicClone(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `args` and `salt`. + /// This method does not revert if the clone has already been deployed. + function createDeterministicClone( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x43), n)) + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + // forgefmt: disable-next-item + mstore(add(m, gt(n, 0xffd2)), add(0xfe61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x0c), add(n, 0x37))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x0c), add(n, 0x37), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `args`. + function initCode(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x57), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x37), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(c, 0x28), implementation) + mstore(add(c, 0x14), add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + mstore(c, add(0x37, n)) // Store the length. + mstore(add(c, add(n, 0x57)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0x77))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `args`. + function initCodeHash(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x2d = 0xffd2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffd2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x43), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x23), 0x5af43d82803e903d91602b57fd5bf3) + mstore(add(m, 0x14), implementation) + mstore(m, add(0x61002d3d81600a3d39f3363d3d373d3d3d363d73, shl(136, n))) + hash := keccak256(add(m, 0x0c), add(n, 0x37)) + } + } + + /// @dev Returns the address of the clone of + /// `implementation` using immutable arguments encoded in `args`, with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress( + address implementation, + bytes memory data, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash(implementation, data); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnClone(instance, 0, 2 ** 256 - 1)`. + function argsOnClone(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x2d))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x2d, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnClone(instance, start, 2 ** 256 - 1)`. + function argsOnClone(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x2d)) + let l := sub(n, and(0xffffff, mul(lt(start, n), start))) + extcodecopy(instance, add(args, 0x20), add(start, 0x2d), add(l, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the clone with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnClone(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x0d), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x2d) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: The ERC1967 proxy here is intended to be upgraded with UUPS. + // This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + function deployERC1967(address implementation) internal returns (address instance) { + instance = deployERC1967(0, implementation); + } + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (61 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x38 | PUSH1 0x38 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create(value, 0x21, 0x5f) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + function deployDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967(0, implementation, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967(0, implementation, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x21, 0x5f)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation`. + function initCodeERC1967(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x60), 0x3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f300) + mstore(add(c, 0x40), 0x55f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc) + mstore(add(c, 0x20), or(shl(24, implementation), 0x600951)) + mstore(add(c, 0x09), 0x603d3d8160223d3973) + mstore(c, 0x5f) // Store the length. + mstore(0x40, add(c, 0x80)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation`. + function initCodeHashERC1967(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + hash := keccak256(0x21, 0x5f) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967 proxy of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`. + function deployERC1967(address implementation, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967(0, implementation, args); + } + + /// @dev Deploys a minimal ERC1967 proxy with `implementation` and `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + instance := create(value, m, add(n, 0x60)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + function deployDeterministicERC1967(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + instance := create2(value, m, add(n, 0x60), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967(0, implementation, args, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation`, `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x60), n)) + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + mstore(gt(n, 0xffc2), add(0xfe61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(m, add(n, 0x60))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, m, add(n, 0x60), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation` and `args`. + function initCodeERC1967(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x80), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x60), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(c, 0x40), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(add(c, 0x20), 0x6009) + mstore(add(c, 0x1e), implementation) + mstore(add(c, 0x0a), add(0x61003d3d8160233d3973, shl(56, n))) + mstore(c, add(n, 0x60)) // Store the length. + mstore(add(c, add(n, 0x80)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0xa0))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation` and `args`. + function initCodeHashERC1967(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x3d = 0xffc2`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffc2)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x60), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x40), 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(add(m, 0x20), 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x16, 0x6009) + mstore(0x14, implementation) + mstore(0x00, add(0x61003d3d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + hash := keccak256(m, add(n, 0x60)) + } + } + + /// @dev Returns the address of the ERC1967 proxy of `implementation`, `args`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967(implementation, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x3d))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x3d, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x3d)) + let l := sub(n, and(0xffffff, mul(lt(start, n), start))) + extcodecopy(instance, add(args, 0x20), add(start, 0x3d), add(l, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x1d), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x3d) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This proxy has a special code path that activates if `calldatasize() == 1`. + // This code path skips the delegatecall and directly returns the `implementation` address. + // The returned implementation is guaranteed to be valid if the keccak256 of the + // proxy's code is equal to `ERC1967I_CODE_HASH`. + + /// @dev Deploys a ERC1967I proxy with `implementation`. + function deployERC1967I(address implementation) internal returns (address instance) { + instance = deployERC1967I(0, implementation); + } + + /// @dev Deploys a ERC1967I proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967I(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (82 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: check calldatasize ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 58 | PC | 1 cds | | + * 14 | EQ | eqs | | + * 60 0x43 | PUSH1 0x43 | dest eqs | | + * 57 | JUMPI | | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x3E | PUSH1 0x3E | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: implementation , return :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 60 0x0F | PUSH1 0x0F | o 32 | | + * 3d | RETURNDATASIZE | 0 o 32 | | + * 39 | CODECOPY | | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 | [0..32): implementation slot | + * 51 | MLOAD | slot | [0..32): implementation slot | + * 54 | SLOAD | impl | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 impl | [0..32): implementation slot | + * 52 | MSTORE | | [0..32): implementation address | + * 59 | MSIZE | 32 | [0..32): implementation address | + * 3d | RETURNDATASIZE | 0 32 | [0..32): implementation address | + * f3 | RETURN | | [0..32): implementation address | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create(value, 0x0c, 0x74) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + function deployDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x0c, 0x74)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the ERC1967I proxy of `implementation`. + function initCodeERC1967I(address implementation) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x74), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(c, 0x54), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(c, 0x34), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(c, 0x1d), implementation) + mstore(add(c, 0x09), 0x60523d8160223d3973) + mstore(add(c, 0x94), 0) + mstore(c, 0x74) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation`. + function initCodeHashERC1967I(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + hash := keccak256(0x0c, 0x74) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967I proxy of `implementation`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967I( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967I(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`. + function deployERC1967I(address implementation, bytes memory args) internal returns (address) { + return deployERC1967I(0, implementation, args); + } + + /// @dev Deploys a minimal ERC1967I proxy with `implementation` and `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967I(uint256 value, address implementation, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + + mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe6100523d8160233d3973, shl(56, n))) + + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + instance := create(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation`, `args`, and `salt`. + function deployDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967I(0, implementation, args, salt); + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation`,`args`, and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967I( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + + mstore(add(m, 0x6b), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x4b), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x2b), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(m, 0x14), implementation) + mstore(m, add(0xfe6100523d8160233d3973, shl(56, n))) + + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + instance := create2(value, add(m, add(0x15, lt(n, 0xffae))), add(0x75, n), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation`, `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(address implementation, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967I(0, implementation, args, salt); + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation`,`args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I( + uint256 value, + address implementation, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x75), n)) + mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x16, 0x600f) + mstore(0x14, implementation) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(gt(n, 0xffad), add(0xfe6100523d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(m, add(n, 0x75))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, m, add(0x75, n), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the ERC1967I proxy of `implementation`and `args`. + function initCodeERC1967I(address implementation, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i))) + } + + mstore(add(c, 0x75), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(c, 0x55), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(c, 0x35), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(c, 0x1e), implementation) + mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n))) + mstore(add(c, add(n, 0x95)), 0) + mstore(c, add(0x75, n)) // Store the length. + mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I proxy of `implementation` and `args. + function initCodeHashERC1967I(address implementation, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x75), i), mload(add(add(args, 0x20), i))) + } + + mstore(add(m, 0x55), 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(add(m, 0x35), 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(add(m, 0x15), 0x5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x16, 0x600f) + mstore(0x14, implementation) + mstore(0x00, add(0x6100523d8160233d3973, shl(56, n))) + mstore(m, mload(0x16)) + hash := keccak256(m, add(0x75, n)) + } + } + + /// @dev Returns the address of the ERC1967I proxy of `implementation`, 'args` with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967I( + address implementation, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967I(implementation, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967I(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x52))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967I(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967I(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x52)) + let l := sub(n, and(0xffffff, mul(lt(start, n), start))) + extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(l, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967I(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x32), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x52) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967 BOOTSTRAP OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // A bootstrap is a minimal UUPS implementation that allows an ERC1967 proxy + // pointing to it to be upgraded. The ERC1967 proxy can then be deployed to a + // deterministic address independent of the implementation: + // ``` + // address bootstrap = LibClone.erc1967Bootstrap(); + // address instance = LibClone.deployDeterministicERC1967(0, bootstrap, salt); + // LibClone.bootstrapERC1967(bootstrap, implementation); + // ``` + + /// @dev Deploys the ERC1967 bootstrap if it has not been deployed. + function erc1967Bootstrap() internal returns (address) { + return erc1967Bootstrap(address(this)); + } + + /// @dev Deploys the ERC1967 bootstrap if it has not been deployed. + function erc1967Bootstrap(address authorizedUpgrader) internal returns (address bootstrap) { + bytes memory c = initCodeERC1967Bootstrap(authorizedUpgrader); + bootstrap = predictDeterministicAddress(keccak256(c), bytes32(0), address(this)); + /// @solidity memory-safe-assembly + assembly { + if iszero(extcodesize(bootstrap)) { + if iszero(create2(0, add(c, 0x20), mload(c), 0)) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + } + + /// @dev Replaces the implementation at `instance`. + function bootstrapERC1967(address instance, address implementation) internal { + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, implementation) + if iszero(call(gas(), instance, 0, 0x0c, 0x14, codesize(), 0x00)) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Replaces the implementation at `instance`, and then call it with `data`. + function bootstrapERC1967AndCall(address instance, address implementation, bytes memory data) + internal + { + /// @solidity memory-safe-assembly + assembly { + let n := mload(data) + mstore(data, implementation) + if iszero(call(gas(), instance, 0, add(data, 0x0c), add(n, 0x14), codesize(), 0x00)) { + if iszero(returndatasize()) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + returndatacopy(mload(0x40), 0x00, returndatasize()) + revert(mload(0x40), returndatasize()) + } + mstore(data, n) // Restore the length of `data`. + } + } + + /// @dev Returns the implementation address of the ERC1967 bootstrap for this contract. + function predictDeterministicAddressERC1967Bootstrap() internal view returns (address) { + return predictDeterministicAddressERC1967Bootstrap(address(this), address(this)); + } + + /// @dev Returns the implementation address of the ERC1967 bootstrap for this contract. + function predictDeterministicAddressERC1967Bootstrap( + address authorizedUpgrader, + address deployer + ) internal pure returns (address) { + bytes32 hash = initCodeHashERC1967Bootstrap(authorizedUpgrader); + return predictDeterministicAddress(hash, bytes32(0), deployer); + } + + /// @dev Returns the initialization code of the ERC1967 bootstrap. + function initCodeERC1967Bootstrap(address authorizedUpgrader) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x80), 0x3d3560601c5af46047573d6000383e3d38fd0000000000000000000000000000) + mstore(add(c, 0x60), 0xa920a3ca505d382bbc55601436116049575b005b363d3d373d3d601436036014) + mstore(add(c, 0x40), 0x0338573d3560601c7f360894a13ba1a3210667c828492db98dca3e2076cc3735) + mstore(add(c, 0x20), authorizedUpgrader) + mstore(add(c, 0x0c), 0x606880600a3d393df3fe3373) + mstore(c, 0x72) + mstore(0x40, add(c, 0xa0)) + } + } + + /// @dev Returns the initialization code hash of the ERC1967 bootstrap. + function initCodeHashERC1967Bootstrap(address authorizedUpgrader) + internal + pure + returns (bytes32) + { + return keccak256(initCodeERC1967Bootstrap(authorizedUpgrader)); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 BEACON PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: If you use this proxy, you MUST make sure that the beacon is a + // valid ERC1967 beacon. This means that the beacon must always return a valid + // address upon a staticcall to `implementation()`, given sufficient gas. + // For performance, the deployment operations and the proxy assumes that the + // beacon is always valid and will NOT validate it. + + /// @dev Deploys a minimal ERC1967 beacon proxy. + function deployERC1967BeaconProxy(address beacon) internal returns (address instance) { + instance = deployERC1967BeaconProxy(0, beacon); + } + + /// @dev Deploys a minimal ERC1967 beacon proxy. + /// Deposits `value` ETH during deployment. + function deployERC1967BeaconProxy(uint256 value, address beacon) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (82 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * | + * ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 36 | CALLDATASIZE | cds 32 | | + * 60 0x04 | PUSH1 0x04 | 4 cds 32 | | + * 36 | CALLDATASIZE | cds 4 cds 32 | | + * 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | | + * 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | | + * 1b | SHL | sel cds 4 cds 32 | | + * 36 | CALLDATASIZE | cds sel cds 4 cds 32 | | + * 52 | MSTORE | cds 4 cds 32 | sel | + * 7f slot | PUSH32 slot | s cds 4 cds 32 | sel | + * 54 | SLOAD | beac cds 4 cds 32 | sel | + * 5a | GAS | g beac cds 4 cds 32 | sel | + * fa | STATICCALL | succ | impl | + * 50 | POP | | impl | + * 36 | CALLDATASIZE | cds | impl | + * 51 | MLOAD | impl | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x4d | PUSH1 0x4d | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + instance := create(value, 0x0c, 0x74) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`. + function deployDeterministicERC1967BeaconProxy(address beacon, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967BeaconProxy(0, beacon, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967BeaconProxy(0, beacon, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x0c, 0x74)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 beacon proxy. + function initCodeERC1967BeaconProxy(address beacon) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x74), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(c, 0x54), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(c, 0x34), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(c, 0x1d), beacon) + mstore(add(c, 0x09), 0x60523d8160223d3973) + mstore(add(c, 0x94), 0) + mstore(c, 0x74) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy. + function initCodeHashERC1967BeaconProxy(address beacon) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(0x40, 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, beacon)))) + hash := keccak256(0x0c, 0x74) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967 beacon proxy, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967BeaconProxy( + address beacon, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967BeaconProxy(beacon); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967 BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a minimal ERC1967 beacon proxy with `args`. + function deployERC1967BeaconProxy(address beacon, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967BeaconProxy(0, beacon, args); + } + + /// @dev Deploys a minimal ERC1967 beacon proxy with `args`. + /// Deposits `value` ETH during deployment. + function deployERC1967BeaconProxy(uint256 value, address beacon, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + instance := create(value, add(m, 0x16), add(n, 0x75)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + function deployDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967BeaconProxy(0, beacon, args, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967BeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + instance := create2(value, add(m, 0x16), add(n, 0x75), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967BeaconProxy(0, beacon, args, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967BeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x8b), n)) + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + mstore(add(m, gt(n, 0xffad)), add(0xfe6100523d8160233d3973, shl(56, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x16), add(n, 0x75))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x16), add(n, 0x75), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 beacon proxy. + function initCodeERC1967BeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x95), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x75), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(c, 0x55), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(c, 0x35), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(c, 0x1e), beacon) + mstore(add(c, 0x0a), add(0x6100523d8160233d3973, shl(56, n))) + mstore(c, add(n, 0x75)) // Store the length. + mstore(add(c, add(n, 0x95)), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(c, add(n, 0xb5))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 beacon proxy with `args`. + function initCodeHashERC1967BeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x52 = 0xffad`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffad)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(m, 0x8b), i), mload(add(add(args, 0x20), i))) + } + mstore(add(m, 0x6b), 0xb3582b35133d50545afa5036515af43d6000803e604d573d6000fd5b3d6000f3) + mstore(add(m, 0x4b), 0x1b60e01b36527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6c) + mstore(add(m, 0x2b), 0x60195155f3363d3d373d3d363d602036600436635c60da) + mstore(add(m, 0x14), beacon) + mstore(m, add(0x6100523d8160233d3973, shl(56, n))) + hash := keccak256(add(m, 0x16), add(n, 0x75)) + } + } + + /// @dev Returns the address of the ERC1967 beacon proxy with `args`, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967BeaconProxy( + address beacon, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967BeaconProxy(beacon, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967BeaconProxy(address instance) internal view returns (bytes memory args) { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x52))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x52, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967BeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967BeaconProxy(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x52)) + let l := sub(n, and(0xffffff, mul(lt(start, n), start))) + extcodecopy(instance, add(args, 0x20), add(start, 0x52), add(l, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967 beacon proxy with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967BeaconProxy(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x32), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x52) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I BEACON PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This proxy has a special code path that activates if `calldatasize() == 1`. + // This code path skips the delegatecall and directly returns the `implementation` address. + // The returned implementation is guaranteed to be valid if the keccak256 of the + // proxy's code is equal to `ERC1967_BEACON_PROXY_CODE_HASH`. + // + // If you use this proxy, you MUST make sure that the beacon is a + // valid ERC1967 beacon. This means that the beacon must always return a valid + // address upon a staticcall to `implementation()`, given sufficient gas. + // For performance, the deployment operations and the proxy assumes that the + // beacon is always valid and will NOT validate it. + + /// @dev Deploys a ERC1967I beacon proxy. + function deployERC1967IBeaconProxy(address beacon) internal returns (address instance) { + instance = deployERC1967IBeaconProxy(0, beacon); + } + + /// @dev Deploys a ERC1967I beacon proxy. + /// Deposits `value` ETH during deployment. + function deployERC1967IBeaconProxy(uint256 value, address beacon) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * 73 beac | PUSH20 beac | beac 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos beac 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot beac 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (87 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * | + * ~~~~~~~ beacon staticcall sub procedure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 36 | CALLDATASIZE | cds 32 | | + * 60 0x04 | PUSH1 0x04 | 4 cds 32 | | + * 36 | CALLDATASIZE | cds 4 cds 32 | | + * 63 0x5c60da1b | PUSH4 0x5c60da1b | 0x5c60da1b cds 4 cds 32 | | + * 60 0xe0 | PUSH1 0xe0 | 224 0x5c60da1b cds 4 cds 32 | | + * 1b | SHL | sel cds 4 cds 32 | | + * 36 | CALLDATASIZE | cds sel cds 4 cds 32 | | + * 52 | MSTORE | cds 4 cds 32 | sel | + * 7f slot | PUSH32 slot | s cds 4 cds 32 | sel | + * 54 | SLOAD | beac cds 4 cds 32 | sel | + * 5a | GAS | g beac cds 4 cds 32 | sel | + * fa | STATICCALL | succ | impl | + * ~~~~~~ check calldatasize ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 36 | CALLDATASIZE | cds succ | | + * 14 | EQ | | impl | + * 60 0x52 | PUSH1 0x52 | | impl | + * 57 | JUMPI | | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 36 | CALLDATASIZE | cds | impl | + * 51 | MLOAD | impl | impl | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + * 5a | GAS | g impl 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 60 0x01 | PUSH1 0x01 | 1 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [1..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x52 | PUSH1 0x52 | dest succ | [1..returndatasize): returndata | + * 57 | JUMPI | | [1..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata | + * 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata | + * fd | REVERT | | [1..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [1..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [1..returndatasize): returndata | + * 60 0x01 | PUSH1 0x01 | 1 rds | [1..returndatasize): returndata | + * f3 | RETURN | | [1..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + instance := create(value, 0x07, 0x79) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`. + function deployDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967IBeaconProxy(0, beacon, salt); + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + instance := create2(value, 0x07, 0x79, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967IBeaconProxy(0, beacon, salt); + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(uint256 value, address beacon, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x07, 0x79)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x07, 0x79, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the ERC1967I beacon proxy. + function initCodeERC1967IBeaconProxy(address beacon) internal pure returns (bytes memory c) { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + mstore(add(c, 0x79), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x59), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x39), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x1d), beacon) + mstore(add(c, 0x09), 0x60573d8160223d3973) + mstore(add(c, 0x99), 0) + mstore(c, 0x79) // Store the length. + mstore(0x40, add(c, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I beacon proxy. + function initCodeHashERC1967IBeaconProxy(address beacon) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(0x40, 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(0x20, 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(0x04, or(shl(160, 0x60573d8160223d3973), shr(96, shl(96, beacon)))) + hash := keccak256(0x07, 0x79) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the ERC1967I beacon proxy, with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967IBeaconProxy( + address beacon, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I BEACON PROXY WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a ERC1967I beacon proxy with `args. + function deployERC1967IBeaconProxy(address beacon, bytes memory args) + internal + returns (address instance) + { + instance = deployERC1967IBeaconProxy(0, beacon, args); + } + + /// @dev Deploys a ERC1967I beacon proxy with `args. + /// Deposits `value` ETH during deployment. + function deployERC1967IBeaconProxy(uint256 value, address beacon, bytes memory args) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + instance := create(value, add(m, 0x16), add(n, 0x7a)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`. + function deployDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967IBeaconProxy(0, beacon, args, salt); + } + + /// @dev Deploys a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967IBeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + instance := create2(value, add(m, 0x16), add(n, 0x7a), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy(address beacon, bytes memory args, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967IBeaconProxy(0, beacon, args, salt); + } + + /// @dev Creates a deterministic ERC1967I beacon proxy with `args` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967IBeaconProxy( + uint256 value, + address beacon, + bytes memory args, + bytes32 salt + ) internal returns (bool alreadyDeployed, address instance) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + let n := mload(args) + pop(staticcall(gas(), 4, add(args, 0x20), n, add(m, 0x90), n)) + mstore(add(m, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(m, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(m, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(m, 0x14), beacon) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + mstore(add(m, gt(n, 0xffa8)), add(0xfe6100573d8160233d3973, shl(56, n))) + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, keccak256(add(m, 0x16), add(n, 0x7a))) + mstore(0x01, shl(96, address())) + mstore(0x15, salt) + instance := keccak256(0x00, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, add(m, 0x16), add(n, 0x7a), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the ERC1967I beacon proxy with `args`. + function initCodeERC1967IBeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes memory c) + { + /// @solidity memory-safe-assembly + assembly { + c := mload(0x40) + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x9a), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x7a), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x5a), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x3a), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x1e), beacon) + mstore(add(c, 0x0a), add(0x6100573d8160233d3973, shl(56, n))) + mstore(add(c, add(n, 0x9a)), 0) + mstore(c, add(n, 0x7a)) // Store the length. + mstore(0x40, add(c, add(n, 0xba))) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the ERC1967I beacon proxy with `args`. + function initCodeHashERC1967IBeaconProxy(address beacon, bytes memory args) + internal + pure + returns (bytes32 hash) + { + /// @solidity memory-safe-assembly + assembly { + let c := mload(0x40) // Cache the free memory pointer. + let n := mload(args) + // Do a out-of-gas revert if `n` is greater than `0xffff - 0x57 = 0xffa8`. + returndatacopy(returndatasize(), returndatasize(), gt(n, 0xffa8)) + for { let i := 0 } lt(i, n) { i := add(i, 0x20) } { + mstore(add(add(c, 0x90), i), mload(add(add(args, 0x20), i))) + } + mstore(add(c, 0x70), 0x3d50545afa361460525736515af43d600060013e6052573d6001fd5b3d6001f3) + mstore(add(c, 0x50), 0x527fa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b3513) + mstore(add(c, 0x30), 0x60195155f3363d3d373d3d363d602036600436635c60da1b60e01b36) + mstore(add(c, 0x14), beacon) + mstore(c, add(0x6100573d8160233d3973, shl(56, n))) + hash := keccak256(add(c, 0x16), add(n, 0x7a)) + } + } + + /// @dev Returns the address of the ERC1967I beacon proxy, with `args` and salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967IBeaconProxy( + address beacon, + bytes memory args, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967IBeaconProxy(beacon, args); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967IBeaconProxy(address instance) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + mstore(args, and(0xffffffffff, sub(extcodesize(instance), 0x57))) // Store the length. + extcodecopy(instance, add(args, 0x20), 0x57, add(mload(args), 0x20)) + mstore(0x40, add(mload(args), add(args, 0x40))) // Allocate memory. + } + } + + /// @dev Equivalent to `argsOnERC1967IBeaconProxy(instance, start, 2 ** 256 - 1)`. + function argsOnERC1967IBeaconProxy(address instance, uint256 start) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + let n := and(0xffffffffff, sub(extcodesize(instance), 0x57)) + let l := sub(n, and(0xffffff, mul(lt(start, n), start))) + extcodecopy(instance, add(args, 0x20), add(start, 0x57), add(l, 0x20)) + mstore(args, mul(sub(n, start), lt(start, n))) // Store the length. + mstore(0x40, add(args, add(0x40, mload(args)))) // Allocate memory. + } + } + + /// @dev Returns a slice of the immutable arguments on `instance` from `start` to `end`. + /// `start` and `end` will be clamped to the range `[0, args.length]`. + /// The `instance` MUST be deployed via the ERC1967I beacon proxy with immutable args functions. + /// Otherwise, the behavior is undefined. + /// Out-of-gas reverts if `instance` does not have any code. + function argsOnERC1967IBeaconProxy(address instance, uint256 start, uint256 end) + internal + view + returns (bytes memory args) + { + /// @solidity memory-safe-assembly + assembly { + args := mload(0x40) + if iszero(lt(end, 0xffff)) { end := 0xffff } + let d := mul(sub(end, start), lt(start, end)) + extcodecopy(instance, args, add(start, 0x37), add(d, 0x20)) + if iszero(and(0xff, mload(add(args, d)))) { + let n := sub(extcodesize(instance), 0x57) + returndatacopy(returndatasize(), returndatasize(), shr(40, n)) + d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n)))) + } + mstore(args, d) // Store the length. + mstore(add(add(args, 0x20), d), 0) // Zeroize the slot after the bytes. + mstore(0x40, add(add(args, 0x40), d)) // Allocate memory. + } + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OTHER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns `address(0)` if the implementation address cannot be determined. + function implementationOf(address instance) internal view returns (address result) { + /// @solidity memory-safe-assembly + assembly { + for { extcodecopy(instance, 0x00, 0x00, 0x57) } 1 {} { + if mload(0x2d) { + // ERC1967I and ERC1967IBeaconProxy detection. + if or( + eq(keccak256(0x00, 0x52), ERC1967I_CODE_HASH), + eq(keccak256(0x00, 0x57), ERC1967I_BEACON_PROXY_CODE_HASH) + ) { + pop(staticcall(gas(), instance, 0x00, 0x01, 0x00, 0x20)) + result := mload(0x0c) + break + } + } + // 0age clone detection. + result := mload(0x0b) + codecopy(0x0b, codesize(), 0x14) // Zeroize the 20 bytes for the address. + if iszero(xor(keccak256(0x00, 0x2c), CLONE_CODE_HASH)) { break } + mstore(0x0b, result) // Restore the zeroized memory. + // CWIA detection. + result := mload(0x0a) + codecopy(0x0a, codesize(), 0x14) // Zeroize the 20 bytes for the address. + if iszero(xor(keccak256(0x00, 0x2d), CWIA_CODE_HASH)) { break } + mstore(0x0a, result) // Restore the zeroized memory. + // PUSH0 clone detection. + result := mload(0x09) + codecopy(0x09, codesize(), 0x14) // Zeroize the 20 bytes for the address. + result := shr(xor(keccak256(0x00, 0x2d), PUSH0_CLONE_CODE_HASH), result) + break + } + result := shr(96, result) + mstore(0x37, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address when a contract with initialization code hash, + /// `hash`, is deployed with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(bytes32 hash, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + /// @solidity memory-safe-assembly + assembly { + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, deployer)) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Requires that `salt` starts with either the zero address or `by`. + function checkStartsWith(bytes32 salt, address by) internal pure { + /// @solidity memory-safe-assembly + assembly { + // If the salt does not start with the zero address or `by`. + if iszero(or(iszero(shr(96, salt)), eq(shr(96, shl(96, by)), shr(96, salt)))) { + mstore(0x00, 0x0c4549ef) // `SaltDoesNotStartWith()`. + revert(0x1c, 0x04) + } + } + } + + /// @dev Returns the `bytes32` at `offset` in `args`, without any bounds checks. + /// To load an address, you can use `address(bytes20(argLoad(args, offset)))`. + function argLoad(bytes memory args, uint256 offset) internal pure returns (bytes32 result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(add(add(args, 0x20), offset)) + } + } +} +contract SoladyFactory { + event Target(address addr); + + function deploySoladyPush(address _implementation) external returns (address split){ + split = LibClone.clone_PUSH0(0, _implementation); + emit Target(split); + } + + function deploySoladyCWIA(address _implementation) external returns (address split){ + split = LibClone.clone(0, _implementation, abi.encodePacked(bytes32(0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7))); + emit Target(split); + } + +} diff --git a/tests/functional/data/sources/SplitsCWIA.sol b/tests/functional/data/sources/SplitsCWIA.sol new file mode 100644 index 0000000000..8897629980 --- /dev/null +++ b/tests/functional/data/sources/SplitsCWIA.sol @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: BSD +pragma solidity ^0.8.4; + +// src/Clone.sol + +/// @title Clone +/// @author zefram.eth, Saw-mon & Natalie +/// @notice Provides helper functions for reading immutable args from calldata +contract Clone { + uint256 private constant ONE_WORD = 0x20; + + /// @notice Reads an immutable arg with type address + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgAddress(uint256 argOffset) + internal + pure + returns (address arg) + { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0x60, calldataload(add(offset, argOffset))) + } + } + + /// @notice Reads an immutable arg with type uint256 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint256(uint256 argOffset) + internal + pure + returns (uint256 arg) + { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := calldataload(add(offset, argOffset)) + } + } + + /// @notice Reads a uint256 array stored in the immutable args. + /// @param argOffset The offset of the arg in the packed data + /// @param arrLen Number of elements in the array + /// @return arr The array + function _getArgUint256Array(uint256 argOffset, uint64 arrLen) + internal + pure + returns (uint256[] memory arr) + { + uint256 offset = _getImmutableArgsOffset() + argOffset; + arr = new uint256[](arrLen); + // solhint-disable-next-line no-inline-assembly + assembly { + let i + arrLen := mul(arrLen, ONE_WORD) + for {} lt(i, arrLen) {} { + let j := add(i, ONE_WORD) + mstore(add(arr, j), calldataload(add(offset, i))) + i := j + } + } + } + + /// @notice Reads an immutable arg with type uint64 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint64(uint256 argOffset) + internal + pure + returns (uint64 arg) + { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0xc0, calldataload(add(offset, argOffset))) + } + } + + /// @notice Reads an immutable arg with type uint8 + /// @param argOffset The offset of the arg in the packed data + /// @return arg The arg value + function _getArgUint8(uint256 argOffset) + internal + pure + returns (uint8 arg) + { + uint256 offset = _getImmutableArgsOffset(); + // solhint-disable-next-line no-inline-assembly + assembly { + arg := shr(0xf8, calldataload(add(offset, argOffset))) + } + } + + /// @return offset The offset of the packed immutable args in calldata + function _getImmutableArgsOffset() internal pure returns (uint256 offset) { + // solhint-disable-next-line no-inline-assembly + assembly { + offset := + sub(calldatasize(), shr(240, calldataload(sub(calldatasize(), 2)))) + } + } +} + +// src/ClonesWithImmutableArgs.sol + +/// @title ClonesWithImmutableArgs +/// @author wighawag, zefram.eth, Saw-mon & Natalie, wminshew +/// @notice Enables creating clone contracts with immutable args +/// @dev extended by will@0xsplits.xyz to add receive() without DELEGECALL & create2 support +/// (h/t WyseNynja https://github.com/wighawag/clones-with-immutable-args/issues/4) +library ClonesWithImmutableArgs { + error CreateFail(); + + uint256 private constant FREE_MEMORY_POINTER_SLOT = 0x40; + uint256 private constant BOOTSTRAP_LENGTH = 0x6f; + uint256 private constant RUNTIME_BASE = 0x65; // BOOTSTRAP_LENGTH - 10 bytes + uint256 private constant ONE_WORD = 0x20; + // = keccak256("ReceiveETH(uint256)") + uint256 private constant RECEIVE_EVENT_SIG = + 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff; + + /// @notice Creates a clone proxy of the implementation contract with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return ptr The ptr to the clone's bytecode + /// @return creationSize The size of the clone to be created + function cloneCreationCode(address implementation, bytes memory data) + internal + pure + returns (uint256 ptr, uint256 creationSize) + { + // unrealistic for memory ptr or data length to exceed 256 bits + // solhint-disable-next-line no-inline-assembly + assembly { + let extraLength := add(mload(data), 2) // +2 bytes for telling how much data there is appended to the call + creationSize := add(extraLength, BOOTSTRAP_LENGTH) + let runSize := sub(creationSize, 0x0a) + + // free memory pointer + ptr := mload(FREE_MEMORY_POINTER_SLOT) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (10 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // 61 runtime | PUSH2 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 offset | PUSH1 offset (o) | o r 0 r | – + // 3d | RETURNDATASIZE | 0 o r 0 r | – + // 39 | CODECOPY | 0 r | [0, runSize): runtime code + // f3 | RETURN | | [0, runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (101 bytes + extraLength) + // ------------------------------------------------------------------------------------------------------------- + + // --- if no calldata, emit event & return w/o `DELEGATECALL` + // 0x000 36 calldatasize cds | - + // 0x001 602f push1 0x2f 0x2f cds | - + // ,=< 0x003 57 jumpi | - + // | 0x004 34 callvalue cv | - + // | 0x005 3d returndatasize 0 cv | - + // | 0x006 52 mstore | [0, 0x20) = cv + // | 0x007 7f9e4a.. push32 0x9e4a.. id | [0, 0x20) = cv + // | 0x028 6020 push1 0x20 0x20 id | [0, 0x20) = cv + // | 0x02a 3d returndatasize 0 0x20 id | [0, 0x20) = cv + // | 0x02b a1 log1 | [0, 0x20) = cv + // | 0x02c 3d returndatasize 0 | [0, 0x20) = cv + // | 0x02d 3d returndatasize 0 0 | [0, 0x20) = cv + // | 0x02e f3 return + // `-> 0x02f 5b jumpdest + + // --- copy calldata to memory --- + // 36 | CALLDATASIZE | cds | – + // 3d | RETURNDATASIZE | 0 cds | – + // 3d | RETURNDATASIZE | 0 0 cds | – + // 37 | CALLDATACOPY | | [0 - cds): calldata + + // --- keep some values in stack --- + // 3d | RETURNDATASIZE | 0 | [0 - cds): calldata + // 3d | RETURNDATASIZE | 0 0 | [0 - cds): calldata + // 3d | RETURNDATASIZE | 0 0 0 | [0 - cds): calldata + // 3d | RETURNDATASIZE | 0 0 0 0 | [0 - cds): calldata + // 61 extra | PUSH2 extra (e) | e 0 0 0 0 | [0 - cds): calldata + + // --- copy extra data to memory --- + // 80 | DUP1 | e e 0 0 0 0 | [0 - cds): calldata + // 60 rb | PUSH1 rb | rb e e 0 0 0 0 | [0 - cds): calldata + // 36 | CALLDATASIZE | cds rb e e 0 0 0 0 | [0 - cds): calldata + // 39 | CODECOPY | e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + + // --- delegate call to the implementation contract --- + // 36 | CALLDATASIZE | cds e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 01 | ADD | cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 3d | RETURNDATASIZE | 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 73 addr | PUSH20 addr | addr 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 5a | GAS | gas addr 0 cds+e 0 0 0 0| [0 - cds): calldata, [cds - cds + e): extraData + // f4 | DELEGATECALL | success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + + // --- copy return data to memory --- + // 3d | RETURNDATASIZE | rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData + // 3e | RETURNDATACOPY | success 0 rds | [0 - rds): returndata, ... the rest might be dirty + + // 60 0x63 | PUSH1 0x63 | 0x63 success | [0 - rds): returndata, ... the rest might be dirty + // 57 | JUMPI | | [0 - rds): returndata, ... the rest might be dirty + + // --- revert --- + // fd | REVERT | | [0 - rds): returndata, ... the rest might be dirty + + // --- return --- + // 5b | JUMPDEST | | [0 - rds): returndata, ... the rest might be dirty + // f3 | RETURN | | [0 - rds): returndata, ... the rest might be dirty + + mstore( + ptr, + or( + hex"6100003d81600a3d39f336602f57343d527f", // 18 bytes + shl(0xe8, runSize) + ) + ) + + mstore( + add(ptr, 0x12), // 0x0 + 0x12 + RECEIVE_EVENT_SIG // 32 bytes + ) + + mstore( + add(ptr, 0x32), // 0x12 + 0x20 + or( + hex"60203da13d3df35b363d3d373d3d3d3d610000806000363936013d73", // 28 bytes + or(shl(0x68, extraLength), shl(0x50, RUNTIME_BASE)) + ) + ) + + mstore( + add(ptr, 0x4e), // 0x32 + 0x1c + shl(0x60, implementation) // 20 bytes + ) + + mstore( + add(ptr, 0x62), // 0x4e + 0x14 + hex"5af43d3d93803e606357fd5bf3" // 13 bytes + ) + + // ------------------------------------------------------------------------------------------------------------- + // APPENDED DATA (Accessible from extcodecopy) + // (but also send as appended data to the delegatecall) + // ------------------------------------------------------------------------------------------------------------- + + let counter := mload(data) + let copyPtr := add(ptr, BOOTSTRAP_LENGTH) + let dataPtr := add(data, ONE_WORD) + + for {} true {} { + if lt(counter, ONE_WORD) { break } + + mstore(copyPtr, mload(dataPtr)) + + copyPtr := add(copyPtr, ONE_WORD) + dataPtr := add(dataPtr, ONE_WORD) + + counter := sub(counter, ONE_WORD) + } + + let mask := shl(mul(0x8, sub(ONE_WORD, counter)), not(0)) + + mstore(copyPtr, and(mload(dataPtr), mask)) + copyPtr := add(copyPtr, counter) + mstore(copyPtr, shl(0xf0, extraLength)) + + // Update free memory pointer + mstore(FREE_MEMORY_POINTER_SLOT, add(ptr, creationSize)) + } + } + + /// @notice Creates a clone proxy of the implementation contract with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param data Encoded immutable args + /// @return instance The address of the created clone + function clone(address implementation, bytes memory data) + internal + returns (address payable instance) + { + (uint256 creationPtr, uint256 creationSize) = + cloneCreationCode(implementation, data); + + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create(0, creationPtr, creationSize) + } + + // if the create failed, the instance address won't be set + if (instance == address(0)) { + revert CreateFail(); + } + } + + /// @notice Creates a clone proxy of the implementation contract with immutable args + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param salt The salt for create2 + /// @param data Encoded immutable args + /// @return instance The address of the created clone + function cloneDeterministic( + address implementation, + bytes32 salt, + bytes memory data + ) + internal + returns (address payable instance) + { + (uint256 creationPtr, uint256 creationSize) = + cloneCreationCode(implementation, data); + + // solhint-disable-next-line no-inline-assembly + assembly { + instance := create2(0, creationPtr, creationSize, salt) + } + + // if the create failed, the instance address won't be set + if (instance == address(0)) { + revert CreateFail(); + } + } + + /// @notice Predicts the address where a deterministic clone of implementation will be deployed + /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length + /// @param implementation The implementation contract to clone + /// @param salt The salt for create2 + /// @param data Encoded immutable args + /// @return predicted The predicted address of the created clone + /// @return exists Whether the clone already exists + function predictDeterministicAddress( + address implementation, + bytes32 salt, + bytes memory data + ) + internal + view + returns (address predicted, bool exists) + { + (uint256 creationPtr, uint256 creationSize) = + cloneCreationCode(implementation, data); + + bytes32 creationHash; + // solhint-disable-next-line no-inline-assembly + assembly { + creationHash := keccak256(creationPtr, creationSize) + } + + predicted = computeAddress(salt, creationHash, address(this)); + exists = predicted.code.length > 0; + } + + /// @dev Returns the address where a contract will be stored if deployed via CREATE2 from a contract located at `deployer`. + function computeAddress( + bytes32 salt, + bytes32 bytecodeHash, + address deployer + ) + internal + pure + returns (address) + { + bytes32 _data = + keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash)); + return address(uint160(uint256(_data))); + } +} + +// src/ExampleClone.sol + +contract ExampleClone is Clone { + function param1() public pure returns (address) { + return _getArgAddress(0); + } + + function param2() public pure returns (uint256) { + return _getArgUint256(20); + } + + function param3() public pure returns (uint64) { + return _getArgUint64(52); + } + + function param4() public pure returns (uint8) { + return _getArgUint8(60); + } +} + +// src/ExampleCloneFactory.sol + +contract SplitsCloneFactory { + using ClonesWithImmutableArgs for address; + + ExampleClone public implementation; + + event Target(address addr); + + constructor(ExampleClone implementation_) { + implementation = implementation_; + } + + function createClone( + address param1, + uint256 param2, + uint64 param3, + uint8 param4 + ) + external + returns (ExampleClone clone) + { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = ExampleClone(address(implementation).clone(data)); + emit Target(address(clone)); + } + + function createDeterministicClone( + address param1, + uint256 param2, + uint64 param3, + uint8 param4, + bytes32 salt + ) + external + returns (ExampleClone clone) + { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + clone = + ExampleClone(address(implementation).cloneDeterministic(salt, data)); + } + + function predictDeterministicCloneAddress( + address param1, + uint256 param2, + uint64 param3, + uint8 param4, + bytes32 salt + ) + external + view + returns (address, bool) + { + bytes memory data = abi.encodePacked(param1, param2, param3, param4); + return address(implementation).predictDeterministicAddress(salt, data); + } +} + diff --git a/tests/functional/data/sources/SudoswapCWIA.sol b/tests/functional/data/sources/SudoswapCWIA.sol new file mode 100644 index 0000000000..1654bbb9fa --- /dev/null +++ b/tests/functional/data/sources/SudoswapCWIA.sol @@ -0,0 +1,2 @@ +2025-01-07T12:32:18.943779Z ERROR foundry_compilers_artifacts_solc::sources: error="/home/thedance/workspace/lssvm2/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": No such file or directory (os error 2) +2025-01-07T12:32:18.953058Z ERROR foundry_compilers_artifacts_solc::sources: error="/home/thedance/workspace/lssvm2/lib/solmate/src/tokens/ERC20.sol": No such file or directory (os error 2) diff --git a/tests/functional/data/sources/VyperFactory.vy b/tests/functional/data/sources/VyperFactory.vy index 4cb7a67721..313f2158f0 100644 --- a/tests/functional/data/sources/VyperFactory.vy +++ b/tests/functional/data/sources/VyperFactory.vy @@ -7,3 +7,9 @@ def create_contract(target: address, _num: uint256) -> address: result: address = create_from_blueprint(target, _num, code_offset=3) log Deployment(result) return result + +@external +def create_proxy(_masterCopy: address)-> address: + result: address= create_forwarder_to(_masterCopy) + log Deployment(result) + return result \ No newline at end of file diff --git a/tests/functional/geth/test_proxy.py b/tests/functional/geth/test_proxy.py index f58e20685b..9ee7f7bae9 100644 --- a/tests/functional/geth/test_proxy.py +++ b/tests/functional/geth/test_proxy.py @@ -1,4 +1,5 @@ from eth_pydantic_types import HexBytes +from eth_utils import to_checksum_address from ape.contracts import ContractContainer from ape_ethereum.proxies import ProxyType @@ -112,3 +113,23 @@ def test_delegate(get_contract_type, geth_contract, owner, ethereum): assert actual is not None assert actual.type == ProxyType.Delegate assert actual.target == target + + +@geth_process_test +def test_sequence(get_contract_type, geth_contract, owner, ethereum): + _type = get_contract_type("SequenceFactory") + contract = ContractContainer(_type) + + target = geth_contract.address + + factory = owner.deploy(contract) + + clones_proxy = factory.deploy(target, 0, sender=owner) + + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + + actual = ethereum.get_proxy_info(proxy_address) + + assert actual is not None + assert actual.type == ProxyType.Sequence + assert actual.target == target diff --git a/tests/functional/test_proxy.py b/tests/functional/test_proxy.py index 3a8bd6bd7f..6e456bad91 100644 --- a/tests/functional/test_proxy.py +++ b/tests/functional/test_proxy.py @@ -1,3 +1,7 @@ +import pytest +from eth_utils import to_checksum_address + +from ape.contracts import ContractContainer from ape_ethereum.proxies import ProxyType """ @@ -5,6 +9,12 @@ """ +@pytest.fixture +def target(vyper_contract_container, owner): + vyper_contract = owner.deploy(vyper_contract_container, 0) + return vyper_contract.address + + def test_minimal_proxy(ethereum, minimal_proxy, chain): actual = ethereum.get_proxy_info(minimal_proxy.address) assert actual is not None @@ -14,3 +24,86 @@ def test_minimal_proxy(ethereum, minimal_proxy, chain): # Show getting the contract using the proxy address. contract = chain.contracts.instance_at(minimal_proxy.address) assert contract.contract_type.abi == [] # No target ABIs; no proxy ABIs either. + + +def test_clones(get_contract_type, owner, ethereum, target): + _type = get_contract_type("ClonesFactory") + contract = ContractContainer(_type) + factory = owner.deploy(contract) + clones_proxy = factory.deployClonesProxy(target, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.Clones + assert actual.target == target + + +def test_CWIA(get_contract_type, owner, ethereum, target): + _type = get_contract_type("CWIA") + contract = ContractContainer(_type) + factory = owner.deploy(contract, target) + clones_proxy = factory.createClone(0, 0, 0, 0, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.CWIA + assert actual.target == target + + +def test_Solady(get_contract_type, owner, ethereum, target): + _type = get_contract_type("SoladyFactory") + contract = ContractContainer(_type) + factory = owner.deploy(contract) + + # test Solady Push proxy + clones_proxy = factory.deploySoladyPush(target, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.SoladyPush0 + assert actual.target == target + + # test Solady CWIA proxy + clones_proxy = factory.deploySoladyCWIA(target, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.SoladyCWIA + assert actual.target == target + + +def test_SplitsCWIA(get_contract_type, owner, ethereum, target): + _type = get_contract_type("SplitsCWIA") + contract = ContractContainer(_type) + factory = owner.deploy(contract, target) + clones_proxy = factory.createClone(0, 0, 0, 0, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.SplitsCWIA + assert actual.target == target + + +def test_OldCWIA(get_contract_type, owner, ethereum, target): + _type = get_contract_type("OldCWIA") + contract = ContractContainer(_type) + contract_instance = owner.deploy(contract) + clones_proxy = contract_instance.clone1(0, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + actual = ethereum.get_proxy_info(contract_instance.address) + assert actual is not None + assert actual.type == ProxyType.Standard + assert actual.target == target + + +def test_Vyper(get_contract_type, owner, ethereum, target): + _type = get_contract_type("VyperFactory") + contract = ContractContainer(_type) + factory = owner.deploy(contract) + clones_proxy = factory.create_proxy(target, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.Vyper + assert actual.target == target From b62a46cedbdffc442e8bc6c446bc20f13785044a Mon Sep 17 00:00:00 2001 From: The Dance Date: Fri, 17 Jan 2025 10:35:05 +0000 Subject: [PATCH 2/4] test: tests for SudoswapCWIA proxy --- .../ethereum/local/SudoswapCWIA.json | 228 + .../functional/data/sources/ClonesFactory.sol | 2 +- .../data/sources/LSSVMPairFactory.sol | 5732 ----------------- tests/functional/data/sources/OldCWIA.sol | 2 +- .../data/sources/SequenceFactory.sol | 1 - .../functional/data/sources/SoladyFactory.sol | 1 - tests/functional/data/sources/SplitsCWIA.sol | 1 - .../functional/data/sources/SudoswapCWIA.sol | 3337 +++++++++- tests/functional/test_proxy.py | 20 +- 9 files changed, 3584 insertions(+), 5740 deletions(-) create mode 100644 tests/functional/data/contracts/ethereum/local/SudoswapCWIA.json delete mode 100644 tests/functional/data/sources/LSSVMPairFactory.sol diff --git a/tests/functional/data/contracts/ethereum/local/SudoswapCWIA.json b/tests/functional/data/contracts/ethereum/local/SudoswapCWIA.json new file mode 100644 index 0000000000..6521ec526c --- /dev/null +++ b/tests/functional/data/contracts/ethereum/local/SudoswapCWIA.json @@ -0,0 +1,228 @@ +{ + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Target", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "contract ILSSVMPairFactoryLike", + "name": "factory", + "type": "address" + }, + { + "internalType": "contract ICurve", + "name": "bondingCurve", + "type": "address" + }, + { + "internalType": "contract IERC1155", + "name": "nft", + "type": "address" + }, + { + "internalType": "uint8", + "name": "poolType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "nftId", + "type": "uint256" + }, + { + "internalType": "contract ERC20", + "name": "token", + "type": "address" + } + ], + "name": "deploycloneERC1155ERC20Pair", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "contract ILSSVMPairFactoryLike", + "name": "factory", + "type": "address" + }, + { + "internalType": "contract ICurve", + "name": "bondingCurve", + "type": "address" + }, + { + "internalType": "contract IERC1155", + "name": "nft", + "type": "address" + }, + { + "internalType": "uint8", + "name": "poolType", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "nftId", + "type": "uint256" + } + ], + "name": "deploycloneERC1155ETHPair", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "contract ILSSVMPairFactoryLike", + "name": "factory", + "type": "address" + }, + { + "internalType": "contract ICurve", + "name": "bondingCurve", + "type": "address" + }, + { + "internalType": "contract IERC721", + "name": "nft", + "type": "address" + }, + { + "internalType": "uint8", + "name": "poolType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "propertyChecker", + "type": "address" + }, + { + "internalType": "contract ERC20", + "name": "token", + "type": "address" + } + ], + "name": "deploycloneERC721ERC20Pair", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, + { + "internalType": "contract ILSSVMPairFactoryLike", + "name": "factory", + "type": "address" + }, + { + "internalType": "contract ICurve", + "name": "bondingCurve", + "type": "address" + }, + { + "internalType": "contract IERC721", + "name": "nft", + "type": "address" + }, + { + "internalType": "uint8", + "name": "poolType", + "type": "uint8" + }, + { + "internalType": "address", + "name": "propertyChecker", + "type": "address" + } + ], + "name": "deploycloneERC721ETHPair", + "outputs": [ + { + "internalType": "address", + "name": "split", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "contractName": "SudoswapCWIAFactory", + "deploymentBytecode": { + "bytecode": "0x6080604052348015600e575f5ffd5b506105668061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80635004b6a81461004e57806352a692d01461007d578063b86a4ae614610090578063cbc78744146100a3575b5f5ffd5b61006161005c36600461033c565b6100b6565b6040516001600160a01b03909116815260200160405180910390f35b61006161008b3660046103cb565b610110565b61006161009e366004610441565b61011f565b6100616100b13660046104bd565b610177565b5f6100c688888888888888610186565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1979650505050505050565b5f6100c6888888888888610211565b5f61012e87878787878761028f565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a19695505050505050565b5f61012e878787878787610211565b5f6040517f609a3d8160093d39f33d3d3d3d363d3d37606560353639366065013d7300000081528860601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528760601b603e8201528660601b60528201528560601b606682015284607a8201538360601b607b8201528260601b608f82015260a3815ff09998505050505050505050565b5f6040517f60923d8160093d39f33d3d3d3d363d3d37605d6035363936605d013d7300000081528760601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528660601b603e8201528560601b60528201528460601b606682015283607a82015382607b820152609b815ff098975050505050505050565b5f6040517f60863d8160093d39f33d3d3d3d363d3d37605160353639366051013d7300000081528760601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528660601b603e8201528560601b60528201528460601b606682015283607a8201538260601b607b820152608f815ff098975050505050505050565b6001600160a01b0381168114610324575f5ffd5b50565b803560ff81168114610337575f5ffd5b919050565b5f5f5f5f5f5f5f60e0888a031215610352575f5ffd5b873561035d81610310565b9650602088013561036d81610310565b9550604088013561037d81610310565b9450606088013561038d81610310565b935061039b60808901610327565b925060a08801356103ab81610310565b915060c08801356103bb81610310565b8091505092959891949750929550565b5f5f5f5f5f5f5f60e0888a0312156103e1575f5ffd5b87356103ec81610310565b965060208801356103fc81610310565b9550604088013561040c81610310565b9450606088013561041c81610310565b935061042a60808901610327565b925060a0880135915060c08801356103bb81610310565b5f5f5f5f5f5f60c08789031215610456575f5ffd5b863561046181610310565b9550602087013561047181610310565b9450604087013561048181610310565b9350606087013561049181610310565b925061049f60808801610327565b915060a08701356104af81610310565b809150509295509295509295565b5f5f5f5f5f5f60c087890312156104d2575f5ffd5b86356104dd81610310565b955060208701356104ed81610310565b945060408701356104fd81610310565b9350606087013561050d81610310565b925061051b60808801610327565b9598949750929591949360a09092013592505056fea26469706673582212205390c18c8017e016f77e86d6d891c49420f77f2a3ba381307ba34d2d4fa7b24564736f6c634300081c0033" + }, + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "methodIdentifiers": { + "deploycloneERC1155ERC20Pair(address,address,address,address,uint8,uint256,address)": "0x52a692d0", + "deploycloneERC1155ETHPair(address,address,address,address,uint8,uint256)": "0xcbc78744", + "deploycloneERC721ERC20Pair(address,address,address,address,uint8,address,address)": "0x5004b6a8", + "deploycloneERC721ETHPair(address,address,address,address,uint8,address)": "0xb86a4ae6" + }, + "runtimeBytecode": { + "bytecode": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80635004b6a81461004e57806352a692d01461007d578063b86a4ae614610090578063cbc78744146100a3575b5f5ffd5b61006161005c36600461033c565b6100b6565b6040516001600160a01b03909116815260200160405180910390f35b61006161008b3660046103cb565b610110565b61006161009e366004610441565b61011f565b6100616100b13660046104bd565b610177565b5f6100c688888888888888610186565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a1979650505050505050565b5f6100c6888888888888610211565b5f61012e87878787878761028f565b6040516001600160a01b03821681529091507ff3a1fccdf4778ea6c44fd680f7bdbf5d53a02733614e6e82354bfcb238eb611a9060200160405180910390a19695505050505050565b5f61012e878787878787610211565b5f6040517f609a3d8160093d39f33d3d3d3d363d3d37606560353639366065013d7300000081528860601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528760601b603e8201528660601b60528201528560601b606682015284607a8201538360601b607b8201528260601b608f82015260a3815ff09998505050505050505050565b5f6040517f60923d8160093d39f33d3d3d3d363d3d37605d6035363936605d013d7300000081528760601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528660601b603e8201528560601b60528201528460601b606682015283607a82015382607b820152609b815ff098975050505050505050565b5f6040517f60863d8160093d39f33d3d3d3d363d3d37605160353639366051013d7300000081528760601b601d8201526c5af43d3d93803e603357fd5bf360981b60318201528660601b603e8201528560601b60528201528460601b606682015283607a8201538260601b607b820152608f815ff098975050505050505050565b6001600160a01b0381168114610324575f5ffd5b50565b803560ff81168114610337575f5ffd5b919050565b5f5f5f5f5f5f5f60e0888a031215610352575f5ffd5b873561035d81610310565b9650602088013561036d81610310565b9550604088013561037d81610310565b9450606088013561038d81610310565b935061039b60808901610327565b925060a08801356103ab81610310565b915060c08801356103bb81610310565b8091505092959891949750929550565b5f5f5f5f5f5f5f60e0888a0312156103e1575f5ffd5b87356103ec81610310565b965060208801356103fc81610310565b9550604088013561040c81610310565b9450606088013561041c81610310565b935061042a60808901610327565b925060a0880135915060c08801356103bb81610310565b5f5f5f5f5f5f60c08789031215610456575f5ffd5b863561046181610310565b9550602087013561047181610310565b9450604087013561048181610310565b9350606087013561049181610310565b925061049f60808801610327565b915060a08701356104af81610310565b809150509295509295509295565b5f5f5f5f5f5f60c087890312156104d2575f5ffd5b86356104dd81610310565b955060208701356104ed81610310565b945060408701356104fd81610310565b9350606087013561050d81610310565b925061051b60808801610327565b9598949750929591949360a09092013592505056fea26469706673582212205390c18c8017e016f77e86d6d891c49420f77f2a3ba381307ba34d2d4fa7b24564736f6c634300081c0033" + }, + "sourceId": "tests/functional/data/sources/SudoswapCWIA.sol", + "sourcemap": "138372:1720:0:-:0;;;;;;;;;;;;;;;;;;;", + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } +} diff --git a/tests/functional/data/sources/ClonesFactory.sol b/tests/functional/data/sources/ClonesFactory.sol index 3176a9bd64..6baa397bc0 100644 --- a/tests/functional/data/sources/ClonesFactory.sol +++ b/tests/functional/data/sources/ClonesFactory.sol @@ -176,4 +176,4 @@ contract ClonesFactory { split = Clones.clone(_implementation); emit Target(split); } -} \ No newline at end of file +} diff --git a/tests/functional/data/sources/LSSVMPairFactory.sol b/tests/functional/data/sources/LSSVMPairFactory.sol deleted file mode 100644 index 9d7da53a44..0000000000 --- a/tests/functional/data/sources/LSSVMPairFactory.sol +++ /dev/null @@ -1,5732 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity >=0.8.0 ^0.8.0 ^0.8.1 ^0.8.2 ^0.8.4; - -// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol - -// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) - -/** - * @title ERC721 token receiver interface - * @dev Interface for any contract that wants to support safeTransfers - * from ERC721 asset contracts. - */ -interface IERC721Receiver { - /** - * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} - * by `operator` from `from`, this function is called. - * - * It must return its Solidity selector to confirm the token transfer. - * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. - * - * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. - */ - function onERC721Received( - address operator, - address from, - uint256 tokenId, - bytes calldata data - ) external returns (bytes4); -} - -// lib/openzeppelin-contracts/contracts/utils/Address.sol - -// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. - * - * _Available since v4.8._ - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata, - string memory errorMessage - ) internal view returns (bytes memory) { - if (success) { - if (returndata.length == 0) { - // only check isContract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - require(isContract(target), "Address: call to non-contract"); - } - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - /** - * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - function _revert(bytes memory returndata, string memory errorMessage) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } -} - -// lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -// lib/openzeppelin-contracts-upgradeable/contracts/access/IAccessControlUpgradeable.sol - -// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) - -/** - * @dev External interface of AccessControl declared to support ERC165 detection. - */ -interface IAccessControlUpgradeable { - /** - * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` - * - * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite - * {RoleAdminChanged} not being emitted signaling this. - * - * _Available since v3.1._ - */ - event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); - - /** - * @dev Emitted when `account` is granted `role`. - * - * `sender` is the account that originated the contract call, an admin role - * bearer except when using {AccessControl-_setupRole}. - */ - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); - - /** - * @dev Emitted when `account` is revoked `role`. - * - * `sender` is the account that originated the contract call: - * - if using `revokeRole`, it is the admin role bearer - * - if using `renounceRole`, it is the role bearer (i.e. `account`) - */ - event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); - - /** - * @dev Returns `true` if `account` has been granted `role`. - */ - function hasRole(bytes32 role, address account) external view returns (bool); - - /** - * @dev Returns the admin role that controls `role`. See {grantRole} and - * {revokeRole}. - * - * To change a role's admin, use {AccessControl-_setRoleAdmin}. - */ - function getRoleAdmin(bytes32 role) external view returns (bytes32); - - /** - * @dev Grants `role` to `account`. - * - * If `account` had not been already granted `role`, emits a {RoleGranted} - * event. - * - * Requirements: - * - * - the caller must have ``role``'s admin role. - */ - function grantRole(bytes32 role, address account) external; - - /** - * @dev Revokes `role` from `account`. - * - * If `account` had been granted `role`, emits a {RoleRevoked} event. - * - * Requirements: - * - * - the caller must have ``role``'s admin role. - */ - function revokeRole(bytes32 role, address account) external; - - /** - * @dev Revokes `role` from the calling account. - * - * Roles are often managed via {grantRole} and {revokeRole}: this function's - * purpose is to provide a mechanism for accounts to lose their privileges - * if they are compromised (such as when a trusted device is misplaced). - * - * If the calling account had been granted `role`, emits a {RoleRevoked} - * event. - * - * Requirements: - * - * - the caller must be `account`. - */ - function renounceRole(bytes32 role, address account) external; -} - -// lib/openzeppelin-contracts-upgradeable/contracts/utils/AddressUpgradeable.sol - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -/** - * @dev Collection of functions related to the address type - */ -library AddressUpgradeable { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResultFromTarget(target, success, returndata, errorMessage); - } - - /** - * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling - * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. - * - * _Available since v4.8._ - */ - function verifyCallResultFromTarget( - address target, - bool success, - bytes memory returndata, - string memory errorMessage - ) internal view returns (bytes memory) { - if (success) { - if (returndata.length == 0) { - // only check isContract if the call was successful and the return data is empty - // otherwise we already know that it was a contract - require(isContract(target), "Address: call to non-contract"); - } - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - /** - * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason or using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - _revert(returndata, errorMessage); - } - } - - function _revert(bytes memory returndata, string memory errorMessage) private pure { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } -} - -// lib/solmate/src/auth/Owned.sol - -/// @notice Simple single owner authorization mixin. -/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) -abstract contract Owned { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - event OwnershipTransferred(address indexed user, address indexed newOwner); - - /*////////////////////////////////////////////////////////////// - OWNERSHIP STORAGE - //////////////////////////////////////////////////////////////*/ - - address public owner; - - modifier onlyOwner() virtual { - require(msg.sender == owner, "UNAUTHORIZED"); - - _; - } - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - constructor(address _owner) { - owner = _owner; - - emit OwnershipTransferred(address(0), _owner); - } - - /*////////////////////////////////////////////////////////////// - OWNERSHIP LOGIC - //////////////////////////////////////////////////////////////*/ - - function transferOwnership(address newOwner) public virtual onlyOwner { - owner = newOwner; - - emit OwnershipTransferred(msg.sender, newOwner); - } -} - -// lib/solmate/src/tokens/ERC20.sol - -/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. -/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) -/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) -/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. -abstract contract ERC20 { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - event Transfer(address indexed from, address indexed to, uint256 amount); - - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /*////////////////////////////////////////////////////////////// - METADATA STORAGE - //////////////////////////////////////////////////////////////*/ - - string public name; - - string public symbol; - - uint8 public immutable decimals; - - /*////////////////////////////////////////////////////////////// - ERC20 STORAGE - //////////////////////////////////////////////////////////////*/ - - uint256 public totalSupply; - - mapping(address => uint256) public balanceOf; - - mapping(address => mapping(address => uint256)) public allowance; - - /*////////////////////////////////////////////////////////////// - EIP-2612 STORAGE - //////////////////////////////////////////////////////////////*/ - - uint256 internal immutable INITIAL_CHAIN_ID; - - bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; - - mapping(address => uint256) public nonces; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - constructor( - string memory _name, - string memory _symbol, - uint8 _decimals - ) { - name = _name; - symbol = _symbol; - decimals = _decimals; - - INITIAL_CHAIN_ID = block.chainid; - INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); - } - - /*////////////////////////////////////////////////////////////// - ERC20 LOGIC - //////////////////////////////////////////////////////////////*/ - - function approve(address spender, uint256 amount) public virtual returns (bool) { - allowance[msg.sender][spender] = amount; - - emit Approval(msg.sender, spender, amount); - - return true; - } - - function transfer(address to, uint256 amount) public virtual returns (bool) { - balanceOf[msg.sender] -= amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(msg.sender, to, amount); - - return true; - } - - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual returns (bool) { - uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. - - if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; - - balanceOf[from] -= amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(from, to, amount); - - return true; - } - - /*////////////////////////////////////////////////////////////// - EIP-2612 LOGIC - //////////////////////////////////////////////////////////////*/ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); - - // Unchecked because the only math done is incrementing - // the owner's nonce which cannot realistically overflow. - unchecked { - address recoveredAddress = ecrecover( - keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - keccak256( - "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" - ), - owner, - spender, - value, - nonces[owner]++, - deadline - ) - ) - ) - ), - v, - r, - s - ); - - require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); - - allowance[recoveredAddress][spender] = value; - } - - emit Approval(owner, spender, value); - } - - function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { - return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); - } - - function computeDomainSeparator() internal view virtual returns (bytes32) { - return - keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name)), - keccak256("1"), - block.chainid, - address(this) - ) - ); - } - - /*////////////////////////////////////////////////////////////// - INTERNAL MINT/BURN LOGIC - //////////////////////////////////////////////////////////////*/ - - function _mint(address to, uint256 amount) internal virtual { - totalSupply += amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(address(0), to, amount); - } - - function _burn(address from, uint256 amount) internal virtual { - balanceOf[from] -= amount; - - // Cannot underflow because a user's balance - // will never be larger than the total supply. - unchecked { - totalSupply -= amount; - } - - emit Transfer(from, address(0), amount); - } -} - -// src/bonding-curves/CurveErrorCodes.sol - -contract CurveErrorCodes { - enum Error { - OK, // No error - INVALID_NUMITEMS, // The numItem value is 0 - SPOT_PRICE_OVERFLOW, // The updated spot price doesn't fit into 128 bits - DELTA_OVERFLOW, // The updated delta doesn't fit into 128 bits - SPOT_PRICE_UNDERFLOW, // The updated spot price goes too low - AUCTION_ENDED // The auction has ended - } -} - -// src/lib/IOwnershipTransferReceiver.sol - -interface IOwnershipTransferReceiver { - function onOwnershipTransferred(address oldOwner, bytes memory data) external payable; -} - -// src/property-checking/IPropertyChecker.sol - -interface IPropertyChecker { - function hasProperties(uint256[] calldata ids, bytes calldata params) external returns (bool); -} - -// src/royalty-auth/IArtBlocks.sol - -/** - * @dev Art Blocks nfts - */ -interface IArtBlocks { - // document getter function of public variable - function admin() external view returns (address); -} - -// src/royalty-auth/IDigitalax.sol - -/** - * @dev Digitalax nfts - */ -interface IDigitalax { - function accessControls() external view returns (address); -} - -/** - * @dev Digitalax Access Controls Simple - */ -interface IDigitalaxAccessControls { - function hasAdminRole(address _account) external view returns (bool); -} - -// src/royalty-auth/IFoundation.sol - -interface IFoundation { - /* - * bytes4(keccak256('getFees(uint256)')) == 0xd5a06d4c - * - * => 0xd5a06d4c = 0xd5a06d4c - */ - function getFees(uint256 tokenId) external view returns (address payable[] memory, uint256[] memory); -} - -interface IFoundationTreasuryNode { - function getFoundationTreasury() external view returns (address payable); -} - -interface IFoundationTreasury { - function isAdmin(address account) external view returns (bool); -} - -// src/royalty-auth/INiftyGateway.sol - -/** - * @dev Nifty builder instance - */ -interface INiftyBuilderInstance { - function niftyRegistryContract() external view returns (address); -} - -/** - * @dev Nifty registry - */ -interface INiftyRegistry { - /** - * @dev function to see if sending key is valid - */ - function isValidNiftySender(address sending_key) external view returns (bool); -} - -// src/settings/ISettings.sol - -interface ISettings { - struct PairInfo { - address prevOwner; - uint96 unlockTime; - address prevFeeRecipient; - } - - function getFeeSplitBps() external pure returns (uint64); - - function getRoyaltyInfo(address pairAddress) external view returns (bool, uint96); - - function settingsFeeRecipient() external returns (address payable); - - function getPrevFeeRecipientForPair(address pairAddress) external returns (address); -} - -// lib/libraries-solidity/contracts/access/IAdminControl.sol - -/// @author: manifold.xyz - -/** - * @dev Interface for admin control - */ -interface IAdminControl is IERC165 { - - event AdminApproved(address indexed account, address indexed sender); - event AdminRevoked(address indexed account, address indexed sender); - - /** - * @dev gets address of all admins - */ - function getAdmins() external view returns (address[] memory); - - /** - * @dev add an admin. Can only be called by contract owner. - */ - function approveAdmin(address admin) external; - - /** - * @dev remove an admin. Can only be called by contract owner. - */ - function revokeAdmin(address admin) external; - - /** - * @dev checks whether or not given address is an admin - * Returns True if they are - */ - function isAdmin(address admin) external view returns (bool); - -} - -// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol - -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol) - -/** - * @dev Required interface of an ERC1155 compliant contract, as defined in the - * https://eips.ethereum.org/EIPS/eip-1155[EIP]. - * - * _Available since v3.1._ - */ -interface IERC1155 is IERC165 { - /** - * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. - */ - event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); - - /** - * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all - * transfers. - */ - event TransferBatch( - address indexed operator, - address indexed from, - address indexed to, - uint256[] ids, - uint256[] values - ); - - /** - * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - * `approved`. - */ - event ApprovalForAll(address indexed account, address indexed operator, bool approved); - - /** - * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. - * - * If an {URI} event was emitted for `id`, the standard - * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value - * returned by {IERC1155MetadataURI-uri}. - */ - event URI(string value, uint256 indexed id); - - /** - * @dev Returns the amount of tokens of token type `id` owned by `account`. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function balanceOf(address account, uint256 id) external view returns (uint256); - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. - * - * Requirements: - * - * - `accounts` and `ids` must have the same length. - */ - function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) - external - view - returns (uint256[] memory); - - /** - * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, - * - * Emits an {ApprovalForAll} event. - * - * Requirements: - * - * - `operator` cannot be the caller. - */ - function setApprovalForAll(address operator, bool approved) external; - - /** - * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. - * - * See {setApprovalForAll}. - */ - function isApprovedForAll(address account, address operator) external view returns (bool); - - /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. - * - * Emits a {TransferSingle} event. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. - * - `from` must have a balance of tokens of type `id` of at least `amount`. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes calldata data - ) external; - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. - * - * Emits a {TransferBatch} event. - * - * Requirements: - * - * - `ids` and `amounts` must have the same length. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the - * acceptance magic value. - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] calldata ids, - uint256[] calldata amounts, - bytes calldata data - ) external; -} - -// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol - -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) - -/** - * @dev _Available since v3.1._ - */ -interface IERC1155Receiver is IERC165 { - /** - * @dev Handles the receipt of a single ERC1155 token type. This function is - * called at the end of a `safeTransferFrom` after the balance has been updated. - * - * NOTE: To accept the transfer, this must return - * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` - * (i.e. 0xf23a6e61, or its own function selector). - * - * @param operator The address which initiated the transfer (i.e. msg.sender) - * @param from The address which previously owned the token - * @param id The ID of the token being transferred - * @param value The amount of tokens being transferred - * @param data Additional data with no specified format - * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed - */ - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external returns (bytes4); - - /** - * @dev Handles the receipt of a multiple ERC1155 token types. This function - * is called at the end of a `safeBatchTransferFrom` after the balances have - * been updated. - * - * NOTE: To accept the transfer(s), this must return - * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` - * (i.e. 0xbc197c81, or its own function selector). - * - * @param operator The address which initiated the batch transfer (i.e. msg.sender) - * @param from The address which previously owned the token - * @param ids An array containing ids of each token being transferred (order and length must match values array) - * @param values An array containing amounts of each token being transferred (order and length must match ids array) - * @param data Additional data with no specified format - * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed - */ - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external returns (bytes4); -} - -// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol - -// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) - -/** - * @dev Required interface of an ERC721 compliant contract. - */ -interface IERC721 is IERC165 { - /** - * @dev Emitted when `tokenId` token is transferred from `from` to `to`. - */ - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. - */ - event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. - */ - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - /** - * @dev Returns the number of tokens in ``owner``'s account. - */ - function balanceOf(address owner) external view returns (uint256 balance); - - /** - * @dev Returns the owner of the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function ownerOf(uint256 tokenId) external view returns (address owner); - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) external; - - /** - * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients - * are aware of the ERC721 protocol to prevent tokens from being forever locked. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * - * Emits a {Transfer} event. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Transfers `tokenId` token from `from` to `to`. - * - * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 - * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must - * understand this adds an external call which potentially creates a reentrancy vulnerability. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. - * - * Emits a {Transfer} event. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external; - - /** - * @dev Gives permission to `to` to transfer `tokenId` token to another account. - * The approval is cleared when the token is transferred. - * - * Only a single account can be approved at a time, so approving the zero address clears previous approvals. - * - * Requirements: - * - * - The caller must own the token or be an approved operator. - * - `tokenId` must exist. - * - * Emits an {Approval} event. - */ - function approve(address to, uint256 tokenId) external; - - /** - * @dev Approve or remove `operator` as an operator for the caller. - * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {ApprovalForAll} event. - */ - function setApprovalForAll(address operator, bool _approved) external; - - /** - * @dev Returns the account approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getApproved(uint256 tokenId) external view returns (address operator); - - /** - * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. - * - * See {setApprovalForAll} - */ - function isApprovedForAll(address owner, address operator) external view returns (bool); -} - -// lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol - -// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol) - -/** - * @dev Implementation of the {IERC721Receiver} interface. - * - * Accepts all token transfers. - * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. - */ -contract ERC721Holder is IERC721Receiver { - /** - * @dev See {IERC721Receiver-onERC721Received}. - * - * Always returns `IERC721Receiver.onERC721Received.selector`. - */ - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public virtual override returns (bytes4) { - return this.onERC721Received.selector; - } -} - -// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) - -/** - * @dev Implementation of the {IERC165} interface. - * - * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check - * for the additional interface id that will be supported. For example: - * - * ```solidity - * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); - * } - * ``` - * - * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. - */ -abstract contract ERC165 is IERC165 { - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC165).interfaceId; - } -} - -// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol - -// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol) - -/** - * @dev Library used to query support of an interface declared via {IERC165}. - * - * Note that these functions return the actual result of the query: they do not - * `revert` if an interface is not supported. It is up to the caller to decide - * what to do in these cases. - */ -library ERC165Checker { - // As per the EIP-165 spec, no interface should ever match 0xffffffff - bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; - - /** - * @dev Returns true if `account` supports the {IERC165} interface. - */ - function supportsERC165(address account) internal view returns (bool) { - // Any contract that implements ERC165 must explicitly indicate support of - // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid - return - supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && - !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID); - } - - /** - * @dev Returns true if `account` supports the interface defined by - * `interfaceId`. Support for {IERC165} itself is queried automatically. - * - * See {IERC165-supportsInterface}. - */ - function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { - // query support of both ERC165 as per the spec and support of _interfaceId - return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); - } - - /** - * @dev Returns a boolean array where each value corresponds to the - * interfaces passed in and whether they're supported or not. This allows - * you to batch check interfaces for a contract where your expectation - * is that some interfaces may not be supported. - * - * See {IERC165-supportsInterface}. - * - * _Available since v3.4._ - */ - function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) - internal - view - returns (bool[] memory) - { - // an array of booleans corresponding to interfaceIds and whether they're supported or not - bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); - - // query support of ERC165 itself - if (supportsERC165(account)) { - // query support of each interface in interfaceIds - for (uint256 i = 0; i < interfaceIds.length; i++) { - interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); - } - } - - return interfaceIdsSupported; - } - - /** - * @dev Returns true if `account` supports all the interfaces defined in - * `interfaceIds`. Support for {IERC165} itself is queried automatically. - * - * Batch-querying can lead to gas savings by skipping repeated checks for - * {IERC165} support. - * - * See {IERC165-supportsInterface}. - */ - function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { - // query support of ERC165 itself - if (!supportsERC165(account)) { - return false; - } - - // query support of each interface in interfaceIds - for (uint256 i = 0; i < interfaceIds.length; i++) { - if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { - return false; - } - } - - // all interfaces supported - return true; - } - - /** - * @notice Query if a contract implements an interface, does not check ERC165 support - * @param account The address of the contract to query for support of an interface - * @param interfaceId The interface identifier, as specified in ERC-165 - * @return true if the contract at account indicates support of the interface with - * identifier interfaceId, false otherwise - * @dev Assumes that account contains a contract that supports ERC165, otherwise - * the behavior of this method is undefined. This precondition can be checked - * with {supportsERC165}. - * Interface identification is specified in ERC-165. - */ - function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { - // prepare call - bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); - - // perform static call - bool success; - uint256 returnSize; - uint256 returnValue; - assembly { - success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) - returnSize := returndatasize() - returnValue := mload(0x00) - } - - return success && returnSize >= 0x20 && returnValue > 0; - } -} - -// lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol - -// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol) - -/** - * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed - * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an - * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer - * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. - * - * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be - * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in - * case an upgrade adds a module that needs to be initialized. - * - * For example: - * - * [.hljs-theme-light.nopadding] - * ``` - * contract MyToken is ERC20Upgradeable { - * function initialize() initializer public { - * __ERC20_init("MyToken", "MTK"); - * } - * } - * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { - * function initializeV2() reinitializer(2) public { - * __ERC20Permit_init("MyToken"); - * } - * } - * ``` - * - * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as - * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. - * - * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure - * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. - * - * [CAUTION] - * ==== - * Avoid leaving a contract uninitialized. - * - * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation - * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke - * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: - * - * [.hljs-theme-light.nopadding] - * ``` - * /// @custom:oz-upgrades-unsafe-allow constructor - * constructor() { - * _disableInitializers(); - * } - * ``` - * ==== - */ -abstract contract Initializable { - /** - * @dev Indicates that the contract has been initialized. - * @custom:oz-retyped-from bool - */ - uint8 private _initialized; - - /** - * @dev Indicates that the contract is in the process of being initialized. - */ - bool private _initializing; - - /** - * @dev Triggered when the contract has been initialized or reinitialized. - */ - event Initialized(uint8 version); - - /** - * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, - * `onlyInitializing` functions can be used to initialize parent contracts. - * - * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a - * constructor. - * - * Emits an {Initialized} event. - */ - modifier initializer() { - bool isTopLevelCall = !_initializing; - require( - (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1), - "Initializable: contract is already initialized" - ); - _initialized = 1; - if (isTopLevelCall) { - _initializing = true; - } - _; - if (isTopLevelCall) { - _initializing = false; - emit Initialized(1); - } - } - - /** - * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the - * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be - * used to initialize parent contracts. - * - * A reinitializer may be used after the original initialization step. This is essential to configure modules that - * are added through upgrades and that require initialization. - * - * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` - * cannot be nested. If one is invoked in the context of another, execution will revert. - * - * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in - * a contract, executing them in the right order is up to the developer or operator. - * - * WARNING: setting the version to 255 will prevent any future reinitialization. - * - * Emits an {Initialized} event. - */ - modifier reinitializer(uint8 version) { - require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); - _initialized = version; - _initializing = true; - _; - _initializing = false; - emit Initialized(version); - } - - /** - * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the - * {initializer} and {reinitializer} modifiers, directly or indirectly. - */ - modifier onlyInitializing() { - require(_initializing, "Initializable: contract is not initializing"); - _; - } - - /** - * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. - * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized - * to any version. It is recommended to use this to lock implementation contracts that are designed to be called - * through proxies. - * - * Emits an {Initialized} event the first time it is successfully executed. - */ - function _disableInitializers() internal virtual { - require(!_initializing, "Initializable: contract is initializing"); - if (_initialized != type(uint8).max) { - _initialized = type(uint8).max; - emit Initialized(type(uint8).max); - } - } - - /** - * @dev Internal function that returns the initialized version. Returns `_initialized` - */ - function _getInitializedVersion() internal view returns (uint8) { - return _initialized; - } - - /** - * @dev Internal function that returns the initialized version. Returns `_initializing` - */ - function _isInitializing() internal view returns (bool) { - return _initializing; - } -} - -// lib/royalty-registry-solidity/contracts/IRoyaltyEngineV1.sol - -/// @author: manifold.xyz - -/** - * @dev Lookup engine interface - */ -interface IRoyaltyEngineV1 is IERC165 { - /** - * Get the royalty for a given token (address, id) and value amount. Does not cache the bps/amounts. Caches the spec for a given token address - * - * @param tokenAddress - The address of the token - * @param tokenId - The id of the token - * @param value - The value you wish to get the royalty of - * - * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get - */ - function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value) - external - returns (address payable[] memory recipients, uint256[] memory amounts); - - /** - * View only version of getRoyalty - * - * @param tokenAddress - The address of the token - * @param tokenId - The id of the token - * @param value - The value you wish to get the royalty of - * - * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get - */ - function getRoyaltyView(address tokenAddress, uint256 tokenId, uint256 value) - external - view - returns (address payable[] memory recipients, uint256[] memory amounts); -} - -// lib/solmate/src/utils/SafeTransferLib.sol - -/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. -/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) -/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. -/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. -library SafeTransferLib { - /*////////////////////////////////////////////////////////////// - ETH OPERATIONS - //////////////////////////////////////////////////////////////*/ - - function safeTransferETH(address to, uint256 amount) internal { - bool success; - - /// @solidity memory-safe-assembly - assembly { - // Transfer the ETH and store if it succeeded or not. - success := call(gas(), to, amount, 0, 0, 0, 0) - } - - require(success, "ETH_TRANSFER_FAILED"); - } - - /*////////////////////////////////////////////////////////////// - ERC20 OPERATIONS - //////////////////////////////////////////////////////////////*/ - - function safeTransferFrom( - ERC20 token, - address from, - address to, - uint256 amount - ) internal { - bool success; - - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - let freeMemoryPointer := mload(0x40) - - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. - mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. - mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. - - success := and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), - // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the or() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) - ) - } - - require(success, "TRANSFER_FROM_FAILED"); - } - - function safeTransfer( - ERC20 token, - address to, - uint256 amount - ) internal { - bool success; - - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - let freeMemoryPointer := mload(0x40) - - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. - mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. - - success := and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), - // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the or() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) - ) - } - - require(success, "TRANSFER_FAILED"); - } - - function safeApprove( - ERC20 token, - address to, - uint256 amount - ) internal { - bool success; - - /// @solidity memory-safe-assembly - assembly { - // Get a pointer to some free memory. - let freeMemoryPointer := mload(0x40) - - // Write the abi-encoded calldata into memory, beginning with the function selector. - mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. - mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. - - success := and( - // Set success to whether the call reverted, if not we check it either - // returned exactly 1 (can't just be non-zero data), or had no return data. - or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), - // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. - // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. - // Counterintuitively, this call must be positioned second to the or() call in the - // surrounding and() call or else returndatasize() will be zero during the computation. - call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) - ) - } - - require(success, "APPROVE_FAILED"); - } -} - -// src/bonding-curves/ICurve.sol - -interface ICurve { - /** - * @notice Validates if a delta value is valid for the curve. The criteria for - * validity can be different for each type of curve, for instance ExponentialCurve - * requires delta to be greater than 1. - * @param delta The delta value to be validated - * @return valid True if delta is valid, false otherwise - */ - function validateDelta(uint128 delta) external pure returns (bool valid); - - /** - * @notice Validates if a new spot price is valid for the curve. Spot price is generally assumed to be the immediate sell price of 1 NFT to the pool, in units of the pool's paired token. - * @param newSpotPrice The new spot price to be set - * @return valid True if the new spot price is valid, false otherwise - */ - function validateSpotPrice(uint128 newSpotPrice) external view returns (bool valid); - - /** - * @notice Given the current state of the pair and the trade, computes how much the user - * should pay to purchase an NFT from the pair, the new spot price, and other values. - * @param spotPrice The current selling spot price of the pair, in tokens - * @param delta The delta parameter of the pair, what it means depends on the curve - * @param numItems The number of NFTs the user is buying from the pair - * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals - * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals - * @return error Any math calculation errors, only Error.OK means the returned values are valid - * @return newSpotPrice The updated selling spot price, in tokens - * @return newDelta The updated delta, used to parameterize the bonding curve - * @return inputValue The amount that the user should pay, in tokens - * @return tradeFee The amount that is sent to the trade fee recipient - * @return protocolFee The amount of fee to send to the protocol, in tokens - */ - function getBuyInfo( - uint128 spotPrice, - uint128 delta, - uint256 numItems, - uint256 feeMultiplier, - uint256 protocolFeeMultiplier - ) - external - view - returns ( - CurveErrorCodes.Error error, - uint128 newSpotPrice, - uint128 newDelta, - uint256 inputValue, - uint256 tradeFee, - uint256 protocolFee - ); - - /** - * @notice Given the current state of the pair and the trade, computes how much the user - * should receive when selling NFTs to the pair, the new spot price, and other values. - * @param spotPrice The current selling spot price of the pair, in tokens - * @param delta The delta parameter of the pair, what it means depends on the curve - * @param numItems The number of NFTs the user is selling to the pair - * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals - * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals - * @return error Any math calculation errors, only Error.OK means the returned values are valid - * @return newSpotPrice The updated selling spot price, in tokens - * @return newDelta The updated delta, used to parameterize the bonding curve - * @return outputValue The amount that the user should receive, in tokens - * @return tradeFee The amount that is sent to the trade fee recipient - * @return protocolFee The amount of fee to send to the protocol, in tokens - */ - function getSellInfo( - uint128 spotPrice, - uint128 delta, - uint256 numItems, - uint256 feeMultiplier, - uint256 protocolFeeMultiplier - ) - external - view - returns ( - CurveErrorCodes.Error error, - uint128 newSpotPrice, - uint128 newDelta, - uint256 outputValue, - uint256 tradeFee, - uint256 protocolFee - ); -} - -// lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol - -// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) - -/** - * @dev Provides information about the current execution context, including the - * sender of the transaction and its data. While these are generally available - * via msg.sender and msg.data, they should not be accessed in such a direct - * manner, since when dealing with meta-transactions the account sending and - * paying for execution may not be the actual sender (as far as an application - * is concerned). - * - * This contract is only required for intermediate, library-like contracts. - */ -abstract contract ContextUpgradeable is Initializable { - function __Context_init() internal onlyInitializing { - } - - function __Context_init_unchained() internal onlyInitializing { - } - function _msgSender() internal view virtual returns (address) { - return msg.sender; - } - - function _msgData() internal view virtual returns (bytes calldata) { - return msg.data; - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[50] private __gap; -} - -// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Receiver.sol - -// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol) - -/** - * @dev _Available since v3.1._ - */ -abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); - } -} - -// lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol - -// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) - -/** - * @dev Contract module which provides a basic access control mechanism, where - * there is an account (an owner) that can be granted exclusive access to - * specific functions. - * - * By default, the owner account will be the one that deploys the contract. This - * can later be changed with {transferOwnership}. - * - * This module is used through inheritance. It will make available the modifier - * `onlyOwner`, which can be applied to your functions to restrict their use to - * the owner. - */ -abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { - address private _owner; - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - function __Ownable_init() internal onlyInitializing { - __Ownable_init_unchained(); - } - - function __Ownable_init_unchained() internal onlyInitializing { - _transferOwnership(_msgSender()); - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - _checkOwner(); - _; - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view virtual returns (address) { - return _owner; - } - - /** - * @dev Throws if the sender is not the owner. - */ - function _checkOwner() internal view virtual { - require(owner() == _msgSender(), "Ownable: caller is not the owner"); - } - - /** - * @dev Leaves the contract without owner. It will not be possible to call - * `onlyOwner` functions anymore. Can only be called by the current owner. - * - * NOTE: Renouncing ownership will leave the contract without an owner, - * thereby removing any functionality that is only available to the owner. - */ - function renounceOwnership() public virtual onlyOwner { - _transferOwnership(address(0)); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) public virtual onlyOwner { - require(newOwner != address(0), "Ownable: new owner is the zero address"); - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Internal function without access restriction. - */ - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = _owner; - _owner = newOwner; - emit OwnershipTransferred(oldOwner, newOwner); - } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[49] private __gap; -} - -// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol - -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol) - -/** - * Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. - * - * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be - * stuck. - * - * @dev _Available since v3.1._ - */ -contract ERC1155Holder is ERC1155Receiver { - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes memory - ) public virtual override returns (bytes4) { - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] memory, - uint256[] memory, - bytes memory - ) public virtual override returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } -} - -// src/lib/OwnableWithTransferCallback.sol - -abstract contract OwnableWithTransferCallback { - using ERC165Checker for address; - using Address for address; - - bytes4 constant TRANSFER_CALLBACK = type(IOwnershipTransferReceiver).interfaceId; - - error Ownable_NotOwner(); - error Ownable_NewOwnerZeroAddress(); - - address private _owner; - - event OwnershipTransferred(address indexed newOwner); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - function __Ownable_init(address initialOwner) internal { - _owner = initialOwner; - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view virtual returns (address) { - return _owner; - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - if (owner() != msg.sender) revert Ownable_NotOwner(); - _; - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * @param newOwner The new address to become owner - * @param data Any additional data to send to the ownership received callback. - * Disallows setting to the zero address as a way to more gas-efficiently avoid reinitialization. - * When ownership is transferred, if the new owner implements IOwnershipTransferCallback, we make a callback. - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner, bytes calldata data) public payable virtual onlyOwner { - if (newOwner == address(0)) revert Ownable_NewOwnerZeroAddress(); - _transferOwnership(newOwner); - - if (newOwner.isContract()) { - try IOwnershipTransferReceiver(newOwner).onOwnershipTransferred{value: msg.value}(msg.sender, data) {} - // If revert... - catch (bytes memory reason) { - // If we just transferred to a contract w/ no callback, this is fine - if (reason.length == 0) { - // i.e., no need to revert - } - // Otherwise, the callback had an error, and we should revert - else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - } - - /** - * @notice Transfers ownership of the contract to a new account (`newOwner`). - * @dev Internal function without access restriction. - */ - function _transferOwnership(address newOwner) internal virtual { - _owner = newOwner; - emit OwnershipTransferred(newOwner); - } -} - -// src/ILSSVMPairFactoryLike.sol - -interface ILSSVMPairFactoryLike { - struct Settings { - uint96 bps; - address pairAddress; - } - - enum PairNFTType { - ERC721, - ERC1155 - } - - enum PairTokenType { - ETH, - ERC20 - } - - enum PairVariant { - ERC721_ETH, - ERC721_ERC20, - ERC1155_ETH, - ERC1155_ERC20 - } - - function protocolFeeMultiplier() external view returns (uint256); - - function protocolFeeRecipient() external view returns (address payable); - - function callAllowed(address target) external view returns (bool); - - function authAllowedForToken(address tokenAddress, address proposedAuthAddress) external view returns (bool); - - function getSettingsForPair(address pairAddress) external view returns (bool settingsEnabled, uint96 bps); - - function enableSettingsForPair(address settings, address pairAddress) external; - - function disableSettingsForPair(address settings, address pairAddress) external; - - function routerStatus(LSSVMRouter router) external view returns (bool allowed, bool wasEverTouched); - - function isValidPair(address pairAddress) external view returns (bool); - - function getPairNFTType(address pairAddress) external pure returns (PairNFTType); - - function getPairTokenType(address pairAddress) external pure returns (PairTokenType); - - function openLock() external; - - function closeLock() external; -} - -// src/LSSVMPair.sol - -/** - * @title The base contract for an NFT/TOKEN AMM pair - * @author boredGenius, 0xmons, 0xCygaar - * @notice This implements the core swap logic from NFT to TOKEN - */ -abstract contract LSSVMPair is OwnableWithTransferCallback, ERC721Holder, ERC1155Holder { - /** - * Library usage ** - */ - - using Address for address; - - /** - * Enums ** - */ - - enum PoolType { - TOKEN, - NFT, - TRADE - } - - /** - * Constants ** - */ - - /** - * @dev 50%, must <= 1 - MAX_PROTOCOL_FEE (set in LSSVMPairFactory) - */ - uint256 internal constant MAX_TRADE_FEE = 0.5e18; - - /** - * Immutable params ** - */ - - /** - * @notice Sudoswap Royalty Engine - */ - IRoyaltyEngineV1 public immutable ROYALTY_ENGINE; - - /** - * Storage variables ** - */ - - /** - * @dev This is generally used to mean the immediate sell price for the next marginal NFT. - * However, this should NOT be assumed, as bonding curves may use spotPrice in different ways. - * Use getBuyNFTQuote and getSellNFTQuote for accurate pricing info. - */ - uint128 public spotPrice; - - /** - * @notice The parameter for the pair's bonding curve. - * Units and meaning are bonding curve dependent. - */ - uint128 public delta; - - /** - * @notice The spread between buy and sell prices, set to be a multiplier we apply to the buy price - * Fee is only relevant for TRADE pools. Units are in base 1e18. - */ - uint96 public fee; - - /** - * @notice The address that swapped assets are sent to. - * For TRADE pools, assets are always sent to the pool, so this is used to track trade fee. - * If set to address(0), will default to owner() for NFT and TOKEN pools. - */ - address payable internal assetRecipient; - - /** - * Events - */ - - event SwapNFTInPair(uint256 amountOut, uint256[] ids); - event SwapNFTInPair(uint256 amountOut, uint256 numNFTs); - event SwapNFTOutPair(uint256 amountIn, uint256[] ids); - event SwapNFTOutPair(uint256 amountIn, uint256 numNFTs); - event SpotPriceUpdate(uint128 newSpotPrice); - event TokenDeposit(uint256 amount); - event TokenWithdrawal(uint256 amount); - event NFTWithdrawal(uint256[] ids); - event NFTWithdrawal(uint256 numNFTs); - event DeltaUpdate(uint128 newDelta); - event FeeUpdate(uint96 newFee); - event AssetRecipientChange(address indexed a); - - /** - * Errors - */ - - error LSSVMPair__NotRouter(); - error LSSVMPair__CallFailed(); - error LSSVMPair__InvalidDelta(); - error LSSVMPair__WrongPoolType(); - error LSSVMPair__OutputTooSmall(); - error LSSVMPair__ZeroSwapAmount(); - error LSSVMPair__RoyaltyTooLarge(); - error LSSVMPair__TradeFeeTooLarge(); - error LSSVMPair__InvalidSpotPrice(); - error LSSVMPair__TargetNotAllowed(); - error LSSVMPair__NftNotTransferred(); - error LSSVMPair__AlreadyInitialized(); - error LSSVMPair__FunctionNotAllowed(); - error LSSVMPair__DemandedInputTooLarge(); - error LSSVMPair__NonTradePoolWithTradeFee(); - error LSSVMPair__BondingCurveError(CurveErrorCodes.Error error); - - constructor(IRoyaltyEngineV1 royaltyEngine) { - ROYALTY_ENGINE = royaltyEngine; - } - - /** - * @notice Called during pair creation to set initial parameters - * @dev Only called once by factory to initialize. - * We verify this by making sure that the current owner is address(0). - * The Ownable library we use disallows setting the owner to be address(0), so this condition - * should only be valid before the first initialize call. - * @param _owner The owner of the pair - * @param _assetRecipient The address that will receive the TOKEN or NFT sent to this pair during swaps. NOTE: If set to address(0), they will go to the pair itself. - * @param _delta The initial delta of the bonding curve - * @param _fee The initial % fee taken, if this is a trade pair - * @param _spotPrice The initial price to sell an asset into the pair - */ - function initialize( - address _owner, - address payable _assetRecipient, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice - ) external { - if (owner() != address(0)) revert LSSVMPair__AlreadyInitialized(); - __Ownable_init(_owner); - - ICurve _bondingCurve = bondingCurve(); - PoolType _poolType = poolType(); - if (_poolType != PoolType.TRADE) { - if (_fee != 0) revert LSSVMPair__NonTradePoolWithTradeFee(); - } else { - if (_fee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); - fee = _fee; - } - - assetRecipient = _assetRecipient; - - if (!_bondingCurve.validateDelta(_delta)) revert LSSVMPair__InvalidDelta(); - if (!_bondingCurve.validateSpotPrice(_spotPrice)) revert LSSVMPair__InvalidSpotPrice(); - delta = _delta; - spotPrice = _spotPrice; - } - - /** - * External state-changing functions - */ - - /** - * @notice Sends token to the pair in exchange for a specific set of NFTs - * @dev To compute the amount of token to send, call bondingCurve.getBuyInfo - * This swap is meant for users who want specific IDs. Also higher chance of - * reverting if some of the specified IDs leave the pool before the swap goes through. - * @param nftIds The list of IDs of the NFTs to purchase - * @param maxExpectedTokenInput The maximum acceptable cost from the sender. If the actual - * amount is greater than this value, the transaction will be reverted. - * @param nftRecipient The recipient of the NFTs - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. - * @return - The amount of token used for purchase - */ - function swapTokenForSpecificNFTs( - uint256[] calldata nftIds, - uint256 maxExpectedTokenInput, - address nftRecipient, - bool isRouter, - address routerCaller - ) external payable virtual returns (uint256); - - /** - * @notice Sends a set of NFTs to the pair in exchange for token - * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. - * @param nftIds The list of IDs of the NFTs to sell to the pair - * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual - * amount is less than this value, the transaction will be reverted. - * @param tokenRecipient The recipient of the token output - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for - * ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for - * ETH pairs. - * @return outputAmount The amount of token received - */ - function swapNFTsForToken( - uint256[] calldata nftIds, - uint256 minExpectedTokenOutput, - address payable tokenRecipient, - bool isRouter, - address routerCaller - ) external virtual returns (uint256 outputAmount); - - /** - * View functions - */ - - /** - * @dev Used as read function to query the bonding curve for buy pricing info - * @param numNFTs The number of NFTs to buy from the pair - */ - function getBuyNFTQuote(uint256 assetId, uint256 numNFTs) - external - view - returns ( - CurveErrorCodes.Error error, - uint256 newSpotPrice, - uint256 newDelta, - uint256 inputAmount, - uint256 protocolFee, - uint256 royaltyAmount - ) - { - uint256 tradeFee; - (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = - bondingCurve().getBuyInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); - - if (numNFTs != 0) { - // Calculate the inputAmount minus tradeFee and protocolFee - uint256 inputAmountMinusFees = inputAmount - tradeFee - protocolFee; - - // Compute royalties - (,, royaltyAmount) = calculateRoyaltiesView(assetId, inputAmountMinusFees); - - inputAmount += royaltyAmount; - } - } - - /** - * @dev Used as read function to query the bonding curve for sell pricing info including royalties - * @param numNFTs The number of NFTs to sell to the pair - */ - function getSellNFTQuote(uint256 assetId, uint256 numNFTs) - external - view - returns ( - CurveErrorCodes.Error error, - uint256 newSpotPrice, - uint256 newDelta, - uint256 outputAmount, - uint256 protocolFee, - uint256 royaltyAmount - ) - { - (error, newSpotPrice, newDelta, outputAmount, /* tradeFee */, protocolFee) = - bondingCurve().getSellInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); - - if (numNFTs != 0) { - // Compute royalties - (,, royaltyAmount) = calculateRoyaltiesView(assetId, outputAmount); - - // Deduct royalties from outputAmount - unchecked { - // Safe because we already require outputAmount >= royaltyAmount in _calculateRoyalties() - outputAmount -= royaltyAmount; - } - } - } - - /** - * @notice Returns the pair's variant (Pair uses ETH or ERC20) - */ - function pairVariant() public pure virtual returns (ILSSVMPairFactoryLike.PairVariant); - - function factory() public pure returns (ILSSVMPairFactoryLike _factory) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - _factory := shr(0x60, calldataload(sub(calldatasize(), paramsLength))) - } - } - - /** - * @notice Returns the type of bonding curve that parameterizes the pair - */ - function bondingCurve() public pure returns (ICurve _bondingCurve) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - _bondingCurve := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 20))) - } - } - - /** - * @notice Returns the address of NFT collection that parameterizes the pair - */ - function nft() public pure returns (address _nft) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - _nft := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 40))) - } - } - - /** - * @notice Returns the pair's type (TOKEN/NFT/TRADE) - */ - function poolType() public pure returns (PoolType _poolType) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - _poolType := shr(0xf8, calldataload(add(sub(calldatasize(), paramsLength), 60))) - } - } - - /** - * @notice Returns the address that receives assets when a swap is done with this pair - * Can be set to another address by the owner, but has no effect on TRADE pools - * If set to address(0), defaults to owner() for NFT/TOKEN pools - */ - function getAssetRecipient() public view returns (address payable) { - // TRADE pools will always receive the asset themselves - if (poolType() == PoolType.TRADE) { - return payable(address(this)); - } - - address payable _assetRecipient = assetRecipient; - - // Otherwise, we return the recipient if it's been set - // Or, we replace it with owner() if it's address(0) - if (_assetRecipient == address(0)) { - return payable(owner()); - } - return _assetRecipient; - } - - /** - * @notice Returns the address that receives trade fees when a swap is done with this pair - * Only relevant for TRADE pools - * If set to address(0), defaults to the pair itself - */ - function getFeeRecipient() public view returns (address payable _feeRecipient) { - _feeRecipient = assetRecipient; - if (_feeRecipient == address(0)) { - _feeRecipient = payable(address(this)); - } - } - - /** - * Internal functions - */ - - /** - * @notice Calculates the amount needed to be sent into the pair for a buy and adjusts spot price or delta if necessary - * @param numNFTs The amount of NFTs to purchase from the pair - * @param _bondingCurve The bonding curve to use for price calculation - * @param _factory The factory to use for protocol fee lookup - * @return tradeFee The amount of tokens to send as trade fee - * @return protocolFee The amount of tokens to send as protocol fee - * @return inputAmount The amount of tokens total tokens receive - */ - function _calculateBuyInfoAndUpdatePoolParams(uint256 numNFTs, ICurve _bondingCurve, ILSSVMPairFactoryLike _factory) - internal - returns (uint256 tradeFee, uint256 protocolFee, uint256 inputAmount) - { - CurveErrorCodes.Error error; - // Save on 2 SLOADs by caching - uint128 currentSpotPrice = spotPrice; - uint128 currentDelta = delta; - uint128 newDelta; - uint128 newSpotPrice; - (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = - _bondingCurve.getBuyInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); - - // Revert if bonding curve had an error - if (error != CurveErrorCodes.Error.OK) { - revert LSSVMPair__BondingCurveError(error); - } - - // Consolidate writes to save gas - if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { - spotPrice = newSpotPrice; - delta = newDelta; - } - - // Emit spot price update if it has been updated - if (currentSpotPrice != newSpotPrice) { - emit SpotPriceUpdate(newSpotPrice); - } - - // Emit delta update if it has been updated - if (currentDelta != newDelta) { - emit DeltaUpdate(newDelta); - } - } - - /** - * @notice Calculates the amount needed to be sent by the pair for a sell and adjusts spot price or delta if necessary - * @param numNFTs The amount of NFTs to send to the the pair - * @param _bondingCurve The bonding curve to use for price calculation - * @param _factory The factory to use for protocol fee lookup - * @return protocolFee The amount of tokens to send as protocol fee - * @return outputAmount The amount of tokens total tokens receive - */ - function _calculateSellInfoAndUpdatePoolParams( - uint256 numNFTs, - ICurve _bondingCurve, - ILSSVMPairFactoryLike _factory - ) internal returns (uint256 protocolFee, uint256 outputAmount) { - CurveErrorCodes.Error error; - // Save on 2 SLOADs by caching - uint128 currentSpotPrice = spotPrice; - uint128 currentDelta = delta; - uint128 newSpotPrice; - uint128 newDelta; - (error, newSpotPrice, newDelta, outputAmount, /*tradeFee*/, protocolFee) = - _bondingCurve.getSellInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); - - // Revert if bonding curve had an error - if (error != CurveErrorCodes.Error.OK) { - revert LSSVMPair__BondingCurveError(error); - } - - // Consolidate writes to save gas - if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { - spotPrice = newSpotPrice; - delta = newDelta; - } - - // Emit spot price update if it has been updated - if (currentSpotPrice != newSpotPrice) { - emit SpotPriceUpdate(newSpotPrice); - } - - // Emit delta update if it has been updated - if (currentDelta != newDelta) { - emit DeltaUpdate(newDelta); - } - } - - /** - * @notice Pulls the token input of a trade from the trader (including all royalties and fees) - * @param inputAmountExcludingRoyalty The amount of tokens to be sent, excluding the royalty (includes protocol fee) - * @param royaltyAmounts The amounts of tokens to be sent as royalties - * @param royaltyRecipients The recipients of the royalties - * @param royaltyTotal The sum of all royaltyAmounts - * @param tradeFeeAmount The amount of tokens to be sent as trade fee (if applicable) - * @param isRouter Whether or not the caller is LSSVMRouter - * @param routerCaller If called from LSSVMRouter, store the original caller - * @param protocolFee The protocol fee to be paid - */ - function _pullTokenInputs( - uint256 inputAmountExcludingRoyalty, - uint256[] memory royaltyAmounts, - address payable[] memory royaltyRecipients, - uint256 royaltyTotal, - uint256 tradeFeeAmount, - bool isRouter, - address routerCaller, - uint256 protocolFee - ) internal virtual; - - /** - * @notice Sends excess tokens back to the caller (if applicable) - * @dev Swap callers interacting with an ETH pair must be able to receive ETH (e.g. if the caller sends too much ETH) - */ - function _refundTokenToSender(uint256 inputAmount) internal virtual; - - /** - * @notice Sends tokens to a recipient - * @param tokenRecipient The address receiving the tokens - * @param outputAmount The amount of tokens to send - */ - function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal virtual; - - /** - * @dev Used internally to grab pair parameters from calldata, see LSSVMPairCloner for technical details - */ - function _immutableParamsLength() internal pure virtual returns (uint256); - - /** - * Royalty support functions - */ - - function _calculateRoyalties(uint256 assetId, uint256 saleAmount) - internal - returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) - { - (address payable[] memory recipients, uint256[] memory amounts) = - ROYALTY_ENGINE.getRoyalty(nft(), assetId, saleAmount); - return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); - } - - /** - * @dev Same as _calculateRoyalties, but uses getRoyaltyView to avoid state mutations and is public for external callers - */ - function calculateRoyaltiesView(uint256 assetId, uint256 saleAmount) - public - view - returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) - { - (address payable[] memory recipients, uint256[] memory amounts) = - ROYALTY_ENGINE.getRoyaltyView(nft(), assetId, saleAmount); - return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); - } - - /** - * @dev Common logic used by _calculateRoyalties() and calculateRoyaltiesView() - */ - function _calculateRoyaltiesLogic(address payable[] memory recipients, uint256[] memory amounts, uint256 saleAmount) - internal - view - returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) - { - // Cache to save gas - uint256 numRecipients = recipients.length; - - if (numRecipients != 0) { - // If a pair has custom Settings, use the overridden royalty amount and only use the first receiver - try factory().getSettingsForPair(address(this)) returns (bool settingsEnabled, uint96 bps) { - if (settingsEnabled) { - royaltyRecipients = new address payable[](1); - royaltyRecipients[0] = recipients[0]; - royaltyAmounts = new uint256[](1); - royaltyAmounts[0] = (saleAmount * bps) / 10000; - - // Update numRecipients to match new recipients list - numRecipients = 1; - } else { - royaltyRecipients = recipients; - royaltyAmounts = amounts; - } - } catch { - // Use the input values to calculate royalties if factory call fails - royaltyRecipients = recipients; - royaltyAmounts = amounts; - } - } - - for (uint256 i; i < numRecipients;) { - royaltyTotal += royaltyAmounts[i]; - unchecked { - ++i; - } - } - - // Ensure royalty total is at most 25% of the sale amount - // This defends against a rogue Manifold registry that charges extremely high royalties - if (royaltyTotal > saleAmount >> 2) { - revert LSSVMPair__RoyaltyTooLarge(); - } - } - - /** - * Owner functions - */ - - /** - * @notice Rescues a specified set of NFTs owned by the pair to the owner address. (onlyOwnable modifier is in the implemented function) - * @param a The NFT to transfer - * @param nftIds The list of IDs of the NFTs to send to the owner - */ - function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual; - - /** - * @notice Rescues ERC20 tokens from the pair to the owner. Only callable by the owner (onlyOwnable modifier is in the implemented function). - * @param a The token to transfer - * @param amount The amount of tokens to send to the owner - */ - function withdrawERC20(ERC20 a, uint256 amount) external virtual; - - /** - * @notice Rescues ERC1155 tokens from the pair to the owner. Only callable by the owner. - * @param a The NFT to transfer - * @param ids The NFT ids to transfer - * @param amounts The amounts of each id to transfer - */ - function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) external virtual; - - /** - * @notice Updates the selling spot price. Only callable by the owner. - * @param newSpotPrice The new selling spot price value, in Token - */ - function changeSpotPrice(uint128 newSpotPrice) external onlyOwner { - ICurve _bondingCurve = bondingCurve(); - if (!_bondingCurve.validateSpotPrice(newSpotPrice)) revert LSSVMPair__InvalidSpotPrice(); - if (spotPrice != newSpotPrice) { - spotPrice = newSpotPrice; - emit SpotPriceUpdate(newSpotPrice); - } - } - - /** - * @notice Updates the delta parameter. Only callable by the owner. - * @param newDelta The new delta parameter - */ - function changeDelta(uint128 newDelta) external onlyOwner { - ICurve _bondingCurve = bondingCurve(); - if (!_bondingCurve.validateDelta(newDelta)) revert LSSVMPair__InvalidDelta(); - if (delta != newDelta) { - delta = newDelta; - emit DeltaUpdate(newDelta); - } - } - - /** - * @notice Updates the fee taken by the LP. Only callable by the owner. - * Only callable if the pool is a Trade pool. Reverts if the fee is >= MAX_FEE. - * @param newFee The new LP fee percentage, 18 decimals - */ - function changeFee(uint96 newFee) external onlyOwner { - PoolType _poolType = poolType(); - if (_poolType != PoolType.TRADE) revert LSSVMPair__NonTradePoolWithTradeFee(); - if (newFee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); - if (fee != newFee) { - fee = newFee; - emit FeeUpdate(newFee); - } - } - - /** - * @notice Changes the address that will receive assets received from - * trades. Only callable by the owner. - * @param newRecipient The new asset recipient - */ - function changeAssetRecipient(address payable newRecipient) external onlyOwner { - if (assetRecipient != newRecipient) { - assetRecipient = newRecipient; - emit AssetRecipientChange(newRecipient); - } - } - - function _preCallCheck(address target) internal virtual; - - /** - * @notice Allows the pair to make arbitrary external calls to contracts - * whitelisted by the protocol. Only callable by the owner. - * @param target The contract to call - * @param data The calldata to pass to the contract - */ - function call(address payable target, bytes calldata data) external onlyOwner { - ILSSVMPairFactoryLike _factory = factory(); - if (!_factory.callAllowed(target)) revert LSSVMPair__TargetNotAllowed(); - - // Ensure the call isn't calling a banned function - bytes4 sig = bytes4(data[:4]); - if ( - sig == IOwnershipTransferReceiver.onOwnershipTransferred.selector - || sig == LSSVMRouter.pairTransferERC20From.selector || sig == LSSVMRouter.pairTransferNFTFrom.selector - || sig == LSSVMRouter.pairTransferERC1155From.selector || sig == ILSSVMPairFactoryLike.openLock.selector - || sig == ILSSVMPairFactoryLike.closeLock.selector - ) { - revert LSSVMPair__FunctionNotAllowed(); - } - - // Prevent calling the pair's underlying nft - // (We ban calling the underlying NFT/ERC20 to avoid maliciously transferring assets approved for the pair to spend) - if (target == nft()) revert LSSVMPair__TargetNotAllowed(); - - _preCallCheck(target); - - (bool success,) = target.call{value: 0}(data); - if (!success) revert LSSVMPair__CallFailed(); - } - - /** - * @notice Allows owner to batch multiple calls, forked from: https://github.com/boringcrypto/BoringSolidity/blob/master/contracts/BoringBatchable.sol - * @notice The revert handling is forked from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c239e1af8d1a1296577108dd6989a17b57434f8e/contracts/utils/Address.sol#L201 - * @dev Intended for withdrawing/altering pool pricing in one tx, only callable by owner, cannot change owner - * @param calls The calldata for each call to make - * @param revertOnFail Whether or not to revert the entire tx if any of the calls fail. Calls to transferOwnership will revert regardless. - */ - function multicall(bytes[] calldata calls, bool revertOnFail) external onlyOwner { - for (uint256 i; i < calls.length;) { - bytes4 sig = bytes4(calls[i][:4]); - // We ban calling transferOwnership when ownership - if (sig == transferOwnership.selector) revert LSSVMPair__FunctionNotAllowed(); - - (bool success, bytes memory result) = address(this).delegatecall(calls[i]); - if (!success && revertOnFail) { - assembly { - revert(add(0x20, result), mload(result)) - } - } - - unchecked { - ++i; - } - } - } -} - -// src/LSSVMRouter.sol - -contract LSSVMRouter { - using SafeTransferLib for address payable; - using SafeTransferLib for ERC20; - - struct PairSwapSpecific { - LSSVMPair pair; - uint256[] nftIds; - } - - struct RobustPairSwapSpecific { - PairSwapSpecific swapInfo; - uint256 maxCost; - } - - struct RobustPairSwapSpecificForToken { - PairSwapSpecific swapInfo; - uint256 minOutput; - } - - struct NFTsForSpecificNFTsTrade { - PairSwapSpecific[] nftToTokenTrades; - PairSwapSpecific[] tokenToNFTTrades; - } - - struct RobustPairNFTsFoTokenAndTokenforNFTsTrade { - RobustPairSwapSpecific[] tokenToNFTTrades; - RobustPairSwapSpecificForToken[] nftToTokenTrades; - uint256 inputAmount; - address payable tokenRecipient; - address nftRecipient; - } - - modifier checkDeadline(uint256 deadline) { - _checkDeadline(deadline); - _; - } - - ILSSVMPairFactoryLike public immutable factory; - - constructor(ILSSVMPairFactoryLike _factory) { - factory = _factory; - } - - /** - * ETH swaps - */ - - /** - * @notice Swaps ETH into specific NFTs using multiple pairs. - * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. - * @param ethRecipient The address that will receive the unspent ETH input - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return remainingValue The unspent ETH amount - */ - function swapETHForSpecificNFTs( - PairSwapSpecific[] calldata swapList, - address payable ethRecipient, - address nftRecipient, - uint256 deadline - ) external payable checkDeadline(deadline) returns (uint256 remainingValue) { - return _swapETHForSpecificNFTs(swapList, msg.value, ethRecipient, nftRecipient); - } - - /** - * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using - * ETH as the intermediary. - * @param trade The struct containing all NFT-to-ETH swaps and ETH-to-NFT swaps. - * @param minOutput The minimum acceptable total excess ETH received - * @param ethRecipient The address that will receive the ETH output - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return outputAmount The total ETH received - */ - function swapNFTsForSpecificNFTsThroughETH( - NFTsForSpecificNFTsTrade calldata trade, - uint256 minOutput, - address payable ethRecipient, - address nftRecipient, - uint256 deadline - ) external payable checkDeadline(deadline) returns (uint256 outputAmount) { - // Swap NFTs for ETH - // minOutput of swap set to 0 since we're doing an aggregate slippage check - outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(address(this))); - - // Add extra value to buy NFTs - outputAmount += msg.value; - - // Swap ETH for specific NFTs - // cost <= inputValue = outputAmount - minOutput, so outputAmount' = (outputAmount - minOutput - cost) + minOutput >= minOutput - outputAmount = _swapETHForSpecificNFTs( - trade.tokenToNFTTrades, outputAmount - minOutput, ethRecipient, nftRecipient - ) + minOutput; - } - - /** - * ERC20 swaps - * - * Note: All ERC20 swaps assume that a single ERC20 token is used for all the pairs involved. - * Swapping using multiple tokens in the same transaction is possible, but the slippage checks - * & the return values will be meaningless, and may lead to undefined behavior. - * - * Note: The sender should ideally grant infinite token approval to the router in order for NFT-to-NFT - * swaps to work smoothly. - */ - - /** - * @notice Swaps ERC20 tokens into specific NFTs using multiple pairs. - * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. - * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return remainingValue The unspent token amount - */ - function swapERC20ForSpecificNFTs( - PairSwapSpecific[] calldata swapList, - uint256 inputAmount, - address nftRecipient, - uint256 deadline - ) external checkDeadline(deadline) returns (uint256 remainingValue) { - return _swapERC20ForSpecificNFTs(swapList, inputAmount, nftRecipient); - } - - /** - * @notice Swaps NFTs into ETH/ERC20 using multiple pairs. - * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. - * @param minOutput The minimum acceptable total tokens received - * @param tokenRecipient The address that will receive the token output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return outputAmount The total tokens received - */ - function swapNFTsForToken( - PairSwapSpecific[] calldata swapList, - uint256 minOutput, - address tokenRecipient, - uint256 deadline - ) external checkDeadline(deadline) returns (uint256 outputAmount) { - return _swapNFTsForToken(swapList, minOutput, payable(tokenRecipient)); - } - - /** - * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using - * an ERC20 token as the intermediary. - * @param trade The struct containing all NFT-to-ERC20 swaps and ERC20-to-NFT swaps. - * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps - * @param minOutput The minimum acceptable total excess tokens received - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return outputAmount The total ERC20 tokens received - */ - function swapNFTsForSpecificNFTsThroughERC20( - NFTsForSpecificNFTsTrade calldata trade, - uint256 inputAmount, - uint256 minOutput, - address nftRecipient, - uint256 deadline - ) external checkDeadline(deadline) returns (uint256 outputAmount) { - // Swap NFTs for ERC20 - // minOutput of swap set to 0 since we're doing an aggregate slippage check - // output tokens are sent to msg.sender - outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(msg.sender)); - - // Add extra value to buy NFTs - outputAmount += inputAmount; - - // Swap ERC20 for specific NFTs - // cost <= maxCost = outputAmount - minOutput, so outputAmount' = outputAmount - cost >= minOutput - // input tokens are taken directly from msg.sender - outputAmount = - _swapERC20ForSpecificNFTs(trade.tokenToNFTTrades, outputAmount - minOutput, nftRecipient) + minOutput; - } - - /** - * Robust Swaps - * These are "robust" versions of the NFT<>Token swap functions which will never revert due to slippage - * Instead, users specify a per-swap max cost. If the price changes more than the user specifies, no swap is attempted. This allows users to specify a batch of swaps, and execute as many of them as possible. - */ - - /** - * @dev Ensure msg.value >= sum of values in maxCostPerPair to make sure the transaction doesn't revert - * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. - * @param ethRecipient The address that will receive the unspent ETH input - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return remainingValue The unspent token amount - */ - function robustSwapETHForSpecificNFTs( - RobustPairSwapSpecific[] calldata swapList, - address payable ethRecipient, - address nftRecipient, - uint256 deadline - ) public payable virtual checkDeadline(deadline) returns (uint256 remainingValue) { - remainingValue = msg.value; - uint256 pairCost; - CurveErrorCodes.Error error; - - // Try doing each swap - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - // Calculate actual cost per swap - (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( - swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length - ); - - // If within our maxCost and no error, proceed - if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { - // We know how much ETH to send because we already did the math above - // So we just send that much - remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( - swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - - // Return remaining value to sender - if (remainingValue > 0) { - ethRecipient.safeTransferETH(remainingValue); - } - } - - /** - * @notice Swaps as many ERC20 tokens for specific NFTs as possible, respecting the per-swap max cost. - * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. - * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps - * @param nftRecipient The address that will receive the NFT output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return remainingValue The unspent token amount - */ - function robustSwapERC20ForSpecificNFTs( - RobustPairSwapSpecific[] calldata swapList, - uint256 inputAmount, - address nftRecipient, - uint256 deadline - ) public virtual checkDeadline(deadline) returns (uint256 remainingValue) { - remainingValue = inputAmount; - uint256 pairCost; - CurveErrorCodes.Error error; - - // Try doing each swap - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - // Calculate actual cost per swap - (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( - swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length - ); - - // If within our maxCost and no error, proceed - if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { - remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs( - swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - } - - /** - * @notice Swaps as many NFTs for tokens as possible, respecting the per-swap min output - * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. - * @param tokenRecipient The address that will receive the token output - * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert - * @return outputAmount The total ETH/ERC20 received - */ - function robustSwapNFTsForToken( - RobustPairSwapSpecificForToken[] calldata swapList, - address payable tokenRecipient, - uint256 deadline - ) public virtual checkDeadline(deadline) returns (uint256 outputAmount) { - // Try doing each swap - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - uint256 pairOutput; - - // Locally scoped to avoid stack too deep error - { - CurveErrorCodes.Error error; - uint256[] memory nftIds = swapList[i].swapInfo.nftIds; - if (nftIds.length == 0) { - unchecked { - ++i; - } - continue; - } - (error,,, pairOutput,,) = swapList[i].swapInfo.pair.getSellNFTQuote(nftIds[0], nftIds.length); - if (error != CurveErrorCodes.Error.OK) { - unchecked { - ++i; - } - continue; - } - } - - // If at least equal to our minOutput, proceed - if (pairOutput >= swapList[i].minOutput) { - // Do the swap and update outputAmount with how many tokens we got - outputAmount += swapList[i].swapInfo.pair.swapNFTsForToken( - swapList[i].swapInfo.nftIds, 0, tokenRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - } - - /** - * @notice Buys NFTs with ETH and sells them for tokens in one transaction - * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: - * - ethToNFTSwapList The list of NFTs to buy - * - nftToTokenSwapList The list of NFTs to sell - * - inputAmount The max amount of tokens to send (if ERC20) - * - tokenRecipient The address that receives tokens from the NFTs sold - * - nftRecipient The address that receives NFTs - * - deadline UNIX timestamp deadline for the swap - */ - function robustSwapETHForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) - external - payable - virtual - returns (uint256 remainingValue, uint256 outputAmount) - { - { - remainingValue = msg.value; - uint256 pairCost; - CurveErrorCodes.Error error; - - // Try doing each swap - uint256 numSwaps = params.tokenToNFTTrades.length; - for (uint256 i; i < numSwaps;) { - // Calculate actual cost per swap - (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( - params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length - ); - - // If within our maxCost and no error, proceed - if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { - // We know how much ETH to send because we already did the math above - // So we just send that much - remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( - params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - - // Return remaining value to sender - if (remainingValue > 0) { - params.tokenRecipient.safeTransferETH(remainingValue); - } - } - { - // Try doing each swap - uint256 numSwaps = params.nftToTokenTrades.length; - for (uint256 i; i < numSwaps;) { - uint256 pairOutput; - - // Locally scoped to avoid stack too deep error - { - CurveErrorCodes.Error error; - uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; - (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( - assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length - ); - if (error != CurveErrorCodes.Error.OK) { - unchecked { - ++i; - } - continue; - } - } - - // If at least equal to our minOutput, proceed - if (pairOutput >= params.nftToTokenTrades[i].minOutput) { - // Do the swap and update outputAmount with how many tokens we got - outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( - params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - } - } - - /** - * @notice Buys NFTs with ERC20, and sells them for tokens in one transaction - * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: - * - ethToNFTSwapList The list of NFTs to buy - * - nftToTokenSwapList The list of NFTs to sell - * - inputAmount The max amount of tokens to send (if ERC20) - * - tokenRecipient The address that receives tokens from the NFTs sold - * - nftRecipient The address that receives NFTs - * - deadline UNIX timestamp deadline for the swap - */ - function robustSwapERC20ForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) - external - virtual - returns (uint256 remainingValue, uint256 outputAmount) - { - { - remainingValue = params.inputAmount; - uint256 pairCost; - CurveErrorCodes.Error error; - - // Try doing each swap - uint256 numSwaps = params.tokenToNFTTrades.length; - for (uint256 i; i < numSwaps;) { - // Calculate actual cost per swap - (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( - params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length - ); - - // If within our maxCost and no error, proceed - if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { - remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs( - params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - } - { - // Try doing each swap - uint256 numSwaps = params.nftToTokenTrades.length; - for (uint256 i; i < numSwaps;) { - uint256 pairOutput; - - // Locally scoped to avoid stack too deep error - { - CurveErrorCodes.Error error; - uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; - (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( - assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length - ); - if (error != CurveErrorCodes.Error.OK) { - unchecked { - ++i; - } - continue; - } - } - - // If at least equal to our minOutput, proceed - if (pairOutput >= params.nftToTokenTrades[i].minOutput) { - // Do the swap and update outputAmount with how many tokens we got - outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( - params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender - ); - } - - unchecked { - ++i; - } - } - } - } - - receive() external payable {} - - /** - * Restricted functions - */ - - /** - * @dev Allows an ERC20 pair contract to transfer ERC20 tokens directly from - * the sender, in order to minimize the number of token transfers. Only callable by an ERC20 pair. - * @param token The ERC20 token to transfer - * @param from The address to transfer tokens from - * @param to The address to transfer tokens to - * @param amount The amount of tokens to transfer - */ - function pairTransferERC20From(ERC20 token, address from, address to, uint256 amount) external { - // verify caller is a trusted pair contract - require(factory.isValidPair(msg.sender), "Not pair"); - // verify caller is an ERC20 pair - require(factory.getPairTokenType(msg.sender) == ILSSVMPairFactoryLike.PairTokenType.ERC20, "Not ERC20 pair"); - - // transfer tokens to pair - token.safeTransferFrom(from, to, amount); - } - - /** - * @dev Allows a pair contract to transfer ERC721 NFTs directly from - * the sender, in order to minimize the number of token transfers. Only callable by a pair. - * @param nft The ERC721 NFT to transfer - * @param from The address to transfer tokens from - * @param to The address to transfer tokens to - * @param id The ID of the NFT to transfer - */ - function pairTransferNFTFrom(IERC721 nft, address from, address to, uint256 id) external { - // verify caller is a trusted pair contract - require(factory.isValidPair(msg.sender), "Not pair"); - - // transfer NFTs to pair - nft.transferFrom(from, to, id); - } - - function pairTransferERC1155From( - IERC1155 nft, - address from, - address to, - uint256[] calldata ids, - uint256[] calldata amounts - ) external { - // verify caller is a trusted pair contract - require(factory.isValidPair(msg.sender), "Not pair"); - - nft.safeBatchTransferFrom(from, to, ids, amounts, bytes("")); - } - - /** - * Internal functions - */ - - /** - * @param deadline The last valid time for a swap - */ - function _checkDeadline(uint256 deadline) internal view { - require(block.timestamp <= deadline, "Deadline passed"); - } - - /** - * @notice Internal function used to swap ETH for a specific set of NFTs - * @param swapList The list of pairs and swap calldata - * @param inputAmount The total amount of ETH to send - * @param ethRecipient The address receiving excess ETH - * @param nftRecipient The address receiving the NFTs from the pairs - * @return remainingValue The unspent token amount - */ - function _swapETHForSpecificNFTs( - PairSwapSpecific[] calldata swapList, - uint256 inputAmount, - address payable ethRecipient, - address nftRecipient - ) internal virtual returns (uint256 remainingValue) { - remainingValue = inputAmount; - - uint256 pairCost; - CurveErrorCodes.Error error; - - // Do swaps - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - // Calculate the cost per swap first to send exact amount of ETH over, saves gas by avoiding the need to send back excess ETH - (error,,, pairCost,,) = swapList[i].pair.getBuyNFTQuote(swapList[i].nftIds[0], swapList[i].nftIds.length); - - // Require no errors - require(error == CurveErrorCodes.Error.OK, "Bonding curve error"); - - // Total ETH taken from sender cannot exceed inputAmount - // because otherwise the deduction from remainingValue will fail - remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs{value: pairCost}( - swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender - ); - - unchecked { - ++i; - } - } - - // Return remaining value to sender - if (remainingValue > 0) { - ethRecipient.safeTransferETH(remainingValue); - } - } - - /** - * @notice Internal function used to swap an ERC20 token for specific NFTs - * @dev Note that we don't need to query the pair's bonding curve first for pricing data because - * we just calculate and take the required amount from the caller during swap time. - * However, we can't "pull" ETH, which is why for the ETH->NFT swaps, we need to calculate the pricing info - * to figure out how much the router should send to the pool. - * @param swapList The list of pairs and swap calldata - * @param inputAmount The total amount of ERC20 tokens to send - * @param nftRecipient The address receiving the NFTs from the pairs - * @return remainingValue The unspent token amount - */ - function _swapERC20ForSpecificNFTs(PairSwapSpecific[] calldata swapList, uint256 inputAmount, address nftRecipient) - internal - virtual - returns (uint256 remainingValue) - { - remainingValue = inputAmount; - - // Do swaps - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - // Tokens are transferred in by the pair calling router.pairTransferERC20From - // Total tokens taken from sender cannot exceed inputAmount - // because otherwise the deduction from remainingValue will fail - remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs( - swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender - ); - - unchecked { - ++i; - } - } - } - - /** - * @notice Swaps NFTs for tokens, designed to be used for 1 token at a time - * @dev Calling with multiple tokens is permitted, BUT minOutput will be - * far from enough of a safety check because different tokens almost certainly have different unit prices. - * @param swapList The list of pairs and swap calldata - * @param minOutput The minimum number of tokens to be receieved from the swaps - * @param tokenRecipient The address that receives the tokens - * @return outputAmount The number of tokens to be received - */ - function _swapNFTsForToken(PairSwapSpecific[] calldata swapList, uint256 minOutput, address payable tokenRecipient) - internal - virtual - returns (uint256 outputAmount) - { - // Do swaps - uint256 numSwaps = swapList.length; - for (uint256 i; i < numSwaps;) { - // Do the swap for token and then update outputAmount - // Note: minExpectedTokenOutput is set to 0 since we're doing an aggregate slippage check below - outputAmount += swapList[i].pair.swapNFTsForToken(swapList[i].nftIds, 0, tokenRecipient, true, msg.sender); - - unchecked { - ++i; - } - } - - // Aggregate slippage check - require(outputAmount >= minOutput, "outputAmount too low"); - } -} - -// src/LSSVMPairERC20.sol - -/** - * @title An NFT/Token pair where the token is an ERC20 - * @author boredGenius, 0xmons, 0xCygaar - */ -abstract contract LSSVMPairERC20 is LSSVMPair { - using SafeTransferLib for ERC20; - - error LSSVMPairERC20__RoyaltyNotPaid(); - error LSSVMPairERC20__MsgValueNotZero(); - error LSSVMPairERC20__AssetRecipientNotPaid(); - - /** - * @notice Returns the ERC20 token associated with the pair - * @dev See LSSVMPairCloner for an explanation on how this works - * @dev The last 20 bytes of the immutable data contain the ERC20 token address - */ - function token() public pure returns (ERC20 _token) { - assembly { - _token := shr(0x60, calldataload(sub(calldatasize(), 20))) - } - } - - /** - * @inheritdoc LSSVMPair - */ - function _pullTokenInputs( - uint256 inputAmountExcludingRoyalty, - uint256[] memory royaltyAmounts, - address payable[] memory royaltyRecipients, - uint256, /* royaltyTotal */ - uint256 tradeFeeAmount, - bool isRouter, - address routerCaller, - uint256 protocolFee - ) internal override { - address _assetRecipient = getAssetRecipient(); - - // Transfer tokens - if (isRouter) { - // Verify if router is allowed - // Locally scoped to avoid stack too deep - { - (bool routerAllowed,) = factory().routerStatus(LSSVMRouter(payable(msg.sender))); - if (!routerAllowed) revert LSSVMPair__NotRouter(); - } - - // Cache state and then call router to transfer tokens from user - uint256 beforeBalance = token().balanceOf(_assetRecipient); - LSSVMRouter(payable(msg.sender)).pairTransferERC20From( - token(), routerCaller, _assetRecipient, inputAmountExcludingRoyalty - protocolFee - ); - - // Verify token transfer (protect pair against malicious router) - ERC20 token_ = token(); - if (token_.balanceOf(_assetRecipient) - beforeBalance != (inputAmountExcludingRoyalty - protocolFee)) { - revert LSSVMPairERC20__AssetRecipientNotPaid(); - } - - // Transfer royalties (if they exist) - for (uint256 i; i < royaltyRecipients.length;) { - beforeBalance = token_.balanceOf(royaltyRecipients[i]); - LSSVMRouter(payable(msg.sender)).pairTransferERC20From( - token_, routerCaller, royaltyRecipients[i], royaltyAmounts[i] - ); - if (token_.balanceOf(royaltyRecipients[i]) - beforeBalance != royaltyAmounts[i]) { - revert LSSVMPairERC20__RoyaltyNotPaid(); - } - unchecked { - ++i; - } - } - - // Take protocol fee (if it exists) - if (protocolFee != 0) { - LSSVMRouter(payable(msg.sender)).pairTransferERC20From( - token_, routerCaller, address(factory()), protocolFee - ); - } - } else { - // Transfer tokens directly (sans the protocol fee) - ERC20 token_ = token(); - token_.safeTransferFrom(msg.sender, _assetRecipient, inputAmountExcludingRoyalty - protocolFee); - - // Transfer royalties (if they exists) - for (uint256 i; i < royaltyRecipients.length;) { - token_.safeTransferFrom(msg.sender, royaltyRecipients[i], royaltyAmounts[i]); - unchecked { - ++i; - } - } - - // Take protocol fee (if it exists) - if (protocolFee != 0) { - token_.safeTransferFrom(msg.sender, address(factory()), protocolFee); - } - } - // Send trade fee if it exists, is TRADE pool, and fee recipient != pool address - // @dev: (note that tokens are sent from the pool and not the caller) - if (poolType() == PoolType.TRADE && tradeFeeAmount != 0) { - address payable _feeRecipient = getFeeRecipient(); - if (_feeRecipient != _assetRecipient) { - token().safeTransfer(_feeRecipient, tradeFeeAmount); - } - } - } - - /** - * @inheritdoc LSSVMPair - */ - function _refundTokenToSender(uint256 inputAmount) internal override { - // Do nothing since we transferred the exact input amount - } - - /** - * @inheritdoc LSSVMPair - */ - function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal override { - // Send tokens to caller - if (outputAmount != 0) { - token().safeTransfer(tokenRecipient, outputAmount); - } - } - - /** - * @inheritdoc LSSVMPair - */ - function withdrawERC20(ERC20 a, uint256 amount) external override onlyOwner { - a.safeTransfer(msg.sender, amount); - - if (a == token()) { - // emit event since it is the pair token - emit TokenWithdrawal(amount); - } - } - - function _preCallCheck(address target) internal pure override { - if (target == address(token())) revert LSSVMPair__TargetNotAllowed(); - } -} - -// src/LSSVMPairETH.sol - -/** - * @title An NFT/Token pair where the token is ETH - * @author boredGenius, 0xmons, 0xCygaar - */ -abstract contract LSSVMPairETH is LSSVMPair { - using SafeTransferLib for address payable; - using SafeTransferLib for ERC20; - - error LSSVMPairETH__InsufficientInput(); - - /** - * @inheritdoc LSSVMPair - */ - function _pullTokenInputs( - uint256 inputAmountExcludingRoyalty, - uint256[] memory royaltyAmounts, - address payable[] memory royaltyRecipients, - uint256 royaltyTotal, - uint256 tradeFeeAmount, - bool, /*isRouter*/ - address, /*routerCaller*/ - uint256 protocolFee - ) internal override { - // Require that the input amount is sufficient to pay for the sale amount, royalties, and fees - if (msg.value < (royaltyTotal + inputAmountExcludingRoyalty)) revert LSSVMPairETH__InsufficientInput(); - - // Transfer inputAmountExcludingRoyalty ETH to assetRecipient if it has been set - address payable _assetRecipient = getAssetRecipient(); - - // Attempt to transfer trade fees only if TRADE pool and they exist - if (poolType() == PoolType.TRADE && tradeFeeAmount != 0) { - address payable _feeRecipient = getFeeRecipient(); - - // Only send and deduct tradeFeeAmount if the fee recipient is not the asset recipient (i.e. the pool) - if (_feeRecipient != _assetRecipient) { - inputAmountExcludingRoyalty -= tradeFeeAmount; - _feeRecipient.safeTransferETH(tradeFeeAmount); - } - // In the else case, we would want to ensure that inputAmountExcludingRoyalty >= tradeFeeAmount / 2 - // to avoid underpaying the trade fee, but it is always true because the max royalty - // is 25%, the max protocol fee is 10%, and the max trade fee is 50%, meaning they can - // never add up to more than 100%. - } - - if (_assetRecipient != address(this)) { - _assetRecipient.safeTransferETH(inputAmountExcludingRoyalty - protocolFee); - } - - // Transfer royalties - for (uint256 i; i < royaltyRecipients.length;) { - royaltyRecipients[i].safeTransferETH(royaltyAmounts[i]); - unchecked { - ++i; - } - } - - // Take protocol fee - if (protocolFee != 0) { - payable(address(factory())).safeTransferETH(protocolFee); - } - } - - /** - * @inheritdoc LSSVMPair - */ - function _refundTokenToSender(uint256 inputAmount) internal override { - // Give excess ETH back to caller - if (msg.value > inputAmount) { - payable(msg.sender).safeTransferETH(msg.value - inputAmount); - } - } - - /** - * @inheritdoc LSSVMPair - */ - function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal override { - // Send ETH to caller - if (outputAmount != 0) { - tokenRecipient.safeTransferETH(outputAmount); - } - } - - /** - * @notice Withdraws all token owned by the pair to the owner address. - * @dev Only callable by the owner. - */ - function withdrawAllETH() external onlyOwner { - withdrawETH(address(this).balance); - } - - /** - * @notice Withdraws a specified amount of token owned by the pair to the owner address. - * @dev Only callable by the owner. - * @param amount The amount of token to send to the owner. If the pair's balance is less than - * this value, the transaction will be reverted. - */ - function withdrawETH(uint256 amount) public onlyOwner { - payable(msg.sender).safeTransferETH(amount); - - // emit event since ETH is the pair token - emit TokenWithdrawal(amount); - } - - /** - * @inheritdoc LSSVMPair - */ - function withdrawERC20(ERC20 a, uint256 amount) external override onlyOwner { - a.safeTransfer(msg.sender, amount); - } - - /** - * @dev All ETH transfers into the pair are accepted. This is the main method - * for the owner to top up the pair's token reserves. - */ - receive() external payable { - emit TokenDeposit(msg.value); - } - - /** - * @dev All ETH transfers into the pair are accepted. This is the main method - * for the owner to top up the pair's token reserves. - */ - fallback() external payable { - // Only allow calls without function selector - require(msg.data.length == _immutableParamsLength()); - emit TokenDeposit(msg.value); - } - - function _preCallCheck(address) internal pure override {} -} - -// src/erc1155/LSSVMPairERC1155.sol - -/** - * @title LSSVMPairERC1155 - * @author boredGenius, 0xmons, 0xCygaar - * @notice An NFT/Token pair for an ERC1155 NFT where NFTs with the same ID are considered fungible. - */ -abstract contract LSSVMPairERC1155 is LSSVMPair { - /** - * External state-changing functions - */ - - /** - * @notice Sends token to the pair in exchange for any `numNFTs` NFTs - * @dev To compute the amount of token to send, call bondingCurve.getBuyInfo. - * This swap function is meant for users who are ID agnostic - * @param numNFTs The number of NFTs to purchase - * @param maxExpectedTokenInput The maximum acceptable cost from the sender. If the actual - * amount is greater than this value, the transaction will be reverted. - * @param nftRecipient The recipient of the NFTs - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. - * @return inputAmount The amount of token used for purchase - */ - function swapTokenForSpecificNFTs( - uint256[] calldata numNFTs, - uint256 maxExpectedTokenInput, - address nftRecipient, - bool isRouter, - address routerCaller - ) external payable virtual override returns (uint256) { - // Store locally to remove extra calls - factory().openLock(); - - // Input validation - { - if (poolType() == PoolType.TOKEN) revert LSSVMPair__WrongPoolType(); - if (numNFTs.length != 1 || numNFTs[0] == 0) revert LSSVMPair__ZeroSwapAmount(); - } - - // Call bonding curve for pricing information - uint256 tradeFee; - uint256 protocolFee; - uint256 inputAmountExcludingRoyalty; - (tradeFee, protocolFee, inputAmountExcludingRoyalty) = - _calculateBuyInfoAndUpdatePoolParams(numNFTs[0], bondingCurve(), factory()); - - (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = - _calculateRoyalties(nftId(), inputAmountExcludingRoyalty - protocolFee - tradeFee); - - // Revert if the input amount is too large - if (royaltyTotal + inputAmountExcludingRoyalty > maxExpectedTokenInput) { - revert LSSVMPair__DemandedInputTooLarge(); - } - - _pullTokenInputs({ - inputAmountExcludingRoyalty: inputAmountExcludingRoyalty, - royaltyRecipients: royaltyRecipients, - royaltyAmounts: royaltyAmounts, - royaltyTotal: royaltyTotal, - tradeFeeAmount: 2 * tradeFee, - isRouter: isRouter, - routerCaller: routerCaller, - protocolFee: protocolFee - }); - - _sendAnyNFTsToRecipient(IERC1155(nft()), nftRecipient, numNFTs[0]); - - _refundTokenToSender(royaltyTotal + inputAmountExcludingRoyalty); - - factory().closeLock(); - - emit SwapNFTOutPair(royaltyTotal + inputAmountExcludingRoyalty, numNFTs[0]); - - return (royaltyTotal + inputAmountExcludingRoyalty); - } - - /** - * @notice Sends a set of NFTs to the pair in exchange for token - * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. - * @param numNFTs The number of NFTs to swap - * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual - * amount is less than this value, the transaction will be reverted. - * @param tokenRecipient The recipient of the token output - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. - * @return outputAmount The amount of token received - */ - function swapNFTsForToken( - uint256[] calldata numNFTs, // @dev this is a bit hacky, to allow for better interop w/ other pair interfaces - uint256 minExpectedTokenOutput, - address payable tokenRecipient, - bool isRouter, - address routerCaller - ) external virtual override returns (uint256 outputAmount) { - // Store locally to remove extra calls - ILSSVMPairFactoryLike _factory = factory(); - - _factory.openLock(); - - ICurve _bondingCurve = bondingCurve(); - - // Input validation - { - if (poolType() == PoolType.NFT) revert LSSVMPair__WrongPoolType(); - if (numNFTs.length != 1 || numNFTs[0] == 0) revert LSSVMPair__ZeroSwapAmount(); - } - - // Call bonding curve for pricing information - uint256 protocolFee; - (protocolFee, outputAmount) = _calculateSellInfoAndUpdatePoolParams(numNFTs[0], _bondingCurve, _factory); - - // Compute royalties - (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = - _calculateRoyalties(nftId(), outputAmount); - - // Deduct royalties from outputAmount - unchecked { - // Safe because we already require outputAmount >= royaltyTotal in calculateRoyalties() - outputAmount -= royaltyTotal; - } - - if (outputAmount < minExpectedTokenOutput) revert LSSVMPair__OutputTooSmall(); - - _takeNFTsFromSender(IERC1155(nft()), numNFTs[0], _factory, isRouter, routerCaller); - - _sendTokenOutput(tokenRecipient, outputAmount); - - for (uint256 i; i < royaltyRecipients.length;) { - _sendTokenOutput(royaltyRecipients[i], royaltyAmounts[i]); - unchecked { - ++i; - } - } - - _sendTokenOutput(payable(address(_factory)), protocolFee); - - _factory.closeLock(); - - emit SwapNFTInPair(outputAmount, numNFTs[0]); - } - - /** - * View functions - */ - - /** - * @notice Returns the ERC-1155 NFT ID this pool uses - */ - function nftId() public pure returns (uint256 id) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - id := calldataload(add(sub(calldatasize(), paramsLength), 61)) - } - } - - /** - * Internal functions - */ - - /** - * @notice Sends some number of NFTs to a recipient address - * @dev Even though we specify the NFT address here, this internal function is only - * used to send NFTs associated with this specific pool. - * @param _nft The address of the NFT to send - * @param nftRecipient The receiving address for the NFTs - * @param numNFTs The number of NFTs to send - */ - function _sendAnyNFTsToRecipient(IERC1155 _nft, address nftRecipient, uint256 numNFTs) internal virtual { - _nft.safeTransferFrom(address(this), nftRecipient, nftId(), numNFTs, bytes("")); - } - - /** - * @notice Takes NFTs from the caller and sends them into the pair's asset recipient - * @dev This is used by the LSSVMPair's swapNFTForToken function. - * @param _nft The NFT collection to take from - * @param numNFTs The number of NFTs to take - * @param isRouter Whether or not to use the router pull flow - * @param routerCaller If the caller is a router, passes in which address to pull from (i.e. the router's caller) - */ - function _takeNFTsFromSender( - IERC1155 _nft, - uint256 numNFTs, - ILSSVMPairFactoryLike factory, - bool isRouter, - address routerCaller - ) internal virtual { - address _assetRecipient = getAssetRecipient(); - - if (isRouter) { - // Verify if router is allowed - LSSVMRouter router = LSSVMRouter(payable(msg.sender)); - (bool routerAllowed,) = factory.routerStatus(router); - if (!routerAllowed) revert LSSVMPair__NotRouter(); - - uint256 _nftId = nftId(); - uint256 beforeBalance = _nft.balanceOf(_assetRecipient, _nftId); - uint256[] memory ids = new uint256[](1); - ids[0] = _nftId; - uint256[] memory amounts = new uint256[](1); - amounts[0] = numNFTs; - router.pairTransferERC1155From(_nft, routerCaller, _assetRecipient, ids, amounts); - if (_nft.balanceOf(_assetRecipient, _nftId) - beforeBalance != numNFTs) { - revert LSSVMPair__NftNotTransferred(); - } - } else { - // Pull NFTs directly from sender - _nft.safeTransferFrom(msg.sender, _assetRecipient, nftId(), numNFTs, bytes("")); - } - } - - /** - * Owner functions - */ - - /** - * @notice Rescues a specified set of NFTs owned by the pair to the owner address. Only callable by the owner. - * @param a The NFT to transfer - * @param nftIds The list of IDs of the NFTs to send to the owner - */ - function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual override onlyOwner { - uint256 numNFTs = nftIds.length; - for (uint256 i; i < numNFTs;) { - a.safeTransferFrom(address(this), msg.sender, nftIds[i]); - unchecked { - ++i; - } - } - } - - /** - * @notice Transfers ERC1155 tokens from the pair to the owner. Only callable by the owner. - * @param a The NFT to transfer - * @param ids The NFT ids to transfer - * @param amounts The amounts of each id to transfer - */ - function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) - external - virtual - override - onlyOwner - { - if (a == IERC1155(nft())) { - // Check if we need to emit an event for withdrawing the NFT this pool is trading - uint256 _nftId = nftId(); - uint256 numNFTs = ids.length; - uint256 numPairNFTsWithdrawn; - for (uint256 i; i < numNFTs;) { - if (ids[i] == _nftId) { - numPairNFTsWithdrawn += amounts[i]; - } - unchecked { - ++i; - } - } - - if (numPairNFTsWithdrawn != 0) { - // Only emit for the pair's NFT - emit NFTWithdrawal(numPairNFTsWithdrawn); - } - } - - a.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, bytes("")); - } -} - -// src/lib/LSSVMPairCloner.sol - -library LSSVMPairCloner { - /** - * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - * - * During the delegate call, extra data is copied into the calldata which can then be - * accessed by the implementation contract. - * - * @return instance The address of the new pair instance - */ - function cloneERC721ETHPair( - address implementation, - ILSSVMPairFactoryLike factory, - ICurve bondingCurve, - IERC721 nft, - uint8 poolType, - address propertyChecker - ) internal returns (address instance) { - assembly { - let ptr := mload(0x40) - - // ------------------------------------------------------------------------------------------------------------- - // CREATION (9 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // creation size = 09 - // runtime size = 86 - // 60 runtime | PUSH1 runtime (r) | r | – - // 3d | RETURNDATASIZE | 0 r | – - // 81 | DUP2 | r 0 r | – - // 60 creation | PUSH1 creation (c) | c r 0 r | – - // 3d | RETURNDATASIZE | 0 c r 0 r | – - // 39 | CODECOPY | 0 r | [0-runSize): runtime code - // f3 | RETURN | | [0-runSize): runtime code - - // ------------------------------------------------------------------------------------------------------------- - // RUNTIME (53 bytes of code + 81 bytes of extra data = 134 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // extra data size = 51 - // 3d | RETURNDATASIZE | 0 | – - // 3d | RETURNDATASIZE | 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 0 | – - // 36 | CALLDATASIZE | cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – - // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata - // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata - // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data - // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata - // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - mstore(ptr, hex"60863d8160093d39f33d3d3d3d363d3d37605160353639366051013d73000000") - mstore(add(ptr, 0x1d), shl(0x60, implementation)) - - // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x35) when rds < cds+0x35) - // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data - // 57 | JUMPI | 0 rds | [0, rds) = return data - // fd | REVERT | – | [0, rds) = return data - // 5b | JUMPDEST | 0 rds | [0, rds) = return data - // f3 | RETURN | – | [0, rds) = return data - mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - - // ------------------------------------------------------------------------------------------------------------- - // EXTRA DATA (81 bytes) - // ------------------------------------------------------------------------------------------------------------- - - mstore(add(ptr, 0x3e), shl(0x60, factory)) - mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) - mstore(add(ptr, 0x66), shl(0x60, nft)) - mstore8(add(ptr, 0x7a), poolType) - mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) - - // ------------------------------------------------------------------------------------------------------------- - // Total length is 143 (8f) bytes - // ------------------------------------------------------------------------------------------------------------- - - instance := create(0, ptr, 0x8f) - } - } - - /** - * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - * - * During the delegate call, extra data is copied into the calldata which can then be - * accessed by the implementation contract. - * - * @return instance The address of the new pair instance - */ - function cloneERC721ERC20Pair( - address implementation, - ILSSVMPairFactoryLike factory, - ICurve bondingCurve, - IERC721 nft, - uint8 poolType, - address propertyChecker, - ERC20 token - ) internal returns (address instance) { - assembly { - let ptr := mload(0x40) - - // ------------------------------------------------------------------------------------------------------------- - // CREATION (9 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // creation size = 09 - // runtime size = 9a - // 60 runtime | PUSH1 runtime (r) | r | – - // 3d | RETURNDATASIZE | 0 r | – - // 81 | DUP2 | r 0 r | – - // 60 creation | PUSH1 creation (c) | c r 0 r | – - // 3d | RETURNDATASIZE | 0 c r 0 r | – - // 39 | CODECOPY | 0 r | [0-runSize): runtime code - // f3 | RETURN | | [0-runSize): runtime code - - // ------------------------------------------------------------------------------------------------------------- - // RUNTIME (53 bytes of code + 101 bytes of extra data = 154 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // extra data size = 65 - // 3d | RETURNDATASIZE | 0 | – - // 3d | RETURNDATASIZE | 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 0 | – - // 36 | CALLDATASIZE | cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – - // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata - // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata - // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data - // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata - // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - mstore(ptr, hex"609a3d8160093d39f33d3d3d3d363d3d37606560353639366065013d73000000") - mstore(add(ptr, 0x1d), shl(0x60, implementation)) - - // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) - // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data - // 57 | JUMPI | 0 rds | [0, rds) = return data - // fd | REVERT | – | [0, rds) = return data - // 5b | JUMPDEST | 0 rds | [0, rds) = return data - // f3 | RETURN | – | [0, rds) = return data - mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - - // ------------------------------------------------------------------------------------------------------------- - // EXTRA DATA (101 bytes) - // ------------------------------------------------------------------------------------------------------------- - - mstore(add(ptr, 0x3e), shl(0x60, factory)) - mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) - mstore(add(ptr, 0x66), shl(0x60, nft)) - mstore8(add(ptr, 0x7a), poolType) - mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) - mstore(add(ptr, 0x8f), shl(0x60, token)) - - // ------------------------------------------------------------------------------------------------------------- - // Total length is 163 (a3) bytes - // ------------------------------------------------------------------------------------------------------------- - - instance := create(0, ptr, 0xa3) - } - } - - /** - * @notice Checks if a contract is a clone of a LSSVMPairETH. - * @dev Only checks the runtime bytecode, does not check the extra data. - * @param factory the factory that deployed the clone - * @param implementation the LSSVMPairETH implementation contract - * @param query the contract to check - * @return result True if the contract is a clone, false otherwise - */ - function isERC721ETHPairClone(address factory, address implementation, address query) - internal - view - returns (bool result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"3d3d3d3d363d3d37605160353639366051013d73000000000000000000000000") - mstore(add(ptr, 0x14), shl(0x60, implementation)) - mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - mstore(add(ptr, 0x35), shl(0x60, factory)) - - // compare expected bytecode with that of the queried contract - let other := add(ptr, 0x49) - extcodecopy(query, other, 0, 0x49) - result := - and( - eq(mload(ptr), mload(other)), - and( - eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), - eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) - ) - ) - } - } - - /** - * @notice Checks if a contract is a clone of a LSSVMPairERC20. - * @dev Only checks the runtime bytecode, does not check the extra data. - * @param implementation the LSSVMPairERC20 implementation contract - * @param query the contract to check - * @return result True if the contract is a clone, false otherwise - */ - function isERC721ERC20PairClone(address factory, address implementation, address query) - internal - view - returns (bool result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"3d3d3d3d363d3d37606560353639366065013d73000000000000000000000000") - mstore(add(ptr, 0x14), shl(0x60, implementation)) - mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - mstore(add(ptr, 0x35), shl(0x60, factory)) - - // compare expected bytecode with that of the queried contract - let other := add(ptr, 0x49) - extcodecopy(query, other, 0, 0x49) - result := - and( - eq(mload(ptr), mload(other)), - and( - eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), - eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) - ) - ) - } - } - - /** - * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - * - * During the delegate call, extra data is copied into the calldata which can then be - * accessed by the implementation contract. - * - * @return instance The address of the new pair instance - */ - function cloneERC1155ETHPair( - address implementation, - ILSSVMPairFactoryLike factory, - ICurve bondingCurve, - IERC1155 nft, - uint8 poolType, - uint256 nftId - ) internal returns (address instance) { - assembly { - let ptr := mload(0x40) - - // ------------------------------------------------------------------------------------------------------------- - // CREATION (9 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // creation size = 09 - // runtime size = 92 - // 60 runtime | PUSH1 runtime (r) | r | – - // 3d | RETURNDATASIZE | 0 r | – - // 81 | DUP2 | r 0 r | – - // 60 creation | PUSH1 creation (c) | c r 0 r | – - // 3d | RETURNDATASIZE | 0 c r 0 r | – - // 39 | CODECOPY | 0 r | [0-runSize): runtime code - // f3 | RETURN | | [0-runSize): runtime code - - // ------------------------------------------------------------------------------------------------------------- - // RUNTIME (53 bytes of code + 93 bytes of extra data = 146 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // extra data size = 5d - // 3d | RETURNDATASIZE | 0 | – - // 3d | RETURNDATASIZE | 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 0 | – - // 36 | CALLDATASIZE | cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – - // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata - // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata - // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data - // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata - // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - mstore(ptr, hex"60923d8160093d39f33d3d3d3d363d3d37605d6035363936605d013d73000000") - mstore(add(ptr, 0x1d), shl(0x60, implementation)) - - // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) - // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data - // 57 | JUMPI | 0 rds | [0, rds) = return data - // fd | REVERT | – | [0, rds) = return data - // 5b | JUMPDEST | 0 rds | [0, rds) = return data - // f3 | RETURN | – | [0, rds) = return data - mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - - // ------------------------------------------------------------------------------------------------------------- - // EXTRA DATA (93 bytes) - // ------------------------------------------------------------------------------------------------------------- - - mstore(add(ptr, 0x3e), shl(0x60, factory)) - mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) - mstore(add(ptr, 0x66), shl(0x60, nft)) - mstore8(add(ptr, 0x7a), poolType) - mstore(add(ptr, 0x7b), nftId) - - instance := create(0, ptr, 0x9b) - } - } - - /** - * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - * - * During the delegate call, extra data is copied into the calldata which can then be - * accessed by the implementation contract. - * - * @return instance The address of the new pair instance - */ - function cloneERC1155ERC20Pair( - address implementation, - ILSSVMPairFactoryLike factory, - ICurve bondingCurve, - IERC1155 nft, - uint8 poolType, - uint256 nftId, - ERC20 token - ) internal returns (address instance) { - assembly { - let ptr := mload(0x40) - - // ------------------------------------------------------------------------------------------------------------- - // CREATION (9 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // creation size = 09 - // runtime size = a6 - // 60 runtime | PUSH1 runtime (r) | r | – - // 3d | RETURNDATASIZE | 0 r | – - // 81 | DUP2 | r 0 r | – - // 60 creation | PUSH1 creation (c) | c r 0 r | – - // 3d | RETURNDATASIZE | 0 c r 0 r | – - // 39 | CODECOPY | 0 r | [0-runSize): runtime code - // f3 | RETURN | | [0-runSize): runtime code - - // ------------------------------------------------------------------------------------------------------------- - // RUNTIME (53 bytes of code + 113 bytes of extra data = 166 bytes) - // ------------------------------------------------------------------------------------------------------------- - - // extra data size = 71 - // 3d | RETURNDATASIZE | 0 | – - // 3d | RETURNDATASIZE | 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 0 0 | – - // 36 | CALLDATASIZE | cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – - // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – - // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata - // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata - // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data - // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata - // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - mstore(ptr, hex"60a63d8160093d39f33d3d3d3d363d3d37607160353639366071013d73000000") - mstore(add(ptr, 0x1d), shl(0x60, implementation)) - - // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData - // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) - // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data - // 57 | JUMPI | 0 rds | [0, rds) = return data - // fd | REVERT | – | [0, rds) = return data - // 5b | JUMPDEST | 0 rds | [0, rds) = return data - // f3 | RETURN | – | [0, rds) = return data - mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - - // ------------------------------------------------------------------------------------------------------------- - // EXTRA DATA (113 bytes) - // ------------------------------------------------------------------------------------------------------------- - - mstore(add(ptr, 0x3e), shl(0x60, factory)) - mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) - mstore(add(ptr, 0x66), shl(0x60, nft)) - mstore8(add(ptr, 0x7a), poolType) - mstore(add(ptr, 0x7b), nftId) - mstore(add(ptr, 0x9b), shl(0x60, token)) - - instance := create(0, ptr, 0xaf) - } - } - - /** - * @notice Checks if a contract is a clone of a LSSVMPairERC1155ETH. - * @dev Only checks the runtime bytecode, does not check the extra data. - * @param factory the factory that deployed the clone - * @param implementation the LSSVMPairERC1155ETH implementation contract - * @param query the contract to check - * @return result True if the contract is a clone, false otherwise - */ - function isERC1155ETHPairClone(address factory, address implementation, address query) - internal - view - returns (bool result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"3d3d3d3d363d3d37605d6035363936605d013d73000000000000000000000000") - mstore(add(ptr, 0x14), shl(0x60, implementation)) - mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - mstore(add(ptr, 0x35), shl(0x60, factory)) - - // compare expected bytecode with that of the queried contract - let other := add(ptr, 0x49) - extcodecopy(query, other, 0, 0x49) - result := - and( - eq(mload(ptr), mload(other)), - and( - eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), - eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) - ) - ) - } - } - - /** - * @notice Checks if a contract is a clone of a LSSVMPairERC1155ERC20. - * @dev Only checks the runtime bytecode, does not check the extra data. - * @param implementation the LSSVMPairERC1155ERC20 implementation contract - * @param query the contract to check - * @return result True if the contract is a clone, false otherwise - */ - function isERC1155ERC20PairClone(address factory, address implementation, address query) - internal - view - returns (bool result) - { - // solhint-disable-next-line no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr, hex"3d3d3d3d363d3d37607160353639366071013d73000000000000000000000000") - mstore(add(ptr, 0x14), shl(0x60, implementation)) - mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") - mstore(add(ptr, 0x35), shl(0x60, factory)) - - // compare expected bytecode with that of the queried contract - let other := add(ptr, 0x49) - extcodecopy(query, other, 0, 0x49) - result := - and( - eq(mload(ptr), mload(other)), - and( - eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), - eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) - ) - ) - } - } -} - -// src/erc721/LSSVMPairERC721.sol - -/** - * @title LSSVMPairERC721 - * @author boredGenius, 0xmons, 0xCygaar - * @notice An NFT/Token pair for an ERC721 NFT - */ -abstract contract LSSVMPairERC721 is LSSVMPair { - error LSSVMPairERC721__PropertyCheckFailed(); - error LSSVMPairERC721__NeedPropertyChecking(); - - /** - * External state-changing functions - */ - - /** - * @inheritdoc LSSVMPair - */ - function swapTokenForSpecificNFTs( - uint256[] calldata nftIds, - uint256 maxExpectedTokenInput, - address nftRecipient, - bool isRouter, - address routerCaller - ) external payable virtual override returns (uint256) { - // Store locally to remove extra calls - factory().openLock(); - - // Input validation - { - PoolType _poolType = poolType(); - if (_poolType == PoolType.TOKEN) revert LSSVMPair__WrongPoolType(); - if (nftIds.length == 0) revert LSSVMPair__ZeroSwapAmount(); - } - - // Call bonding curve for pricing information - uint256 protocolFee; - uint256 tradeFee; - uint256 inputAmountExcludingRoyalty; - (tradeFee, protocolFee, inputAmountExcludingRoyalty) = - _calculateBuyInfoAndUpdatePoolParams(nftIds.length, bondingCurve(), factory()); - - // Calculate royalties - (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = - _calculateRoyalties(nftIds[0], inputAmountExcludingRoyalty - protocolFee - tradeFee); - - // Revert if the input amount is too large - if (royaltyTotal + inputAmountExcludingRoyalty > maxExpectedTokenInput) { - revert LSSVMPair__DemandedInputTooLarge(); - } - - _pullTokenInputs({ - inputAmountExcludingRoyalty: inputAmountExcludingRoyalty, - royaltyAmounts: royaltyAmounts, - royaltyRecipients: royaltyRecipients, - royaltyTotal: royaltyTotal, - tradeFeeAmount: 2 * tradeFee, - isRouter: isRouter, - routerCaller: routerCaller, - protocolFee: protocolFee - }); - - { - _sendSpecificNFTsToRecipient(IERC721(nft()), nftRecipient, nftIds); - } - - _refundTokenToSender(royaltyTotal + inputAmountExcludingRoyalty); - - factory().closeLock(); - - emit SwapNFTOutPair(royaltyTotal + inputAmountExcludingRoyalty, nftIds); - - return (royaltyTotal + inputAmountExcludingRoyalty); - } - - /** - * @inheritdoc LSSVMPair - */ - function swapNFTsForToken( - uint256[] calldata nftIds, - uint256 minExpectedTokenOutput, - address payable tokenRecipient, - bool isRouter, - address routerCaller - ) external virtual override returns (uint256 outputAmount) { - if (propertyChecker() != address(0)) revert LSSVMPairERC721__NeedPropertyChecking(); - - return _swapNFTsForToken(nftIds, minExpectedTokenOutput, tokenRecipient, isRouter, routerCaller); - } - - /** - * @notice Sends a set of NFTs to the pair in exchange for token - * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. - * @param nftIds The list of IDs of the NFTs to sell to the pair - * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual - * amount is less than this value, the transaction will be reverted. - * @param tokenRecipient The recipient of the token output - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for - * ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for - * ETH pairs. - * @param propertyCheckerParams Parameters to pass into the pair's underlying property checker - * @return outputAmount The amount of token received - */ - function swapNFTsForToken( - uint256[] calldata nftIds, - uint256 minExpectedTokenOutput, - address payable tokenRecipient, - bool isRouter, - address routerCaller, - bytes calldata propertyCheckerParams - ) external virtual returns (uint256 outputAmount) { - if (!IPropertyChecker(propertyChecker()).hasProperties(nftIds, propertyCheckerParams)) { - revert LSSVMPairERC721__PropertyCheckFailed(); - } - - return _swapNFTsForToken(nftIds, minExpectedTokenOutput, tokenRecipient, isRouter, routerCaller); - } - - /** - * View functions - */ - - /** - * @notice Returns the property checker address - */ - function propertyChecker() public pure returns (address _propertyChecker) { - uint256 paramsLength = _immutableParamsLength(); - assembly { - _propertyChecker := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 61))) - } - } - - /** - * Internal functions - */ - - function _swapNFTsForToken( - uint256[] calldata nftIds, - uint256 minExpectedTokenOutput, - address payable tokenRecipient, - bool isRouter, - address routerCaller - ) internal virtual returns (uint256 outputAmount) { - // Store locally to remove extra calls - ILSSVMPairFactoryLike _factory = factory(); - - _factory.openLock(); - - // Input validation - { - PoolType _poolType = poolType(); - if (_poolType == PoolType.NFT) revert LSSVMPair__WrongPoolType(); - if (nftIds.length == 0) revert LSSVMPair__ZeroSwapAmount(); - } - - // Call bonding curve for pricing information - uint256 protocolFee; - (protocolFee, outputAmount) = _calculateSellInfoAndUpdatePoolParams(nftIds.length, bondingCurve(), _factory); - - // Compute royalties - (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) = - _calculateRoyalties(nftIds[0], outputAmount); - - // Deduct royalties from outputAmount - unchecked { - // Safe because we already require outputAmount >= royaltyTotal in calculateRoyalties() - outputAmount -= royaltyTotal; - } - - if (outputAmount < minExpectedTokenOutput) revert LSSVMPair__OutputTooSmall(); - - _takeNFTsFromSender(IERC721(nft()), nftIds, _factory, isRouter, routerCaller); - - _sendTokenOutput(tokenRecipient, outputAmount); - for (uint256 i; i < royaltyRecipients.length;) { - _sendTokenOutput(royaltyRecipients[i], royaltyAmounts[i]); - unchecked { - ++i; - } - } - - _sendTokenOutput(payable(address(_factory)), protocolFee); - - _factory.closeLock(); - - emit SwapNFTInPair(outputAmount, nftIds); - } - - /** - * @notice Sends specific NFTs to a recipient address - * @dev Even though we specify the NFT address here, this internal function is only - * used to send NFTs associated with this specific pool. - * @param _nft The address of the NFT to send - * @param nftRecipient The receiving address for the NFTs - * @param nftIds The specific IDs of NFTs to send - */ - function _sendSpecificNFTsToRecipient(IERC721 _nft, address nftRecipient, uint256[] calldata nftIds) - internal - virtual - { - // Send NFTs to recipient - uint256 numNFTs = nftIds.length; - for (uint256 i; i < numNFTs;) { - _nft.transferFrom(address(this), nftRecipient, nftIds[i]); - - unchecked { - ++i; - } - } - } - - /** - * @notice Takes NFTs from the caller and sends them into the pair's asset recipient - * @dev This is used by the LSSVMPair's swapNFTForToken function. - * @param _nft The NFT collection to take from - * @param nftIds The specific NFT IDs to take - * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. - * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. - */ - function _takeNFTsFromSender( - IERC721 _nft, - uint256[] calldata nftIds, - ILSSVMPairFactoryLike _factory, - bool isRouter, - address routerCaller - ) internal virtual { - { - address _assetRecipient = getAssetRecipient(); - uint256 numNFTs = nftIds.length; - - if (isRouter) { - // Verify if router is allowed - LSSVMRouter router = LSSVMRouter(payable(msg.sender)); - (bool routerAllowed,) = _factory.routerStatus(router); - if (!routerAllowed) revert LSSVMPair__NotRouter(); - - // Call router to pull NFTs - // If more than 1 NFT is being transfered, and there is no property checker, we can do a balance check - // instead of an ownership check, as pools are indifferent between NFTs from the same collection - if ((numNFTs > 1) && (propertyChecker() == address(0))) { - uint256 beforeBalance = _nft.balanceOf(_assetRecipient); - for (uint256 i; i < numNFTs;) { - router.pairTransferNFTFrom(_nft, routerCaller, _assetRecipient, nftIds[i]); - - unchecked { - ++i; - } - } - if (_nft.balanceOf(_assetRecipient) - beforeBalance != numNFTs) { - revert LSSVMPair__NftNotTransferred(); - } - } - // Otherwise we need to pull each asset 1 at a time and verify ownership - else { - for (uint256 i; i < numNFTs;) { - router.pairTransferNFTFrom(_nft, routerCaller, _assetRecipient, nftIds[i]); - if (_nft.ownerOf(nftIds[i]) != _assetRecipient) revert LSSVMPair__NftNotTransferred(); - unchecked { - ++i; - } - } - } - } else { - // Pull NFTs directly from sender - for (uint256 i; i < numNFTs;) { - _nft.transferFrom(msg.sender, _assetRecipient, nftIds[i]); - unchecked { - ++i; - } - } - } - } - } - - /** - * Owner functions - */ - - /** - * @notice Rescues a specified set of NFTs owned by the pair to the owner address. (onlyOwner modifier is in the implemented function) - * @param a The NFT to transfer - * @param nftIds The list of IDs of the NFTs to send to the owner - */ - function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual override onlyOwner { - uint256 numNFTs = nftIds.length; - for (uint256 i; i < numNFTs;) { - a.safeTransferFrom(address(this), msg.sender, nftIds[i]); - unchecked { - ++i; - } - } - - if (a == IERC721(nft())) { - emit NFTWithdrawal(nftIds); - } - } - - /** - * @notice Rescues ERC1155 tokens from the pair to the owner. Only callable by the owner. - * @param a The NFT to transfer - * @param ids The NFT ids to transfer - * @param amounts The amounts of each id to transfer - */ - function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) - external - virtual - override - onlyOwner - { - a.safeBatchTransferFrom(address(this), msg.sender, ids, amounts, ""); - } -} - -// src/erc1155/LSSVMPairERC1155ERC20.sol - -/** - * @title An ERC1155 pair where the token is an ERC20 - * @author boredGenius, 0xmons, 0xCygaar - */ -contract LSSVMPairERC1155ERC20 is LSSVMPairERC1155, LSSVMPairERC20 { - uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 113; - - constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} - - /** - * Public functions - */ - - /** - * @inheritdoc LSSVMPair - */ - function pairVariant() public pure virtual override returns (ILSSVMPairFactoryLike.PairVariant) { - return ILSSVMPairFactoryLike.PairVariant.ERC1155_ERC20; - } - - /** - * Internal functions - */ - - /** - * @inheritdoc LSSVMPair - * @dev see LSSVMPairCloner for params length calculation - */ - function _immutableParamsLength() internal pure override returns (uint256) { - return IMMUTABLE_PARAMS_LENGTH; - } -} - -// src/erc1155/LSSVMPairERC1155ETH.sol - -/** - * @title An ERC1155 pair where the token is an ETH - * @author boredGenius, 0xmons, 0xCygaar - */ -contract LSSVMPairERC1155ETH is LSSVMPairERC1155, LSSVMPairETH { - uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 93; - - constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} - - /** - * Public functions - */ - - /** - * @inheritdoc LSSVMPair - */ - function pairVariant() public pure virtual override returns (ILSSVMPairFactoryLike.PairVariant) { - return ILSSVMPairFactoryLike.PairVariant.ERC1155_ETH; - } - - /** - * Internal functions - */ - - /** - * @inheritdoc LSSVMPair - * @dev see LSSVMPairCloner for params length calculation - */ - function _immutableParamsLength() internal pure override returns (uint256) { - return IMMUTABLE_PARAMS_LENGTH; - } -} - -// src/erc721/LSSVMPairERC721ERC20.sol - -/** - * @title An NFT/Token pair where the token is an ERC20 - * @author boredGenius, 0xmons, 0xCygaar - */ -contract LSSVMPairERC721ERC20 is LSSVMPairERC721, LSSVMPairERC20 { - uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 101; - - constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} - - /** - * Public functions - */ - - /** - * @inheritdoc LSSVMPair - */ - function pairVariant() public pure override returns (ILSSVMPairFactoryLike.PairVariant) { - return ILSSVMPairFactoryLike.PairVariant.ERC721_ERC20; - } - - /** - * Internal functions - */ - - /** - * @inheritdoc LSSVMPair - * @dev see LSSVMPairCloner for params length calculation - */ - function _immutableParamsLength() internal pure override returns (uint256) { - return IMMUTABLE_PARAMS_LENGTH; - } -} - -// src/erc721/LSSVMPairERC721ETH.sol - -/** - * @title An NFT/Token pair where the token is ETH - * @author boredGenius, 0xmons, 0xCygaar - */ -contract LSSVMPairERC721ETH is LSSVMPairERC721, LSSVMPairETH { - uint256 internal constant IMMUTABLE_PARAMS_LENGTH = 81; - - constructor(IRoyaltyEngineV1 royaltyEngine) LSSVMPair(royaltyEngine) {} - - /** - * Public functions - */ - - /** - * @inheritdoc LSSVMPair - */ - function pairVariant() public pure override returns (ILSSVMPairFactoryLike.PairVariant) { - return ILSSVMPairFactoryLike.PairVariant.ERC721_ETH; - } - - /** - * Internal functions - */ - - /** - * @inheritdoc LSSVMPair - * @dev see LSSVMPairCloner for params length calculation - */ - function _immutableParamsLength() internal pure override returns (uint256) { - return IMMUTABLE_PARAMS_LENGTH; - } -} - -// src/LSSVMPairFactory.sol - -/** - * @notice Imports for authAllowedForToken (forked from manifold.xyz Royalty Registry) - */ - -/** - * @title The factory contract used to deploy new pairs - * @author boredGenius, 0xmons, 0xCygaar - */ -contract LSSVMPairFactory is Owned, ILSSVMPairFactoryLike { - using LSSVMPairCloner for address; - using AddressUpgradeable for address; - using SafeTransferLib for address payable; - using SafeTransferLib for ERC20; - - uint256 internal constant MAX_PROTOCOL_FEE = 0.1e18; // 10%, must <= 1 - MAX_FEE - - LSSVMPairERC721ETH public immutable erc721ETHTemplate; - LSSVMPairERC721ERC20 public immutable erc721ERC20Template; - LSSVMPairERC1155ETH public immutable erc1155ETHTemplate; - LSSVMPairERC1155ERC20 public immutable erc1155ERC20Template; - address payable public override protocolFeeRecipient; - - // Units are in base 1e18 - uint256 public override protocolFeeMultiplier; - - mapping(ICurve => bool) public bondingCurveAllowed; - mapping(address => bool) public override callAllowed; - - // Data structures for settings logic - mapping(address => mapping(address => bool)) public settingsForCollection; - mapping(address => address) public settingsForPair; - - struct RouterStatus { - bool allowed; - bool wasEverTouched; - } - - mapping(LSSVMRouter => RouterStatus) public override routerStatus; - - address private constant _NOT_ENTERED = address(1); - address private _caller; - - event NewERC721Pair(address indexed poolAddress, uint256[] initialIds); - event NewERC1155Pair(address indexed poolAddress, uint256 initialBalance); - event ERC20Deposit(address indexed poolAddress, uint256 amount); - event NFTDeposit(address indexed poolAddress, uint256[] ids); - event ERC1155Deposit(address indexed poolAddress, uint256 indexed id, uint256 amount); - event ProtocolFeeRecipientUpdate(address indexed recipientAddress); - event ProtocolFeeMultiplierUpdate(uint256 newMultiplier); - event BondingCurveStatusUpdate(ICurve indexed bondingCurve, bool isAllowed); - event CallTargetStatusUpdate(address indexed target, bool isAllowed); - event RouterStatusUpdate(LSSVMRouter indexed router, bool isAllowed); - - error LSSVMPairFactory__FeeTooLarge(); - error LSSVMPairFactory__BondingCurveNotWhitelisted(); - error LSSVMPairFactory__ReentrantCall(); - error LSSVMPairFactory__ZeroAddress(); - error LSSVMPairFactory__CannotCallRouter(); - error LSSVMPairFactory__UnauthorizedCaller(); - error LSSVMPairFactory__InvalidPair(); - error LSSVMPairFactory__SettingsNotEnabledForCollection(); - error LSSVMPairFactory__SettingsNotEnabledForPair(); - - constructor( - LSSVMPairERC721ETH _erc721ETHTemplate, - LSSVMPairERC721ERC20 _erc721ERC20Template, - LSSVMPairERC1155ETH _erc1155ETHTemplate, - LSSVMPairERC1155ERC20 _erc1155ERC20Template, - address payable _protocolFeeRecipient, - uint256 _protocolFeeMultiplier, - address _owner - ) Owned(_owner) { - erc721ETHTemplate = _erc721ETHTemplate; - erc721ERC20Template = _erc721ERC20Template; - erc1155ETHTemplate = _erc1155ETHTemplate; - erc1155ERC20Template = _erc1155ERC20Template; - protocolFeeRecipient = _protocolFeeRecipient; - if (_protocolFeeMultiplier > MAX_PROTOCOL_FEE) revert LSSVMPairFactory__FeeTooLarge(); - protocolFeeMultiplier = _protocolFeeMultiplier; - _caller = _NOT_ENTERED; - } - - /** - * External functions - */ - - /** - * @notice Creates a pair contract using EIP-1167. - * @param _nft The NFT contract of the collection the pair trades - * @param _bondingCurve The bonding curve for the pair to price NFTs, must be whitelisted - * @param _assetRecipient The address that will receive the assets traders give during trades. - * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. - * @param _poolType TOKEN, NFT, or TRADE - * @param _delta The delta value used by the bonding curve. The meaning of delta depends on the specific curve. - * @param _fee The fee taken by the LP in each trade. Can only be non-zero if _poolType is Trade. - * @param _spotPrice The initial selling spot price - * @param _propertyChecker The contract to use for verifying properties of IDs sent in - * @param _initialNFTIDs The list of IDs of NFTs to transfer from the sender to the pair - * @return pair The new pair - */ - function createPairERC721ETH( - IERC721 _nft, - ICurve _bondingCurve, - address payable _assetRecipient, - LSSVMPair.PoolType _poolType, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - address _propertyChecker, - uint256[] calldata _initialNFTIDs - ) external payable returns (LSSVMPairERC721ETH pair) { - if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); - - pair = LSSVMPairERC721ETH( - payable( - address(erc721ETHTemplate).cloneERC721ETHPair( - this, _bondingCurve, _nft, uint8(_poolType), _propertyChecker - ) - ) - ); - - _initializePairERC721ETH(pair, _nft, _assetRecipient, _delta, _fee, _spotPrice, _initialNFTIDs); - emit NewERC721Pair(address(pair), _initialNFTIDs); - } - - struct CreateERC721ERC20PairParams { - ERC20 token; - IERC721 nft; - ICurve bondingCurve; - address payable assetRecipient; - LSSVMPair.PoolType poolType; - uint128 delta; - uint96 fee; - uint128 spotPrice; - address propertyChecker; - uint256[] initialNFTIDs; - uint256 initialTokenBalance; - } - - /** - * @notice Creates a pair contract using EIP-1167. - * @param params The info used to create a new pair. This includes: - * - token: The ERC20 token the pair trades - * - nft: The NFT contract of the collection the pair trades - * - bondingCurve: The bonding curve for the pair to price NFTs, must be whitelisted - * - assetRecipient: The address that will receive the assets traders give during trades. - * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. - * - poolType: TOKEN, NFT, or TRADE - * - delta: The delta value used by the bonding curve. The meaning of delta depends on the specific curve. - * - fee: The fee taken by the LP in each trade. Can only be non-zero if poolType is Trade. - * - spotPrice: Param 1 for the bonding curve, usually used for start price - * - delta: Param 2 for the bonding curve, usually used for dynamic adjustment - * - propertyChecker: The contract to use for verifying properties of IDs sent in - * - initialNFTIDs: The list of IDs of NFTs to transfer from the sender to the pair - * - initialTokenBalance: The initial token balance sent from the sender to the new pair - * @return pair The new pair - */ - function createPairERC721ERC20(CreateERC721ERC20PairParams calldata params) - external - returns (LSSVMPairERC721ERC20 pair) - { - if (!bondingCurveAllowed[params.bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); - - pair = LSSVMPairERC721ERC20( - payable( - address(erc721ERC20Template).cloneERC721ERC20Pair( - this, params.bondingCurve, params.nft, uint8(params.poolType), params.propertyChecker, params.token - ) - ) - ); - - _initializePairERC721ERC20( - pair, - params.token, - params.nft, - params.assetRecipient, - params.delta, - params.fee, - params.spotPrice, - params.initialNFTIDs, - params.initialTokenBalance - ); - emit NewERC721Pair(address(pair), params.initialNFTIDs); - } - /** - * @notice Creates a pair contract using EIP-1167. - * @param _nft The NFT contract of the collection the pair trades - * @param _bondingCurve The bonding curve for the pair to price NFTs, must be whitelisted - * @param _assetRecipient The address that will receive the assets traders give during trades. - * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. - * @param _poolType TOKEN, NFT, or TRADE - * @param _delta The delta value used by the bonding curve. The meaning of delta depends on the specific curve. - * @param _fee The fee taken by the LP in each trade. Can only be non-zero if _poolType is Trade. - * @param _spotPrice The initial selling spot price - * @param _nftId The ID of the NFT to trade - * @param _initialNFTBalance The amount of NFTs to transfer from the sender to the pair - * @return pair The new pair - */ - - function createPairERC1155ETH( - IERC1155 _nft, - ICurve _bondingCurve, - address payable _assetRecipient, - LSSVMPair.PoolType _poolType, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - uint256 _nftId, - uint256 _initialNFTBalance - ) external payable returns (LSSVMPairERC1155ETH pair) { - if (!bondingCurveAllowed[_bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); - - pair = LSSVMPairERC1155ETH( - payable( - address(erc1155ETHTemplate).cloneERC1155ETHPair(this, _bondingCurve, _nft, uint8(_poolType), _nftId) - ) - ); - - _initializePairERC1155ETH(pair, _nft, _assetRecipient, _delta, _fee, _spotPrice, _nftId, _initialNFTBalance); - emit NewERC1155Pair(address(pair), _initialNFTBalance); - } - - struct CreateERC1155ERC20PairParams { - ERC20 token; - IERC1155 nft; - ICurve bondingCurve; - address payable assetRecipient; - LSSVMPair.PoolType poolType; - uint128 delta; - uint96 fee; - uint128 spotPrice; - uint256 nftId; - uint256 initialNFTBalance; - uint256 initialTokenBalance; - } - - /** - * @notice Creates a pair contract using EIP-1167. - * @param params The info used to create a new pair. This includes: - * - token: The ERC20 token the pair trades - * - nft: The NFT contract of the collection the pair trades - * - bondingCurve: The bonding curve for the pair to price NFTs, must be whitelisted - * - assetRecipient: The address that will receive the assets traders give during trades. - * If set to address(0), assets will be sent to the pool address. Not available to TRADE pools. - * - poolType: TOKEN, NFT, or TRADE - * - delta: The delta value used by the bonding curve. The meaning of delta depends on the specific curve. - * - fee: The fee taken by the LP in each trade. Can only be non-zero if poolType is Trade. - * - spotPrice: Param 1 for the bonding curve, usually used for start price - * - nftId: The ERC1155 nft id that this pair trades - * - initialNFTBalance: The initial NFT balance sent from the sender to the new pair - * - initialTokenBalance: The initial token balance sent from the sender to the new pair - * @return pair The new pair - */ - function createPairERC1155ERC20(CreateERC1155ERC20PairParams calldata params) - external - returns (LSSVMPairERC1155ERC20 pair) - { - if (!bondingCurveAllowed[params.bondingCurve]) revert LSSVMPairFactory__BondingCurveNotWhitelisted(); - - pair = LSSVMPairERC1155ERC20( - payable( - address(erc1155ERC20Template).cloneERC1155ERC20Pair( - this, params.bondingCurve, params.nft, uint8(params.poolType), params.nftId, params.token - ) - ) - ); - - _initializePairERC1155ERC20( - pair, - params.token, - params.nft, - params.assetRecipient, - params.delta, - params.fee, - params.spotPrice, - params.nftId, - params.initialNFTBalance, - params.initialTokenBalance - ); - emit NewERC1155Pair(address(pair), params.initialNFTBalance); - } - - function isValidPair(address pairAddress) public view returns (bool) { - PairVariant variant = LSSVMPair(pairAddress).pairVariant(); - if (variant == PairVariant.ERC721_ETH) { - return LSSVMPairCloner.isERC721ETHPairClone(address(this), address(erc721ETHTemplate), pairAddress); - } else if (variant == PairVariant.ERC721_ERC20) { - return LSSVMPairCloner.isERC721ERC20PairClone(address(this), address(erc721ERC20Template), pairAddress); - } else if (variant == PairVariant.ERC1155_ETH) { - return LSSVMPairCloner.isERC1155ETHPairClone(address(this), address(erc1155ETHTemplate), pairAddress); - } else if (variant == PairVariant.ERC1155_ERC20) { - return LSSVMPairCloner.isERC1155ERC20PairClone(address(this), address(erc1155ERC20Template), pairAddress); - } else { - return false; - } - } - - function getPairNFTType(address pairAddress) public pure returns (PairNFTType) { - PairVariant variant = LSSVMPair(pairAddress).pairVariant(); - return PairNFTType(uint8(variant) / 2); - } - - function getPairTokenType(address pairAddress) public pure returns (PairTokenType) { - PairVariant variant = LSSVMPair(pairAddress).pairVariant(); - return PairTokenType(uint8(variant) % 2); - } - - function openLock() public { - if (_caller == msg.sender) revert LSSVMPairFactory__ReentrantCall(); - _caller = msg.sender; - } - - function closeLock() public { - if (_caller != msg.sender) revert LSSVMPairFactory__ReentrantCall(); - _caller = _NOT_ENTERED; - } - - /** - * @notice Checks if an address is an allowed auth for a token - * @param tokenAddress The token address to check - * @param proposedAuthAddress The auth address to check - * @return True if the proposedAuthAddress is a valid auth for the tokenAddress, false otherwise. - */ - function authAllowedForToken(address tokenAddress, address proposedAuthAddress) public view returns (bool) { - // Check for admin interface - if ( - ERC165Checker.supportsInterface(tokenAddress, type(IAdminControl).interfaceId) - && IAdminControl(tokenAddress).isAdmin(proposedAuthAddress) - ) { - return true; - } - // Check for owner - try OwnableUpgradeable(tokenAddress).owner() returns (address owner) { - if (owner == proposedAuthAddress) return true; - - if (owner.isContract()) { - try OwnableUpgradeable(owner).owner() returns (address passThroughOwner) { - if (passThroughOwner == proposedAuthAddress) return true; - } catch {} - } - } catch {} - // Check for default OZ auth role - try IAccessControlUpgradeable(tokenAddress).hasRole(0x00, proposedAuthAddress) returns (bool hasRole) { - if (hasRole) return true; - } catch {} - // Nifty Gateway overrides - try INiftyBuilderInstance(tokenAddress).niftyRegistryContract() returns (address niftyRegistry) { - try INiftyRegistry(niftyRegistry).isValidNiftySender(proposedAuthAddress) returns (bool valid) { - if (valid) return true; - } catch {} - } catch {} - // Foundation overrides - try IFoundationTreasuryNode(tokenAddress).getFoundationTreasury() returns (address payable foundationTreasury) { - try IFoundationTreasury(foundationTreasury).isAdmin(proposedAuthAddress) returns (bool isAdmin) { - if (isAdmin) return true; - } catch {} - } catch {} - // DIGITALAX overrides - try IDigitalax(tokenAddress).accessControls() returns (address externalAccessControls) { - try IDigitalaxAccessControls(externalAccessControls).hasAdminRole(proposedAuthAddress) returns ( - bool hasRole - ) { - if (hasRole) return true; - } catch {} - } catch {} - // Art Blocks overrides - try IArtBlocks(tokenAddress).admin() returns (address admin) { - if (admin == proposedAuthAddress) return true; - } catch {} - return false; - } - - /** - * @notice Allows receiving ETH in order to receive protocol fees - */ - receive() external payable {} - - /** - * Admin functions - */ - - /** - * @notice Withdraws the ETH balance to the protocol fee recipient. - * Only callable by the owner. - */ - function withdrawETHProtocolFees() external onlyOwner { - protocolFeeRecipient.safeTransferETH(address(this).balance); - } - - /** - * @notice Withdraws ERC20 tokens to the protocol fee recipient. Only callable by the owner. - * @param token The token to transfer - * @param amount The amount of tokens to transfer - */ - function withdrawERC20ProtocolFees(ERC20 token, uint256 amount) external onlyOwner { - token.safeTransfer(protocolFeeRecipient, amount); - } - - /** - * @notice Changes the protocol fee recipient address. Only callable by the owner. - * @param _protocolFeeRecipient The new fee recipient - */ - function changeProtocolFeeRecipient(address payable _protocolFeeRecipient) external onlyOwner { - if (_protocolFeeRecipient == address(0)) revert LSSVMPairFactory__ZeroAddress(); - protocolFeeRecipient = _protocolFeeRecipient; - emit ProtocolFeeRecipientUpdate(_protocolFeeRecipient); - } - - /** - * @notice Changes the protocol fee multiplier. Only callable by the owner. - * @param _protocolFeeMultiplier The new fee multiplier, 18 decimals - */ - function changeProtocolFeeMultiplier(uint256 _protocolFeeMultiplier) external onlyOwner { - if (_protocolFeeMultiplier > MAX_PROTOCOL_FEE) revert LSSVMPairFactory__FeeTooLarge(); - protocolFeeMultiplier = _protocolFeeMultiplier; - emit ProtocolFeeMultiplierUpdate(_protocolFeeMultiplier); - } - - /** - * @notice Sets the whitelist status of a bonding curve contract. Only callable by the owner. - * @param bondingCurve The bonding curve contract - * @param isAllowed True to whitelist, false to remove from whitelist - */ - function setBondingCurveAllowed(ICurve bondingCurve, bool isAllowed) external onlyOwner { - bondingCurveAllowed[bondingCurve] = isAllowed; - emit BondingCurveStatusUpdate(bondingCurve, isAllowed); - } - - /** - * @notice Sets the whitelist status of a contract to be called arbitrarily by a pair. - * Only callable by the owner. - * @param target The target contract - * @param isAllowed True to whitelist, false to remove from whitelist - */ - function setCallAllowed(address payable target, bool isAllowed) external onlyOwner { - // Ensure target is not / was not ever a router - if (isAllowed) { - if (routerStatus[LSSVMRouter(target)].wasEverTouched) revert LSSVMPairFactory__CannotCallRouter(); - } - - callAllowed[target] = isAllowed; - emit CallTargetStatusUpdate(target, isAllowed); - } - - /** - * @notice Updates the router whitelist. Only callable by the owner. - * @param _router The router - * @param isAllowed True to whitelist, false to remove from whitelist - */ - function setRouterAllowed(LSSVMRouter _router, bool isAllowed) external onlyOwner { - // Ensure target is not arbitrarily callable by pairs - if (isAllowed) { - if (callAllowed[address(_router)]) revert LSSVMPairFactory__CannotCallRouter(); - } - routerStatus[_router] = RouterStatus({allowed: isAllowed, wasEverTouched: true}); - - emit RouterStatusUpdate(_router, isAllowed); - } - - /** - * @notice Returns the Settings for a pair if it currently has Settings - * @param pairAddress The address of the pair to look up - * @return settingsEnabled Whether or not the pair has custom settings - * @return bps The royalty basis points from the custom settings, 0 if there is no custom settings - */ - function getSettingsForPair(address pairAddress) public view returns (bool settingsEnabled, uint96 bps) { - address settingsAddress = settingsForPair[pairAddress]; - if (settingsAddress == address(0)) { - return (false, 0); - } - return ISettings(settingsAddress).getRoyaltyInfo(pairAddress); - } - - /** - * @notice Enables or disables an settings for a given NFT collection - * @param settings The address of the Settings contract - * @param collectionAddress The NFT project that the settings is toggled for - * @param enable Bool to determine whether to disable or enable the settings - */ - function toggleSettingsForCollection(address settings, address collectionAddress, bool enable) public { - if (!authAllowedForToken(collectionAddress, msg.sender)) revert LSSVMPairFactory__UnauthorizedCaller(); - if (enable) { - settingsForCollection[collectionAddress][settings] = true; - } else { - delete settingsForCollection[collectionAddress][settings]; - } - } - - /** - * @notice Enables an Settings for a given Pair - * @notice Only the owner of the Pair can call this function - * @notice The Settings must be enabled for the Pair's collection - * @param settings The address of the Settings contract - * @param pairAddress The address of the Pair contract - */ - function enableSettingsForPair(address settings, address pairAddress) public { - if (!isValidPair(pairAddress)) revert LSSVMPairFactory__InvalidPair(); - LSSVMPair pair = LSSVMPair(pairAddress); - if (pair.owner() != msg.sender) revert LSSVMPairFactory__UnauthorizedCaller(); - if (!settingsForCollection[address(pair.nft())][settings]) { - revert LSSVMPairFactory__SettingsNotEnabledForCollection(); - } - settingsForPair[pairAddress] = settings; - } - - /** - * @notice Disables an Settings for a given Pair - * @notice Only the owner of the Pair can call this function - * @notice The Settings must already be enabled for the Pair - * @param settings The address of the Settings contract - * @param pairAddress The address of the Pair contract - */ - function disableSettingsForPair(address settings, address pairAddress) public { - if (!isValidPair(pairAddress)) revert LSSVMPairFactory__InvalidPair(); - if (settingsForPair[pairAddress] != settings) revert LSSVMPairFactory__SettingsNotEnabledForPair(); - LSSVMPair pair = LSSVMPair(pairAddress); - if (pair.owner() != msg.sender) revert LSSVMPairFactory__UnauthorizedCaller(); - delete settingsForPair[pairAddress]; - } - - /** - * Internal functions - */ - - function _initializePairERC721ETH( - LSSVMPairERC721ETH _pair, - IERC721 _nft, - address payable _assetRecipient, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - uint256[] calldata _initialNFTIDs - ) internal { - // Initialize pair - _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); - - // Transfer initial ETH to pair - if (msg.value != 0) payable(address(_pair)).safeTransferETH(msg.value); - - // Transfer initial NFTs from sender to pair - uint256 numNFTs = _initialNFTIDs.length; - for (uint256 i; i < numNFTs;) { - _nft.transferFrom(msg.sender, address(_pair), _initialNFTIDs[i]); - - unchecked { - ++i; - } - } - } - - function _initializePairERC721ERC20( - LSSVMPairERC721ERC20 _pair, - ERC20 _token, - IERC721 _nft, - address payable _assetRecipient, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - uint256[] calldata _initialNFTIDs, - uint256 _initialTokenBalance - ) internal { - // Initialize pair - _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); - - // Transfer initial tokens to pair (if != 0) - if (_initialTokenBalance != 0) { - _token.safeTransferFrom(msg.sender, address(_pair), _initialTokenBalance); - } - - // Transfer initial NFTs from sender to pair - uint256 numNFTs = _initialNFTIDs.length; - for (uint256 i; i < numNFTs;) { - _nft.transferFrom(msg.sender, address(_pair), _initialNFTIDs[i]); - - unchecked { - ++i; - } - } - } - - function _initializePairERC1155ETH( - LSSVMPairERC1155ETH _pair, - IERC1155 _nft, - address payable _assetRecipient, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - uint256 _nftId, - uint256 _initialNFTBalance - ) internal { - // Initialize pair - _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); - - // Transfer initial ETH to pair - if (msg.value != 0) payable(address(_pair)).safeTransferETH(msg.value); - - // Transfer initial NFTs from sender to pair - if (_initialNFTBalance != 0) { - _nft.safeTransferFrom(msg.sender, address(_pair), _nftId, _initialNFTBalance, bytes("")); - } - } - - function _initializePairERC1155ERC20( - LSSVMPairERC1155ERC20 _pair, - ERC20 _token, - IERC1155 _nft, - address payable _assetRecipient, - uint128 _delta, - uint96 _fee, - uint128 _spotPrice, - uint256 _nftId, - uint256 _initialNFTBalance, - uint256 _initialTokenBalance - ) internal { - // Initialize pair - _pair.initialize(msg.sender, _assetRecipient, _delta, _fee, _spotPrice); - - // Transfer initial tokens to pair - if (_initialTokenBalance != 0) { - _token.safeTransferFrom(msg.sender, address(_pair), _initialTokenBalance); - } - - // Transfer initial NFTs from sender to pair - if (_initialNFTBalance != 0) { - _nft.safeTransferFrom(msg.sender, address(_pair), _nftId, _initialNFTBalance, bytes("")); - } - } - - /** - * @dev Used to deposit NFTs into a pair after creation and emit an event for indexing (if recipient is indeed a pair) - */ - function depositNFTs(IERC721 _nft, uint256[] calldata ids, address recipient) external { - uint256 numNFTs = ids.length; - - // Early return for trivial transfers - if (numNFTs == 0) return; - - // Transfer NFTs from caller to recipient - for (uint256 i; i < numNFTs;) { - _nft.transferFrom(msg.sender, recipient, ids[i]); - - unchecked { - ++i; - } - } - if (isValidPair(recipient) && (address(_nft) == LSSVMPair(recipient).nft())) { - emit NFTDeposit(recipient, ids); - } - } - - /** - * @dev Used to deposit ERC20s into a pair after creation and emit an event for indexing (if recipient is indeed an ERC20 pair and the token matches) - */ - function depositERC20(ERC20 token, address recipient, uint256 amount) external { - // Early return for trivial transfers - if (amount == 0) return; - - token.safeTransferFrom(msg.sender, recipient, amount); - if ( - isValidPair(recipient) && getPairTokenType(recipient) == PairTokenType.ERC20 - && token == LSSVMPairERC20(recipient).token() - ) { - emit ERC20Deposit(recipient, amount); - } - } - - /** - * @dev Used to deposit ERC1155 NFTs into a pair after creation and emit an event for indexing (if recipient is indeed a pair) - */ - function depositERC1155(IERC1155 nft, uint256 id, address recipient, uint256 amount) external { - if (amount == 0) return; - - nft.safeTransferFrom(msg.sender, recipient, id, amount, bytes("")); - - if ( - isValidPair(recipient) && getPairNFTType(recipient) == PairNFTType.ERC1155 - && address(nft) == LSSVMPair(recipient).nft() && id == LSSVMPairERC1155(recipient).nftId() - ) { - emit ERC1155Deposit(recipient, id, amount); - } - } -} - diff --git a/tests/functional/data/sources/OldCWIA.sol b/tests/functional/data/sources/OldCWIA.sol index 98531def3d..000a9544a0 100644 --- a/tests/functional/data/sources/OldCWIA.sol +++ b/tests/functional/data/sources/OldCWIA.sol @@ -774,4 +774,4 @@ contract Template is ClonesWithImmutableArgs, ClonesWithCallData { emit Cloned(address(clonedGreeter)); } } - + \ No newline at end of file diff --git a/tests/functional/data/sources/SequenceFactory.sol b/tests/functional/data/sources/SequenceFactory.sol index 6c87279c3b..699c84e1d4 100644 --- a/tests/functional/data/sources/SequenceFactory.sol +++ b/tests/functional/data/sources/SequenceFactory.sol @@ -83,4 +83,3 @@ contract Factory { } } - diff --git a/tests/functional/data/sources/SoladyFactory.sol b/tests/functional/data/sources/SoladyFactory.sol index 28d2681a5b..a1f23842e1 100644 --- a/tests/functional/data/sources/SoladyFactory.sol +++ b/tests/functional/data/sources/SoladyFactory.sol @@ -2873,5 +2873,4 @@ contract SoladyFactory { split = LibClone.clone(0, _implementation, abi.encodePacked(bytes32(0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7))); emit Target(split); } - } diff --git a/tests/functional/data/sources/SplitsCWIA.sol b/tests/functional/data/sources/SplitsCWIA.sol index 8897629980..30596e6e2f 100644 --- a/tests/functional/data/sources/SplitsCWIA.sol +++ b/tests/functional/data/sources/SplitsCWIA.sol @@ -452,4 +452,3 @@ contract SplitsCloneFactory { return address(implementation).predictDeterministicAddress(salt, data); } } - diff --git a/tests/functional/data/sources/SudoswapCWIA.sol b/tests/functional/data/sources/SudoswapCWIA.sol index 1654bbb9fa..5dae75f2aa 100644 --- a/tests/functional/data/sources/SudoswapCWIA.sol +++ b/tests/functional/data/sources/SudoswapCWIA.sol @@ -1,2 +1,3335 @@ -2025-01-07T12:32:18.943779Z ERROR foundry_compilers_artifacts_solc::sources: error="/home/thedance/workspace/lssvm2/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": No such file or directory (os error 2) -2025-01-07T12:32:18.953058Z ERROR foundry_compilers_artifacts_solc::sources: error="/home/thedance/workspace/lssvm2/lib/solmate/src/tokens/ERC20.sol": No such file or directory (os error 2) +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0 ^0.8.0 ^0.8.1 ^0.8.4; + +// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol + +// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol) + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + +// lib/openzeppelin-contracts/contracts/utils/Address.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol) + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResultFromTarget(target, success, returndata, errorMessage); + } + + /** + * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling + * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + * + * _Available since v4.8._ + */ + function verifyCallResultFromTarget( + address target, + bool success, + bytes memory returndata, + string memory errorMessage + ) internal view returns (bytes memory) { + if (success) { + if (returndata.length == 0) { + // only check isContract if the call was successful and the return data is empty + // otherwise we already know that it was a contract + require(isContract(target), "Address: call to non-contract"); + } + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + /** + * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason or using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + function _revert(bytes memory returndata, string memory errorMessage) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + /// @solidity memory-safe-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +// lib/solmate/src/tokens/ERC20.sol + +/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) +/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) +/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. +abstract contract ERC20 { + /*////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + event Transfer(address indexed from, address indexed to, uint256 amount); + + event Approval(address indexed owner, address indexed spender, uint256 amount); + + /*////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + + string public name; + + string public symbol; + + uint8 public immutable decimals; + + /*////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + + mapping(address => mapping(address => uint256)) public allowance; + + /*////////////////////////////////////////////////////////////// + EIP-2612 STORAGE + //////////////////////////////////////////////////////////////*/ + + uint256 internal immutable INITIAL_CHAIN_ID; + + bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; + + mapping(address => uint256) public nonces; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + + constructor( + string memory _name, + string memory _symbol, + uint8 _decimals + ) { + name = _name; + symbol = _symbol; + decimals = _decimals; + + INITIAL_CHAIN_ID = block.chainid; + INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); + } + + /*////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowance[msg.sender][spender] = amount; + + emit Approval(msg.sender, spender, amount); + + return true; + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + balanceOf[msg.sender] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(msg.sender, to, amount); + + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. + + if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; + + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } + + /*////////////////////////////////////////////////////////////// + EIP-2612 LOGIC + //////////////////////////////////////////////////////////////*/ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) public virtual { + require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); + + // Unchecked because the only math done is incrementing + // the owner's nonce which cannot realistically overflow. + unchecked { + address recoveredAddress = ecrecover( + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" + ), + owner, + spender, + value, + nonces[owner]++, + deadline + ) + ) + ) + ), + v, + r, + s + ); + + require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); + + allowance[recoveredAddress][spender] = value; + } + + emit Approval(owner, spender, value); + } + + function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { + return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); + } + + function computeDomainSeparator() internal view virtual returns (bytes32) { + return + keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(name)), + keccak256("1"), + block.chainid, + address(this) + ) + ); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + + function _mint(address to, uint256 amount) internal virtual { + totalSupply += amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(address(0), to, amount); + } + + function _burn(address from, uint256 amount) internal virtual { + balanceOf[from] -= amount; + + // Cannot underflow because a user's balance + // will never be larger than the total supply. + unchecked { + totalSupply -= amount; + } + + emit Transfer(from, address(0), amount); + } +} + +// src/bonding-curves/CurveErrorCodes.sol + +contract CurveErrorCodes { + enum Error { + OK, // No error + INVALID_NUMITEMS, // The numItem value is 0 + SPOT_PRICE_OVERFLOW, // The updated spot price doesn't fit into 128 bits + DELTA_OVERFLOW, // The updated delta doesn't fit into 128 bits + SPOT_PRICE_UNDERFLOW, // The updated spot price goes too low + AUCTION_ENDED // The auction has ended + } +} + +// src/lib/IOwnershipTransferReceiver.sol + +interface IOwnershipTransferReceiver { + function onOwnershipTransferred(address oldOwner, bytes memory data) external payable; +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol + +// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol) + +/** + * @dev Required interface of an ERC1155 compliant contract, as defined in the + * https://eips.ethereum.org/EIPS/eip-1155[EIP]. + * + * _Available since v3.1._ + */ +interface IERC1155 is IERC165 { + /** + * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + */ + event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); + + /** + * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all + * transfers. + */ + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + + /** + * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to + * `approved`. + */ + event ApprovalForAll(address indexed account, address indexed operator, bool approved); + + /** + * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + * + * If an {URI} event was emitted for `id`, the standard + * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value + * returned by {IERC1155MetadataURI-uri}. + */ + event URI(string value, uint256 indexed id); + + /** + * @dev Returns the amount of tokens of token type `id` owned by `account`. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function balanceOf(address account, uint256 id) external view returns (uint256); + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. + * + * Requirements: + * + * - `accounts` and `ids` must have the same length. + */ + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) + external + view + returns (uint256[] memory); + + /** + * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + * + * Emits an {ApprovalForAll} event. + * + * Requirements: + * + * - `operator` cannot be the caller. + */ + function setApprovalForAll(address operator, bool approved) external; + + /** + * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. + * + * See {setApprovalForAll}. + */ + function isApprovedForAll(address account, address operator) external view returns (bool); + + /** + * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. + * + * Emits a {TransferSingle} event. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. + * - `from` must have a balance of tokens of type `id` of at least `amount`. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the + * acceptance magic value. + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes calldata data + ) external; + + /** + * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. + * + * Emits a {TransferBatch} event. + * + * Requirements: + * + * - `ids` and `amounts` must have the same length. + * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the + * acceptance magic value. + */ + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts, + bytes calldata data + ) external; +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol) + +/** + * @dev _Available since v3.1._ + */ +interface IERC1155Receiver is IERC165 { + /** + * @dev Handles the receipt of a single ERC1155 token type. This function is + * called at the end of a `safeTransferFrom` after the balance has been updated. + * + * NOTE: To accept the transfer, this must return + * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + * (i.e. 0xf23a6e61, or its own function selector). + * + * @param operator The address which initiated the transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param id The ID of the token being transferred + * @param value The amount of tokens being transferred + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + */ + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + /** + * @dev Handles the receipt of a multiple ERC1155 token types. This function + * is called at the end of a `safeBatchTransferFrom` after the balances have + * been updated. + * + * NOTE: To accept the transfer(s), this must return + * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + * (i.e. 0xbc197c81, or its own function selector). + * + * @param operator The address which initiated the batch transfer (i.e. msg.sender) + * @param from The address which previously owned the token + * @param ids An array containing ids of each token being transferred (order and length must match values array) + * @param values An array containing amounts of each token being transferred (order and length must match ids array) + * @param data Additional data with no specified format + * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + */ + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + +// lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 + * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must + * understand this adds an external call which potentially creates a reentrancy vulnerability. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); +} + +// lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC721/utils/ERC721Holder.sol) + +/** + * @dev Implementation of the {IERC721Receiver} interface. + * + * Accepts all token transfers. + * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or {IERC721-setApprovalForAll}. + */ +contract ERC721Holder is IERC721Receiver { + /** + * @dev See {IERC721Receiver-onERC721Received}. + * + * Always returns `IERC721Receiver.onERC721Received.selector`. + */ + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC721Received.selector; + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol + +// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +// lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol + +// OpenZeppelin Contracts (last updated v4.8.0) (utils/introspection/ERC165Checker.sol) + +/** + * @dev Library used to query support of an interface declared via {IERC165}. + * + * Note that these functions return the actual result of the query: they do not + * `revert` if an interface is not supported. It is up to the caller to decide + * what to do in these cases. + */ +library ERC165Checker { + // As per the EIP-165 spec, no interface should ever match 0xffffffff + bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff; + + /** + * @dev Returns true if `account` supports the {IERC165} interface. + */ + function supportsERC165(address account) internal view returns (bool) { + // Any contract that implements ERC165 must explicitly indicate support of + // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid + return + supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) && + !supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID); + } + + /** + * @dev Returns true if `account` supports the interface defined by + * `interfaceId`. Support for {IERC165} itself is queried automatically. + * + * See {IERC165-supportsInterface}. + */ + function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) { + // query support of both ERC165 as per the spec and support of _interfaceId + return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId); + } + + /** + * @dev Returns a boolean array where each value corresponds to the + * interfaces passed in and whether they're supported or not. This allows + * you to batch check interfaces for a contract where your expectation + * is that some interfaces may not be supported. + * + * See {IERC165-supportsInterface}. + * + * _Available since v3.4._ + */ + function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) + internal + view + returns (bool[] memory) + { + // an array of booleans corresponding to interfaceIds and whether they're supported or not + bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length); + + // query support of ERC165 itself + if (supportsERC165(account)) { + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]); + } + } + + return interfaceIdsSupported; + } + + /** + * @dev Returns true if `account` supports all the interfaces defined in + * `interfaceIds`. Support for {IERC165} itself is queried automatically. + * + * Batch-querying can lead to gas savings by skipping repeated checks for + * {IERC165} support. + * + * See {IERC165-supportsInterface}. + */ + function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) { + // query support of ERC165 itself + if (!supportsERC165(account)) { + return false; + } + + // query support of each interface in interfaceIds + for (uint256 i = 0; i < interfaceIds.length; i++) { + if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) { + return false; + } + } + + // all interfaces supported + return true; + } + + /** + * @notice Query if a contract implements an interface, does not check ERC165 support + * @param account The address of the contract to query for support of an interface + * @param interfaceId The interface identifier, as specified in ERC-165 + * @return true if the contract at account indicates support of the interface with + * identifier interfaceId, false otherwise + * @dev Assumes that account contains a contract that supports ERC165, otherwise + * the behavior of this method is undefined. This precondition can be checked + * with {supportsERC165}. + * Interface identification is specified in ERC-165. + */ + function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) { + // prepare call + bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId); + + // perform static call + bool success; + uint256 returnSize; + uint256 returnValue; + assembly { + success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20) + returnSize := returndatasize() + returnValue := mload(0x00) + } + + return success && returnSize >= 0x20 && returnValue > 0; + } +} + +// lib/royalty-registry-solidity/contracts/IRoyaltyEngineV1.sol + +/// @author: manifold.xyz + +/** + * @dev Lookup engine interface + */ +interface IRoyaltyEngineV1 is IERC165 { + /** + * Get the royalty for a given token (address, id) and value amount. Does not cache the bps/amounts. Caches the spec for a given token address + * + * @param tokenAddress - The address of the token + * @param tokenId - The id of the token + * @param value - The value you wish to get the royalty of + * + * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get + */ + function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value) + external + returns (address payable[] memory recipients, uint256[] memory amounts); + + /** + * View only version of getRoyalty + * + * @param tokenAddress - The address of the token + * @param tokenId - The id of the token + * @param value - The value you wish to get the royalty of + * + * returns Two arrays of equal length, royalty recipients and the corresponding amount each recipient should get + */ + function getRoyaltyView(address tokenAddress, uint256 tokenId, uint256 value) + external + view + returns (address payable[] memory recipients, uint256[] memory amounts); +} + +// lib/solmate/src/utils/SafeTransferLib.sol + +/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. +/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) +/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. +/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. +library SafeTransferLib { + /*////////////////////////////////////////////////////////////// + ETH OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferETH(address to, uint256 amount) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Transfer the ETH and store if it succeeded or not. + success := call(gas(), to, amount, 0, 0, 0, 0) + } + + require(success, "ETH_TRANSFER_FAILED"); + } + + /*////////////////////////////////////////////////////////////// + ERC20 OPERATIONS + //////////////////////////////////////////////////////////////*/ + + function safeTransferFrom( + ERC20 token, + address from, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument. + mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) + ) + } + + require(success, "TRANSFER_FROM_FAILED"); + } + + function safeTransfer( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "TRANSFER_FAILED"); + } + + function safeApprove( + ERC20 token, + address to, + uint256 amount + ) internal { + bool success; + + /// @solidity memory-safe-assembly + assembly { + // Get a pointer to some free memory. + let freeMemoryPointer := mload(0x40) + + // Write the abi-encoded calldata into memory, beginning with the function selector. + mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) + mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument. + mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. + + success := and( + // Set success to whether the call reverted, if not we check it either + // returned exactly 1 (can't just be non-zero data), or had no return data. + or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), + // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. + // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. + // Counterintuitively, this call must be positioned second to the or() call in the + // surrounding and() call or else returndatasize() will be zero during the computation. + call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) + ) + } + + require(success, "APPROVE_FAILED"); + } +} + +// src/bonding-curves/ICurve.sol + +interface ICurve { + /** + * @notice Validates if a delta value is valid for the curve. The criteria for + * validity can be different for each type of curve, for instance ExponentialCurve + * requires delta to be greater than 1. + * @param delta The delta value to be validated + * @return valid True if delta is valid, false otherwise + */ + function validateDelta(uint128 delta) external pure returns (bool valid); + + /** + * @notice Validates if a new spot price is valid for the curve. Spot price is generally assumed to be the immediate sell price of 1 NFT to the pool, in units of the pool's paired token. + * @param newSpotPrice The new spot price to be set + * @return valid True if the new spot price is valid, false otherwise + */ + function validateSpotPrice(uint128 newSpotPrice) external view returns (bool valid); + + /** + * @notice Given the current state of the pair and the trade, computes how much the user + * should pay to purchase an NFT from the pair, the new spot price, and other values. + * @param spotPrice The current selling spot price of the pair, in tokens + * @param delta The delta parameter of the pair, what it means depends on the curve + * @param numItems The number of NFTs the user is buying from the pair + * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals + * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals + * @return error Any math calculation errors, only Error.OK means the returned values are valid + * @return newSpotPrice The updated selling spot price, in tokens + * @return newDelta The updated delta, used to parameterize the bonding curve + * @return inputValue The amount that the user should pay, in tokens + * @return tradeFee The amount that is sent to the trade fee recipient + * @return protocolFee The amount of fee to send to the protocol, in tokens + */ + function getBuyInfo( + uint128 spotPrice, + uint128 delta, + uint256 numItems, + uint256 feeMultiplier, + uint256 protocolFeeMultiplier + ) + external + view + returns ( + CurveErrorCodes.Error error, + uint128 newSpotPrice, + uint128 newDelta, + uint256 inputValue, + uint256 tradeFee, + uint256 protocolFee + ); + + /** + * @notice Given the current state of the pair and the trade, computes how much the user + * should receive when selling NFTs to the pair, the new spot price, and other values. + * @param spotPrice The current selling spot price of the pair, in tokens + * @param delta The delta parameter of the pair, what it means depends on the curve + * @param numItems The number of NFTs the user is selling to the pair + * @param feeMultiplier Determines how much fee the LP takes from this trade, 18 decimals + * @param protocolFeeMultiplier Determines how much fee the protocol takes from this trade, 18 decimals + * @return error Any math calculation errors, only Error.OK means the returned values are valid + * @return newSpotPrice The updated selling spot price, in tokens + * @return newDelta The updated delta, used to parameterize the bonding curve + * @return outputValue The amount that the user should receive, in tokens + * @return tradeFee The amount that is sent to the trade fee recipient + * @return protocolFee The amount of fee to send to the protocol, in tokens + */ + function getSellInfo( + uint128 spotPrice, + uint128 delta, + uint256 numItems, + uint256 feeMultiplier, + uint256 protocolFeeMultiplier + ) + external + view + returns ( + CurveErrorCodes.Error error, + uint128 newSpotPrice, + uint128 newDelta, + uint256 outputValue, + uint256 tradeFee, + uint256 protocolFee + ); +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Receiver.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol) + +/** + * @dev _Available since v3.1._ + */ +abstract contract ERC1155Receiver is ERC165, IERC1155Receiver { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); + } +} + +// lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol + +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/utils/ERC1155Holder.sol) + +/** + * Simple implementation of `ERC1155Receiver` that will allow a contract to hold ERC1155 tokens. + * + * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be + * stuck. + * + * @dev _Available since v3.1._ + */ +contract ERC1155Holder is ERC1155Receiver { + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public virtual override returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } +} + +// src/lib/OwnableWithTransferCallback.sol + +abstract contract OwnableWithTransferCallback { + using ERC165Checker for address; + using Address for address; + + bytes4 constant TRANSFER_CALLBACK = type(IOwnershipTransferReceiver).interfaceId; + + error Ownable_NotOwner(); + error Ownable_NewOwnerZeroAddress(); + + address private _owner; + + event OwnershipTransferred(address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + function __Ownable_init(address initialOwner) internal { + _owner = initialOwner; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + if (owner() != msg.sender) revert Ownable_NotOwner(); + _; + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * @param newOwner The new address to become owner + * @param data Any additional data to send to the ownership received callback. + * Disallows setting to the zero address as a way to more gas-efficiently avoid reinitialization. + * When ownership is transferred, if the new owner implements IOwnershipTransferCallback, we make a callback. + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner, bytes calldata data) public payable virtual onlyOwner { + if (newOwner == address(0)) revert Ownable_NewOwnerZeroAddress(); + _transferOwnership(newOwner); + + if (newOwner.isContract()) { + try IOwnershipTransferReceiver(newOwner).onOwnershipTransferred{value: msg.value}(msg.sender, data) {} + // If revert... + catch (bytes memory reason) { + // If we just transferred to a contract w/ no callback, this is fine + if (reason.length == 0) { + // i.e., no need to revert + } + // Otherwise, the callback had an error, and we should revert + else { + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } + } + + /** + * @notice Transfers ownership of the contract to a new account (`newOwner`). + * @dev Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + _owner = newOwner; + emit OwnershipTransferred(newOwner); + } +} + +// src/ILSSVMPairFactoryLike.sol + +interface ILSSVMPairFactoryLike { + struct Settings { + uint96 bps; + address pairAddress; + } + + enum PairNFTType { + ERC721, + ERC1155 + } + + enum PairTokenType { + ETH, + ERC20 + } + + enum PairVariant { + ERC721_ETH, + ERC721_ERC20, + ERC1155_ETH, + ERC1155_ERC20 + } + + function protocolFeeMultiplier() external view returns (uint256); + + function protocolFeeRecipient() external view returns (address payable); + + function callAllowed(address target) external view returns (bool); + + function authAllowedForToken(address tokenAddress, address proposedAuthAddress) external view returns (bool); + + function getSettingsForPair(address pairAddress) external view returns (bool settingsEnabled, uint96 bps); + + function enableSettingsForPair(address settings, address pairAddress) external; + + function disableSettingsForPair(address settings, address pairAddress) external; + + function routerStatus(LSSVMRouter router) external view returns (bool allowed, bool wasEverTouched); + + function isValidPair(address pairAddress) external view returns (bool); + + function getPairNFTType(address pairAddress) external pure returns (PairNFTType); + + function getPairTokenType(address pairAddress) external pure returns (PairTokenType); + + function openLock() external; + + function closeLock() external; +} + +// src/LSSVMPair.sol + +/** + * @title The base contract for an NFT/TOKEN AMM pair + * @author boredGenius, 0xmons, 0xCygaar + * @notice This implements the core swap logic from NFT to TOKEN + */ +abstract contract LSSVMPair is OwnableWithTransferCallback, ERC721Holder, ERC1155Holder { + /** + * Library usage ** + */ + + using Address for address; + + /** + * Enums ** + */ + + enum PoolType { + TOKEN, + NFT, + TRADE + } + + /** + * Constants ** + */ + + /** + * @dev 50%, must <= 1 - MAX_PROTOCOL_FEE (set in LSSVMPairFactory) + */ + uint256 internal constant MAX_TRADE_FEE = 0.5e18; + + /** + * Immutable params ** + */ + + /** + * @notice Sudoswap Royalty Engine + */ + IRoyaltyEngineV1 public immutable ROYALTY_ENGINE; + + /** + * Storage variables ** + */ + + /** + * @dev This is generally used to mean the immediate sell price for the next marginal NFT. + * However, this should NOT be assumed, as bonding curves may use spotPrice in different ways. + * Use getBuyNFTQuote and getSellNFTQuote for accurate pricing info. + */ + uint128 public spotPrice; + + /** + * @notice The parameter for the pair's bonding curve. + * Units and meaning are bonding curve dependent. + */ + uint128 public delta; + + /** + * @notice The spread between buy and sell prices, set to be a multiplier we apply to the buy price + * Fee is only relevant for TRADE pools. Units are in base 1e18. + */ + uint96 public fee; + + /** + * @notice The address that swapped assets are sent to. + * For TRADE pools, assets are always sent to the pool, so this is used to track trade fee. + * If set to address(0), will default to owner() for NFT and TOKEN pools. + */ + address payable internal assetRecipient; + + /** + * Events + */ + + event SwapNFTInPair(uint256 amountOut, uint256[] ids); + event SwapNFTInPair(uint256 amountOut, uint256 numNFTs); + event SwapNFTOutPair(uint256 amountIn, uint256[] ids); + event SwapNFTOutPair(uint256 amountIn, uint256 numNFTs); + event SpotPriceUpdate(uint128 newSpotPrice); + event TokenDeposit(uint256 amount); + event TokenWithdrawal(uint256 amount); + event NFTWithdrawal(uint256[] ids); + event NFTWithdrawal(uint256 numNFTs); + event DeltaUpdate(uint128 newDelta); + event FeeUpdate(uint96 newFee); + event AssetRecipientChange(address indexed a); + + /** + * Errors + */ + + error LSSVMPair__NotRouter(); + error LSSVMPair__CallFailed(); + error LSSVMPair__InvalidDelta(); + error LSSVMPair__WrongPoolType(); + error LSSVMPair__OutputTooSmall(); + error LSSVMPair__ZeroSwapAmount(); + error LSSVMPair__RoyaltyTooLarge(); + error LSSVMPair__TradeFeeTooLarge(); + error LSSVMPair__InvalidSpotPrice(); + error LSSVMPair__TargetNotAllowed(); + error LSSVMPair__NftNotTransferred(); + error LSSVMPair__AlreadyInitialized(); + error LSSVMPair__FunctionNotAllowed(); + error LSSVMPair__DemandedInputTooLarge(); + error LSSVMPair__NonTradePoolWithTradeFee(); + error LSSVMPair__BondingCurveError(CurveErrorCodes.Error error); + + constructor(IRoyaltyEngineV1 royaltyEngine) { + ROYALTY_ENGINE = royaltyEngine; + } + + /** + * @notice Called during pair creation to set initial parameters + * @dev Only called once by factory to initialize. + * We verify this by making sure that the current owner is address(0). + * The Ownable library we use disallows setting the owner to be address(0), so this condition + * should only be valid before the first initialize call. + * @param _owner The owner of the pair + * @param _assetRecipient The address that will receive the TOKEN or NFT sent to this pair during swaps. NOTE: If set to address(0), they will go to the pair itself. + * @param _delta The initial delta of the bonding curve + * @param _fee The initial % fee taken, if this is a trade pair + * @param _spotPrice The initial price to sell an asset into the pair + */ + function initialize( + address _owner, + address payable _assetRecipient, + uint128 _delta, + uint96 _fee, + uint128 _spotPrice + ) external { + if (owner() != address(0)) revert LSSVMPair__AlreadyInitialized(); + __Ownable_init(_owner); + + ICurve _bondingCurve = bondingCurve(); + PoolType _poolType = poolType(); + if (_poolType != PoolType.TRADE) { + if (_fee != 0) revert LSSVMPair__NonTradePoolWithTradeFee(); + } else { + if (_fee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); + fee = _fee; + } + + assetRecipient = _assetRecipient; + + if (!_bondingCurve.validateDelta(_delta)) revert LSSVMPair__InvalidDelta(); + if (!_bondingCurve.validateSpotPrice(_spotPrice)) revert LSSVMPair__InvalidSpotPrice(); + delta = _delta; + spotPrice = _spotPrice; + } + + /** + * External state-changing functions + */ + + /** + * @notice Sends token to the pair in exchange for a specific set of NFTs + * @dev To compute the amount of token to send, call bondingCurve.getBuyInfo + * This swap is meant for users who want specific IDs. Also higher chance of + * reverting if some of the specified IDs leave the pool before the swap goes through. + * @param nftIds The list of IDs of the NFTs to purchase + * @param maxExpectedTokenInput The maximum acceptable cost from the sender. If the actual + * amount is greater than this value, the transaction will be reverted. + * @param nftRecipient The recipient of the NFTs + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. + * @return - The amount of token used for purchase + */ + function swapTokenForSpecificNFTs( + uint256[] calldata nftIds, + uint256 maxExpectedTokenInput, + address nftRecipient, + bool isRouter, + address routerCaller + ) external payable virtual returns (uint256); + + /** + * @notice Sends a set of NFTs to the pair in exchange for token + * @dev To compute the amount of token to that will be received, call bondingCurve.getSellInfo. + * @param nftIds The list of IDs of the NFTs to sell to the pair + * @param minExpectedTokenOutput The minimum acceptable token received by the sender. If the actual + * amount is less than this value, the transaction will be reverted. + * @param tokenRecipient The recipient of the token output + * @param isRouter True if calling from LSSVMRouter, false otherwise. Not used for + * ETH pairs. + * @param routerCaller If isRouter is true, ERC20 tokens will be transferred from this address. Not used for + * ETH pairs. + * @return outputAmount The amount of token received + */ + function swapNFTsForToken( + uint256[] calldata nftIds, + uint256 minExpectedTokenOutput, + address payable tokenRecipient, + bool isRouter, + address routerCaller + ) external virtual returns (uint256 outputAmount); + + /** + * View functions + */ + + /** + * @dev Used as read function to query the bonding curve for buy pricing info + * @param numNFTs The number of NFTs to buy from the pair + */ + function getBuyNFTQuote(uint256 assetId, uint256 numNFTs) + external + view + returns ( + CurveErrorCodes.Error error, + uint256 newSpotPrice, + uint256 newDelta, + uint256 inputAmount, + uint256 protocolFee, + uint256 royaltyAmount + ) + { + uint256 tradeFee; + (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = + bondingCurve().getBuyInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); + + if (numNFTs != 0) { + // Calculate the inputAmount minus tradeFee and protocolFee + uint256 inputAmountMinusFees = inputAmount - tradeFee - protocolFee; + + // Compute royalties + (,, royaltyAmount) = calculateRoyaltiesView(assetId, inputAmountMinusFees); + + inputAmount += royaltyAmount; + } + } + + /** + * @dev Used as read function to query the bonding curve for sell pricing info including royalties + * @param numNFTs The number of NFTs to sell to the pair + */ + function getSellNFTQuote(uint256 assetId, uint256 numNFTs) + external + view + returns ( + CurveErrorCodes.Error error, + uint256 newSpotPrice, + uint256 newDelta, + uint256 outputAmount, + uint256 protocolFee, + uint256 royaltyAmount + ) + { + (error, newSpotPrice, newDelta, outputAmount, /* tradeFee */, protocolFee) = + bondingCurve().getSellInfo(spotPrice, delta, numNFTs, fee, factory().protocolFeeMultiplier()); + + if (numNFTs != 0) { + // Compute royalties + (,, royaltyAmount) = calculateRoyaltiesView(assetId, outputAmount); + + // Deduct royalties from outputAmount + unchecked { + // Safe because we already require outputAmount >= royaltyAmount in _calculateRoyalties() + outputAmount -= royaltyAmount; + } + } + } + + /** + * @notice Returns the pair's variant (Pair uses ETH or ERC20) + */ + function pairVariant() public pure virtual returns (ILSSVMPairFactoryLike.PairVariant); + + function factory() public pure returns (ILSSVMPairFactoryLike _factory) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _factory := shr(0x60, calldataload(sub(calldatasize(), paramsLength))) + } + } + + /** + * @notice Returns the type of bonding curve that parameterizes the pair + */ + function bondingCurve() public pure returns (ICurve _bondingCurve) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _bondingCurve := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 20))) + } + } + + /** + * @notice Returns the address of NFT collection that parameterizes the pair + */ + function nft() public pure returns (address _nft) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _nft := shr(0x60, calldataload(add(sub(calldatasize(), paramsLength), 40))) + } + } + + /** + * @notice Returns the pair's type (TOKEN/NFT/TRADE) + */ + function poolType() public pure returns (PoolType _poolType) { + uint256 paramsLength = _immutableParamsLength(); + assembly { + _poolType := shr(0xf8, calldataload(add(sub(calldatasize(), paramsLength), 60))) + } + } + + /** + * @notice Returns the address that receives assets when a swap is done with this pair + * Can be set to another address by the owner, but has no effect on TRADE pools + * If set to address(0), defaults to owner() for NFT/TOKEN pools + */ + function getAssetRecipient() public view returns (address payable) { + // TRADE pools will always receive the asset themselves + if (poolType() == PoolType.TRADE) { + return payable(address(this)); + } + + address payable _assetRecipient = assetRecipient; + + // Otherwise, we return the recipient if it's been set + // Or, we replace it with owner() if it's address(0) + if (_assetRecipient == address(0)) { + return payable(owner()); + } + return _assetRecipient; + } + + /** + * @notice Returns the address that receives trade fees when a swap is done with this pair + * Only relevant for TRADE pools + * If set to address(0), defaults to the pair itself + */ + function getFeeRecipient() public view returns (address payable _feeRecipient) { + _feeRecipient = assetRecipient; + if (_feeRecipient == address(0)) { + _feeRecipient = payable(address(this)); + } + } + + /** + * Internal functions + */ + + /** + * @notice Calculates the amount needed to be sent into the pair for a buy and adjusts spot price or delta if necessary + * @param numNFTs The amount of NFTs to purchase from the pair + * @param _bondingCurve The bonding curve to use for price calculation + * @param _factory The factory to use for protocol fee lookup + * @return tradeFee The amount of tokens to send as trade fee + * @return protocolFee The amount of tokens to send as protocol fee + * @return inputAmount The amount of tokens total tokens receive + */ + function _calculateBuyInfoAndUpdatePoolParams(uint256 numNFTs, ICurve _bondingCurve, ILSSVMPairFactoryLike _factory) + internal + returns (uint256 tradeFee, uint256 protocolFee, uint256 inputAmount) + { + CurveErrorCodes.Error error; + // Save on 2 SLOADs by caching + uint128 currentSpotPrice = spotPrice; + uint128 currentDelta = delta; + uint128 newDelta; + uint128 newSpotPrice; + (error, newSpotPrice, newDelta, inputAmount, tradeFee, protocolFee) = + _bondingCurve.getBuyInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); + + // Revert if bonding curve had an error + if (error != CurveErrorCodes.Error.OK) { + revert LSSVMPair__BondingCurveError(error); + } + + // Consolidate writes to save gas + if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { + spotPrice = newSpotPrice; + delta = newDelta; + } + + // Emit spot price update if it has been updated + if (currentSpotPrice != newSpotPrice) { + emit SpotPriceUpdate(newSpotPrice); + } + + // Emit delta update if it has been updated + if (currentDelta != newDelta) { + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Calculates the amount needed to be sent by the pair for a sell and adjusts spot price or delta if necessary + * @param numNFTs The amount of NFTs to send to the the pair + * @param _bondingCurve The bonding curve to use for price calculation + * @param _factory The factory to use for protocol fee lookup + * @return protocolFee The amount of tokens to send as protocol fee + * @return outputAmount The amount of tokens total tokens receive + */ + function _calculateSellInfoAndUpdatePoolParams( + uint256 numNFTs, + ICurve _bondingCurve, + ILSSVMPairFactoryLike _factory + ) internal returns (uint256 protocolFee, uint256 outputAmount) { + CurveErrorCodes.Error error; + // Save on 2 SLOADs by caching + uint128 currentSpotPrice = spotPrice; + uint128 currentDelta = delta; + uint128 newSpotPrice; + uint128 newDelta; + (error, newSpotPrice, newDelta, outputAmount, /*tradeFee*/, protocolFee) = + _bondingCurve.getSellInfo(currentSpotPrice, currentDelta, numNFTs, fee, _factory.protocolFeeMultiplier()); + + // Revert if bonding curve had an error + if (error != CurveErrorCodes.Error.OK) { + revert LSSVMPair__BondingCurveError(error); + } + + // Consolidate writes to save gas + if (currentSpotPrice != newSpotPrice || currentDelta != newDelta) { + spotPrice = newSpotPrice; + delta = newDelta; + } + + // Emit spot price update if it has been updated + if (currentSpotPrice != newSpotPrice) { + emit SpotPriceUpdate(newSpotPrice); + } + + // Emit delta update if it has been updated + if (currentDelta != newDelta) { + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Pulls the token input of a trade from the trader (including all royalties and fees) + * @param inputAmountExcludingRoyalty The amount of tokens to be sent, excluding the royalty (includes protocol fee) + * @param royaltyAmounts The amounts of tokens to be sent as royalties + * @param royaltyRecipients The recipients of the royalties + * @param royaltyTotal The sum of all royaltyAmounts + * @param tradeFeeAmount The amount of tokens to be sent as trade fee (if applicable) + * @param isRouter Whether or not the caller is LSSVMRouter + * @param routerCaller If called from LSSVMRouter, store the original caller + * @param protocolFee The protocol fee to be paid + */ + function _pullTokenInputs( + uint256 inputAmountExcludingRoyalty, + uint256[] memory royaltyAmounts, + address payable[] memory royaltyRecipients, + uint256 royaltyTotal, + uint256 tradeFeeAmount, + bool isRouter, + address routerCaller, + uint256 protocolFee + ) internal virtual; + + /** + * @notice Sends excess tokens back to the caller (if applicable) + * @dev Swap callers interacting with an ETH pair must be able to receive ETH (e.g. if the caller sends too much ETH) + */ + function _refundTokenToSender(uint256 inputAmount) internal virtual; + + /** + * @notice Sends tokens to a recipient + * @param tokenRecipient The address receiving the tokens + * @param outputAmount The amount of tokens to send + */ + function _sendTokenOutput(address payable tokenRecipient, uint256 outputAmount) internal virtual; + + /** + * @dev Used internally to grab pair parameters from calldata, see LSSVMPairCloner for technical details + */ + function _immutableParamsLength() internal pure virtual returns (uint256); + + /** + * Royalty support functions + */ + + function _calculateRoyalties(uint256 assetId, uint256 saleAmount) + internal + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + (address payable[] memory recipients, uint256[] memory amounts) = + ROYALTY_ENGINE.getRoyalty(nft(), assetId, saleAmount); + return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); + } + + /** + * @dev Same as _calculateRoyalties, but uses getRoyaltyView to avoid state mutations and is public for external callers + */ + function calculateRoyaltiesView(uint256 assetId, uint256 saleAmount) + public + view + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + (address payable[] memory recipients, uint256[] memory amounts) = + ROYALTY_ENGINE.getRoyaltyView(nft(), assetId, saleAmount); + return _calculateRoyaltiesLogic(recipients, amounts, saleAmount); + } + + /** + * @dev Common logic used by _calculateRoyalties() and calculateRoyaltiesView() + */ + function _calculateRoyaltiesLogic(address payable[] memory recipients, uint256[] memory amounts, uint256 saleAmount) + internal + view + returns (address payable[] memory royaltyRecipients, uint256[] memory royaltyAmounts, uint256 royaltyTotal) + { + // Cache to save gas + uint256 numRecipients = recipients.length; + + if (numRecipients != 0) { + // If a pair has custom Settings, use the overridden royalty amount and only use the first receiver + try factory().getSettingsForPair(address(this)) returns (bool settingsEnabled, uint96 bps) { + if (settingsEnabled) { + royaltyRecipients = new address payable[](1); + royaltyRecipients[0] = recipients[0]; + royaltyAmounts = new uint256[](1); + royaltyAmounts[0] = (saleAmount * bps) / 10000; + + // Update numRecipients to match new recipients list + numRecipients = 1; + } else { + royaltyRecipients = recipients; + royaltyAmounts = amounts; + } + } catch { + // Use the input values to calculate royalties if factory call fails + royaltyRecipients = recipients; + royaltyAmounts = amounts; + } + } + + for (uint256 i; i < numRecipients;) { + royaltyTotal += royaltyAmounts[i]; + unchecked { + ++i; + } + } + + // Ensure royalty total is at most 25% of the sale amount + // This defends against a rogue Manifold registry that charges extremely high royalties + if (royaltyTotal > saleAmount >> 2) { + revert LSSVMPair__RoyaltyTooLarge(); + } + } + + /** + * Owner functions + */ + + /** + * @notice Rescues a specified set of NFTs owned by the pair to the owner address. (onlyOwnable modifier is in the implemented function) + * @param a The NFT to transfer + * @param nftIds The list of IDs of the NFTs to send to the owner + */ + function withdrawERC721(IERC721 a, uint256[] calldata nftIds) external virtual; + + /** + * @notice Rescues ERC20 tokens from the pair to the owner. Only callable by the owner (onlyOwnable modifier is in the implemented function). + * @param a The token to transfer + * @param amount The amount of tokens to send to the owner + */ + function withdrawERC20(ERC20 a, uint256 amount) external virtual; + + /** + * @notice Rescues ERC1155 tokens from the pair to the owner. Only callable by the owner. + * @param a The NFT to transfer + * @param ids The NFT ids to transfer + * @param amounts The amounts of each id to transfer + */ + function withdrawERC1155(IERC1155 a, uint256[] calldata ids, uint256[] calldata amounts) external virtual; + + /** + * @notice Updates the selling spot price. Only callable by the owner. + * @param newSpotPrice The new selling spot price value, in Token + */ + function changeSpotPrice(uint128 newSpotPrice) external onlyOwner { + ICurve _bondingCurve = bondingCurve(); + if (!_bondingCurve.validateSpotPrice(newSpotPrice)) revert LSSVMPair__InvalidSpotPrice(); + if (spotPrice != newSpotPrice) { + spotPrice = newSpotPrice; + emit SpotPriceUpdate(newSpotPrice); + } + } + + /** + * @notice Updates the delta parameter. Only callable by the owner. + * @param newDelta The new delta parameter + */ + function changeDelta(uint128 newDelta) external onlyOwner { + ICurve _bondingCurve = bondingCurve(); + if (!_bondingCurve.validateDelta(newDelta)) revert LSSVMPair__InvalidDelta(); + if (delta != newDelta) { + delta = newDelta; + emit DeltaUpdate(newDelta); + } + } + + /** + * @notice Updates the fee taken by the LP. Only callable by the owner. + * Only callable if the pool is a Trade pool. Reverts if the fee is >= MAX_FEE. + * @param newFee The new LP fee percentage, 18 decimals + */ + function changeFee(uint96 newFee) external onlyOwner { + PoolType _poolType = poolType(); + if (_poolType != PoolType.TRADE) revert LSSVMPair__NonTradePoolWithTradeFee(); + if (newFee > MAX_TRADE_FEE) revert LSSVMPair__TradeFeeTooLarge(); + if (fee != newFee) { + fee = newFee; + emit FeeUpdate(newFee); + } + } + + /** + * @notice Changes the address that will receive assets received from + * trades. Only callable by the owner. + * @param newRecipient The new asset recipient + */ + function changeAssetRecipient(address payable newRecipient) external onlyOwner { + if (assetRecipient != newRecipient) { + assetRecipient = newRecipient; + emit AssetRecipientChange(newRecipient); + } + } + + function _preCallCheck(address target) internal virtual; + + /** + * @notice Allows the pair to make arbitrary external calls to contracts + * whitelisted by the protocol. Only callable by the owner. + * @param target The contract to call + * @param data The calldata to pass to the contract + */ + function call(address payable target, bytes calldata data) external onlyOwner { + ILSSVMPairFactoryLike _factory = factory(); + if (!_factory.callAllowed(target)) revert LSSVMPair__TargetNotAllowed(); + + // Ensure the call isn't calling a banned function + bytes4 sig = bytes4(data[:4]); + if ( + sig == IOwnershipTransferReceiver.onOwnershipTransferred.selector + || sig == LSSVMRouter.pairTransferERC20From.selector || sig == LSSVMRouter.pairTransferNFTFrom.selector + || sig == LSSVMRouter.pairTransferERC1155From.selector || sig == ILSSVMPairFactoryLike.openLock.selector + || sig == ILSSVMPairFactoryLike.closeLock.selector + ) { + revert LSSVMPair__FunctionNotAllowed(); + } + + // Prevent calling the pair's underlying nft + // (We ban calling the underlying NFT/ERC20 to avoid maliciously transferring assets approved for the pair to spend) + if (target == nft()) revert LSSVMPair__TargetNotAllowed(); + + _preCallCheck(target); + + (bool success,) = target.call{value: 0}(data); + if (!success) revert LSSVMPair__CallFailed(); + } + + /** + * @notice Allows owner to batch multiple calls, forked from: https://github.com/boringcrypto/BoringSolidity/blob/master/contracts/BoringBatchable.sol + * @notice The revert handling is forked from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/c239e1af8d1a1296577108dd6989a17b57434f8e/contracts/utils/Address.sol#L201 + * @dev Intended for withdrawing/altering pool pricing in one tx, only callable by owner, cannot change owner + * @param calls The calldata for each call to make + * @param revertOnFail Whether or not to revert the entire tx if any of the calls fail. Calls to transferOwnership will revert regardless. + */ + function multicall(bytes[] calldata calls, bool revertOnFail) external onlyOwner { + for (uint256 i; i < calls.length;) { + bytes4 sig = bytes4(calls[i][:4]); + // We ban calling transferOwnership when ownership + if (sig == transferOwnership.selector) revert LSSVMPair__FunctionNotAllowed(); + + (bool success, bytes memory result) = address(this).delegatecall(calls[i]); + if (!success && revertOnFail) { + assembly { + revert(add(0x20, result), mload(result)) + } + } + + unchecked { + ++i; + } + } + } +} + +// src/LSSVMRouter.sol + +contract LSSVMRouter { + using SafeTransferLib for address payable; + using SafeTransferLib for ERC20; + + struct PairSwapSpecific { + LSSVMPair pair; + uint256[] nftIds; + } + + struct RobustPairSwapSpecific { + PairSwapSpecific swapInfo; + uint256 maxCost; + } + + struct RobustPairSwapSpecificForToken { + PairSwapSpecific swapInfo; + uint256 minOutput; + } + + struct NFTsForSpecificNFTsTrade { + PairSwapSpecific[] nftToTokenTrades; + PairSwapSpecific[] tokenToNFTTrades; + } + + struct RobustPairNFTsFoTokenAndTokenforNFTsTrade { + RobustPairSwapSpecific[] tokenToNFTTrades; + RobustPairSwapSpecificForToken[] nftToTokenTrades; + uint256 inputAmount; + address payable tokenRecipient; + address nftRecipient; + } + + modifier checkDeadline(uint256 deadline) { + _checkDeadline(deadline); + _; + } + + ILSSVMPairFactoryLike public immutable factory; + + constructor(ILSSVMPairFactoryLike _factory) { + factory = _factory; + } + + /** + * ETH swaps + */ + + /** + * @notice Swaps ETH into specific NFTs using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param ethRecipient The address that will receive the unspent ETH input + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent ETH amount + */ + function swapETHForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) external payable checkDeadline(deadline) returns (uint256 remainingValue) { + return _swapETHForSpecificNFTs(swapList, msg.value, ethRecipient, nftRecipient); + } + + /** + * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using + * ETH as the intermediary. + * @param trade The struct containing all NFT-to-ETH swaps and ETH-to-NFT swaps. + * @param minOutput The minimum acceptable total excess ETH received + * @param ethRecipient The address that will receive the ETH output + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ETH received + */ + function swapNFTsForSpecificNFTsThroughETH( + NFTsForSpecificNFTsTrade calldata trade, + uint256 minOutput, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) external payable checkDeadline(deadline) returns (uint256 outputAmount) { + // Swap NFTs for ETH + // minOutput of swap set to 0 since we're doing an aggregate slippage check + outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(address(this))); + + // Add extra value to buy NFTs + outputAmount += msg.value; + + // Swap ETH for specific NFTs + // cost <= inputValue = outputAmount - minOutput, so outputAmount' = (outputAmount - minOutput - cost) + minOutput >= minOutput + outputAmount = _swapETHForSpecificNFTs( + trade.tokenToNFTTrades, outputAmount - minOutput, ethRecipient, nftRecipient + ) + minOutput; + } + + /** + * ERC20 swaps + * + * Note: All ERC20 swaps assume that a single ERC20 token is used for all the pairs involved. + * Swapping using multiple tokens in the same transaction is possible, but the slippage checks + * & the return values will be meaningless, and may lead to undefined behavior. + * + * Note: The sender should ideally grant infinite token approval to the router in order for NFT-to-NFT + * swaps to work smoothly. + */ + + /** + * @notice Swaps ERC20 tokens into specific NFTs using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function swapERC20ForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address nftRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 remainingValue) { + return _swapERC20ForSpecificNFTs(swapList, inputAmount, nftRecipient); + } + + /** + * @notice Swaps NFTs into ETH/ERC20 using multiple pairs. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. + * @param minOutput The minimum acceptable total tokens received + * @param tokenRecipient The address that will receive the token output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total tokens received + */ + function swapNFTsForToken( + PairSwapSpecific[] calldata swapList, + uint256 minOutput, + address tokenRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 outputAmount) { + return _swapNFTsForToken(swapList, minOutput, payable(tokenRecipient)); + } + + /** + * @notice Swaps one set of NFTs into another set of specific NFTs using multiple pairs, using + * an ERC20 token as the intermediary. + * @param trade The struct containing all NFT-to-ERC20 swaps and ERC20-to-NFT swaps. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param minOutput The minimum acceptable total excess tokens received + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ERC20 tokens received + */ + function swapNFTsForSpecificNFTsThroughERC20( + NFTsForSpecificNFTsTrade calldata trade, + uint256 inputAmount, + uint256 minOutput, + address nftRecipient, + uint256 deadline + ) external checkDeadline(deadline) returns (uint256 outputAmount) { + // Swap NFTs for ERC20 + // minOutput of swap set to 0 since we're doing an aggregate slippage check + // output tokens are sent to msg.sender + outputAmount = _swapNFTsForToken(trade.nftToTokenTrades, 0, payable(msg.sender)); + + // Add extra value to buy NFTs + outputAmount += inputAmount; + + // Swap ERC20 for specific NFTs + // cost <= maxCost = outputAmount - minOutput, so outputAmount' = outputAmount - cost >= minOutput + // input tokens are taken directly from msg.sender + outputAmount = + _swapERC20ForSpecificNFTs(trade.tokenToNFTTrades, outputAmount - minOutput, nftRecipient) + minOutput; + } + + /** + * Robust Swaps + * These are "robust" versions of the NFT<>Token swap functions which will never revert due to slippage + * Instead, users specify a per-swap max cost. If the price changes more than the user specifies, no swap is attempted. This allows users to specify a batch of swaps, and execute as many of them as possible. + */ + + /** + * @dev Ensure msg.value >= sum of values in maxCostPerPair to make sure the transaction doesn't revert + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param ethRecipient The address that will receive the unspent ETH input + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function robustSwapETHForSpecificNFTs( + RobustPairSwapSpecific[] calldata swapList, + address payable ethRecipient, + address nftRecipient, + uint256 deadline + ) public payable virtual checkDeadline(deadline) returns (uint256 remainingValue) { + remainingValue = msg.value; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( + swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { + // We know how much ETH to send because we already did the math above + // So we just send that much + remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( + swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + ethRecipient.safeTransferETH(remainingValue); + } + } + + /** + * @notice Swaps as many ERC20 tokens for specific NFTs as possible, respecting the per-swap max cost. + * @param swapList The list of pairs to trade with and the IDs of the NFTs to buy from each. + * @param inputAmount The amount of ERC20 tokens to add to the ERC20-to-NFT swaps + * @param nftRecipient The address that will receive the NFT output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return remainingValue The unspent token amount + */ + function robustSwapERC20ForSpecificNFTs( + RobustPairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address nftRecipient, + uint256 deadline + ) public virtual checkDeadline(deadline) returns (uint256 remainingValue) { + remainingValue = inputAmount; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = swapList[i].swapInfo.pair.getBuyNFTQuote( + swapList[i].swapInfo.nftIds[0], swapList[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= swapList[i].maxCost && error == CurveErrorCodes.Error.OK) { + remainingValue -= swapList[i].swapInfo.pair.swapTokenForSpecificNFTs( + swapList[i].swapInfo.nftIds, pairCost, nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + + /** + * @notice Swaps as many NFTs for tokens as possible, respecting the per-swap min output + * @param swapList The list of pairs to trade with and the IDs of the NFTs to sell to each. + * @param tokenRecipient The address that will receive the token output + * @param deadline The Unix timestamp (in seconds) at/after which the swap will revert + * @return outputAmount The total ETH/ERC20 received + */ + function robustSwapNFTsForToken( + RobustPairSwapSpecificForToken[] calldata swapList, + address payable tokenRecipient, + uint256 deadline + ) public virtual checkDeadline(deadline) returns (uint256 outputAmount) { + // Try doing each swap + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256[] memory nftIds = swapList[i].swapInfo.nftIds; + if (nftIds.length == 0) { + unchecked { + ++i; + } + continue; + } + (error,,, pairOutput,,) = swapList[i].swapInfo.pair.getSellNFTQuote(nftIds[0], nftIds.length); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= swapList[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += swapList[i].swapInfo.pair.swapNFTsForToken( + swapList[i].swapInfo.nftIds, 0, tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + + /** + * @notice Buys NFTs with ETH and sells them for tokens in one transaction + * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: + * - ethToNFTSwapList The list of NFTs to buy + * - nftToTokenSwapList The list of NFTs to sell + * - inputAmount The max amount of tokens to send (if ERC20) + * - tokenRecipient The address that receives tokens from the NFTs sold + * - nftRecipient The address that receives NFTs + * - deadline UNIX timestamp deadline for the swap + */ + function robustSwapETHForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) + external + payable + virtual + returns (uint256 remainingValue, uint256 outputAmount) + { + { + remainingValue = msg.value; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = params.tokenToNFTTrades.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( + params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { + // We know how much ETH to send because we already did the math above + // So we just send that much + remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs{value: pairCost}( + params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + params.tokenRecipient.safeTransferETH(remainingValue); + } + } + { + // Try doing each swap + uint256 numSwaps = params.nftToTokenTrades.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; + (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( + assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length + ); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= params.nftToTokenTrades[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( + params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + } + + /** + * @notice Buys NFTs with ERC20, and sells them for tokens in one transaction + * @param params All the parameters for the swap (packed in struct to avoid stack too deep), containing: + * - ethToNFTSwapList The list of NFTs to buy + * - nftToTokenSwapList The list of NFTs to sell + * - inputAmount The max amount of tokens to send (if ERC20) + * - tokenRecipient The address that receives tokens from the NFTs sold + * - nftRecipient The address that receives NFTs + * - deadline UNIX timestamp deadline for the swap + */ + function robustSwapERC20ForSpecificNFTsAndNFTsToToken(RobustPairNFTsFoTokenAndTokenforNFTsTrade calldata params) + external + virtual + returns (uint256 remainingValue, uint256 outputAmount) + { + { + remainingValue = params.inputAmount; + uint256 pairCost; + CurveErrorCodes.Error error; + + // Try doing each swap + uint256 numSwaps = params.tokenToNFTTrades.length; + for (uint256 i; i < numSwaps;) { + // Calculate actual cost per swap + (error,,, pairCost,,) = params.tokenToNFTTrades[i].swapInfo.pair.getBuyNFTQuote( + params.tokenToNFTTrades[i].swapInfo.nftIds[0], params.tokenToNFTTrades[i].swapInfo.nftIds.length + ); + + // If within our maxCost and no error, proceed + if (pairCost <= params.tokenToNFTTrades[i].maxCost && error == CurveErrorCodes.Error.OK) { + remainingValue -= params.tokenToNFTTrades[i].swapInfo.pair.swapTokenForSpecificNFTs( + params.tokenToNFTTrades[i].swapInfo.nftIds, pairCost, params.nftRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + { + // Try doing each swap + uint256 numSwaps = params.nftToTokenTrades.length; + for (uint256 i; i < numSwaps;) { + uint256 pairOutput; + + // Locally scoped to avoid stack too deep error + { + CurveErrorCodes.Error error; + uint256 assetId = params.nftToTokenTrades[i].swapInfo.nftIds[0]; + (error,,, pairOutput,,) = params.nftToTokenTrades[i].swapInfo.pair.getSellNFTQuote( + assetId, params.nftToTokenTrades[i].swapInfo.nftIds.length + ); + if (error != CurveErrorCodes.Error.OK) { + unchecked { + ++i; + } + continue; + } + } + + // If at least equal to our minOutput, proceed + if (pairOutput >= params.nftToTokenTrades[i].minOutput) { + // Do the swap and update outputAmount with how many tokens we got + outputAmount += params.nftToTokenTrades[i].swapInfo.pair.swapNFTsForToken( + params.nftToTokenTrades[i].swapInfo.nftIds, 0, params.tokenRecipient, true, msg.sender + ); + } + + unchecked { + ++i; + } + } + } + } + + receive() external payable {} + + /** + * Restricted functions + */ + + /** + * @dev Allows an ERC20 pair contract to transfer ERC20 tokens directly from + * the sender, in order to minimize the number of token transfers. Only callable by an ERC20 pair. + * @param token The ERC20 token to transfer + * @param from The address to transfer tokens from + * @param to The address to transfer tokens to + * @param amount The amount of tokens to transfer + */ + function pairTransferERC20From(ERC20 token, address from, address to, uint256 amount) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + // verify caller is an ERC20 pair + require(factory.getPairTokenType(msg.sender) == ILSSVMPairFactoryLike.PairTokenType.ERC20, "Not ERC20 pair"); + + // transfer tokens to pair + token.safeTransferFrom(from, to, amount); + } + + /** + * @dev Allows a pair contract to transfer ERC721 NFTs directly from + * the sender, in order to minimize the number of token transfers. Only callable by a pair. + * @param nft The ERC721 NFT to transfer + * @param from The address to transfer tokens from + * @param to The address to transfer tokens to + * @param id The ID of the NFT to transfer + */ + function pairTransferNFTFrom(IERC721 nft, address from, address to, uint256 id) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + + // transfer NFTs to pair + nft.transferFrom(from, to, id); + } + + function pairTransferERC1155From( + IERC1155 nft, + address from, + address to, + uint256[] calldata ids, + uint256[] calldata amounts + ) external { + // verify caller is a trusted pair contract + require(factory.isValidPair(msg.sender), "Not pair"); + + nft.safeBatchTransferFrom(from, to, ids, amounts, bytes("")); + } + + /** + * Internal functions + */ + + /** + * @param deadline The last valid time for a swap + */ + function _checkDeadline(uint256 deadline) internal view { + require(block.timestamp <= deadline, "Deadline passed"); + } + + /** + * @notice Internal function used to swap ETH for a specific set of NFTs + * @param swapList The list of pairs and swap calldata + * @param inputAmount The total amount of ETH to send + * @param ethRecipient The address receiving excess ETH + * @param nftRecipient The address receiving the NFTs from the pairs + * @return remainingValue The unspent token amount + */ + function _swapETHForSpecificNFTs( + PairSwapSpecific[] calldata swapList, + uint256 inputAmount, + address payable ethRecipient, + address nftRecipient + ) internal virtual returns (uint256 remainingValue) { + remainingValue = inputAmount; + + uint256 pairCost; + CurveErrorCodes.Error error; + + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Calculate the cost per swap first to send exact amount of ETH over, saves gas by avoiding the need to send back excess ETH + (error,,, pairCost,,) = swapList[i].pair.getBuyNFTQuote(swapList[i].nftIds[0], swapList[i].nftIds.length); + + // Require no errors + require(error == CurveErrorCodes.Error.OK, "Bonding curve error"); + + // Total ETH taken from sender cannot exceed inputAmount + // because otherwise the deduction from remainingValue will fail + remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs{value: pairCost}( + swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender + ); + + unchecked { + ++i; + } + } + + // Return remaining value to sender + if (remainingValue > 0) { + ethRecipient.safeTransferETH(remainingValue); + } + } + + /** + * @notice Internal function used to swap an ERC20 token for specific NFTs + * @dev Note that we don't need to query the pair's bonding curve first for pricing data because + * we just calculate and take the required amount from the caller during swap time. + * However, we can't "pull" ETH, which is why for the ETH->NFT swaps, we need to calculate the pricing info + * to figure out how much the router should send to the pool. + * @param swapList The list of pairs and swap calldata + * @param inputAmount The total amount of ERC20 tokens to send + * @param nftRecipient The address receiving the NFTs from the pairs + * @return remainingValue The unspent token amount + */ + function _swapERC20ForSpecificNFTs(PairSwapSpecific[] calldata swapList, uint256 inputAmount, address nftRecipient) + internal + virtual + returns (uint256 remainingValue) + { + remainingValue = inputAmount; + + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Tokens are transferred in by the pair calling router.pairTransferERC20From + // Total tokens taken from sender cannot exceed inputAmount + // because otherwise the deduction from remainingValue will fail + remainingValue -= swapList[i].pair.swapTokenForSpecificNFTs( + swapList[i].nftIds, remainingValue, nftRecipient, true, msg.sender + ); + + unchecked { + ++i; + } + } + } + + /** + * @notice Swaps NFTs for tokens, designed to be used for 1 token at a time + * @dev Calling with multiple tokens is permitted, BUT minOutput will be + * far from enough of a safety check because different tokens almost certainly have different unit prices. + * @param swapList The list of pairs and swap calldata + * @param minOutput The minimum number of tokens to be receieved from the swaps + * @param tokenRecipient The address that receives the tokens + * @return outputAmount The number of tokens to be received + */ + function _swapNFTsForToken(PairSwapSpecific[] calldata swapList, uint256 minOutput, address payable tokenRecipient) + internal + virtual + returns (uint256 outputAmount) + { + // Do swaps + uint256 numSwaps = swapList.length; + for (uint256 i; i < numSwaps;) { + // Do the swap for token and then update outputAmount + // Note: minExpectedTokenOutput is set to 0 since we're doing an aggregate slippage check below + outputAmount += swapList[i].pair.swapNFTsForToken(swapList[i].nftIds, 0, tokenRecipient, true, msg.sender); + + unchecked { + ++i; + } + } + + // Aggregate slippage check + require(outputAmount >= minOutput, "outputAmount too low"); + } +} + +// src/lib/LSSVMPairCloner.sol + +library LSSVMPairCloner { + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC721ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 86 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 81 bytes of extra data = 134 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 51 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60863d8160093d39f33d3d3d3d363d3d37605160353639366051013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x35) when rds < cds+0x35) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (81 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) + + // ------------------------------------------------------------------------------------------------------------- + // Total length is 143 (8f) bytes + // ------------------------------------------------------------------------------------------------------------- + + instance := create(0, ptr, 0x8f) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC721ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker, + ERC20 token + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 9a + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 101 bytes of extra data = 154 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 65 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"609a3d8160093d39f33d3d3d3d363d3d37606560353639366065013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (101 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), shl(0x60, propertyChecker)) + mstore(add(ptr, 0x8f), shl(0x60, token)) + + // ------------------------------------------------------------------------------------------------------------- + // Total length is 163 (a3) bytes + // ------------------------------------------------------------------------------------------------------------- + + instance := create(0, ptr, 0xa3) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairETH. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param factory the factory that deployed the clone + * @param implementation the LSSVMPairETH implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC721ETHPairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37605160353639366051013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC20. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param implementation the LSSVMPairERC20 implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC721ERC20PairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37606560353639366065013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC1155ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = 92 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 93 bytes of extra data = 146 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 5d + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60923d8160093d39f33d3d3d3d363d3d37605d6035363936605d013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (93 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), nftId) + + instance := create(0, ptr, 0x9b) + } + } + + /** + * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + * + * This function uses the create opcode, which should never revert. + * + * During the delegate call, extra data is copied into the calldata which can then be + * accessed by the implementation contract. + * + * @return instance The address of the new pair instance + */ + function cloneERC1155ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId, + ERC20 token + ) internal returns (address instance) { + assembly { + let ptr := mload(0x40) + + // ------------------------------------------------------------------------------------------------------------- + // CREATION (9 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // creation size = 09 + // runtime size = a6 + // 60 runtime | PUSH1 runtime (r) | r | – + // 3d | RETURNDATASIZE | 0 r | – + // 81 | DUP2 | r 0 r | – + // 60 creation | PUSH1 creation (c) | c r 0 r | – + // 3d | RETURNDATASIZE | 0 c r 0 r | – + // 39 | CODECOPY | 0 r | [0-runSize): runtime code + // f3 | RETURN | | [0-runSize): runtime code + + // ------------------------------------------------------------------------------------------------------------- + // RUNTIME (53 bytes of code + 113 bytes of extra data = 166 bytes) + // ------------------------------------------------------------------------------------------------------------- + + // extra data size = 71 + // 3d | RETURNDATASIZE | 0 | – + // 3d | RETURNDATASIZE | 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 0 0 | – + // 36 | CALLDATASIZE | cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | – + // 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | – + // 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata + // 60 extra | PUSH1 extra | extra 0 0 0 0 | [0, cds) = calldata + // 60 0x35 | PUSH1 0x35 | 0x35 extra 0 0 0 0 | [0, cds) = calldata // 0x35 (53) is runtime size - data + // 36 | CALLDATASIZE | cds 0x35 extra 0 0 0 0 | [0, cds) = calldata + // 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 60 extra | PUSH1 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + mstore(ptr, hex"60a63d8160093d39f33d3d3d3d363d3d37607160353639366071013d73000000") + mstore(add(ptr, 0x1d), shl(0x60, implementation)) + + // 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x35) = extraData + // 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37) + // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0, rds) = return data + // 57 | JUMPI | 0 rds | [0, rds) = return data + // fd | REVERT | – | [0, rds) = return data + // 5b | JUMPDEST | 0 rds | [0, rds) = return data + // f3 | RETURN | – | [0, rds) = return data + mstore(add(ptr, 0x31), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + + // ------------------------------------------------------------------------------------------------------------- + // EXTRA DATA (113 bytes) + // ------------------------------------------------------------------------------------------------------------- + + mstore(add(ptr, 0x3e), shl(0x60, factory)) + mstore(add(ptr, 0x52), shl(0x60, bondingCurve)) + mstore(add(ptr, 0x66), shl(0x60, nft)) + mstore8(add(ptr, 0x7a), poolType) + mstore(add(ptr, 0x7b), nftId) + mstore(add(ptr, 0x9b), shl(0x60, token)) + + instance := create(0, ptr, 0xaf) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC1155ETH. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param factory the factory that deployed the clone + * @param implementation the LSSVMPairERC1155ETH implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC1155ETHPairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37605d6035363936605d013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } + + /** + * @notice Checks if a contract is a clone of a LSSVMPairERC1155ERC20. + * @dev Only checks the runtime bytecode, does not check the extra data. + * @param implementation the LSSVMPairERC1155ERC20 implementation contract + * @param query the contract to check + * @return result True if the contract is a clone, false otherwise + */ + function isERC1155ERC20PairClone(address factory, address implementation, address query) + internal + view + returns (bool result) + { + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr, hex"3d3d3d3d363d3d37607160353639366071013d73000000000000000000000000") + mstore(add(ptr, 0x14), shl(0x60, implementation)) + mstore(add(ptr, 0x28), hex"5af43d3d93803e603357fd5bf300000000000000000000000000000000000000") + mstore(add(ptr, 0x35), shl(0x60, factory)) + + // compare expected bytecode with that of the queried contract + let other := add(ptr, 0x49) + extcodecopy(query, other, 0, 0x49) + result := + and( + eq(mload(ptr), mload(other)), + and( + eq(mload(add(ptr, 0x20)), mload(add(other, 0x20))), + eq(mload(add(ptr, 0x29)), mload(add(other, 0x29))) + ) + ) + } + } +} + +contract SudoswapCWIAFactory { + event Target(address addr); + + function deploycloneERC721ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker) external returns (address split){ + split = LSSVMPairCloner.cloneERC721ETHPair(implementation, factory,bondingCurve, nft, poolType, propertyChecker); + emit Target(split); + } + function deploycloneERC721ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC721 nft, + uint8 poolType, + address propertyChecker, + ERC20 token) external returns (address split){ + split = LSSVMPairCloner.cloneERC721ERC20Pair(implementation, factory,bondingCurve, nft, poolType, propertyChecker, token); + emit Target(split); + } + + function deploycloneERC1155ETHPair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId) external returns (address split){ + split = LSSVMPairCloner.cloneERC1155ETHPair(implementation, factory,bondingCurve, nft, poolType, nftId); + emit Target(split); + } + + function deploycloneERC1155ERC20Pair( + address implementation, + ILSSVMPairFactoryLike factory, + ICurve bondingCurve, + IERC1155 nft, + uint8 poolType, + uint256 nftId, + ERC20 token) external returns (address split){ + split = LSSVMPairCloner.cloneERC1155ETHPair(implementation, factory,bondingCurve, nft, poolType, nftId); + emit Target(split); + } +} diff --git a/tests/functional/test_proxy.py b/tests/functional/test_proxy.py index 6e456bad91..74732cac24 100644 --- a/tests/functional/test_proxy.py +++ b/tests/functional/test_proxy.py @@ -91,7 +91,6 @@ def test_OldCWIA(get_contract_type, owner, ethereum, target): clones_proxy = contract_instance.clone1(0, sender=owner) proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) actual = ethereum.get_proxy_info(proxy_address) - actual = ethereum.get_proxy_info(contract_instance.address) assert actual is not None assert actual.type == ProxyType.Standard assert actual.target == target @@ -107,3 +106,22 @@ def test_Vyper(get_contract_type, owner, ethereum, target): assert actual is not None assert actual.type == ProxyType.Vyper assert actual.target == target + + +def test_SudoswapCWIA(get_contract_type, owner, ethereum, target): + _type = get_contract_type("SudoswapCWIA") + contract = ContractContainer(_type) + contract_instance = owner.deploy(contract) + clones_proxy = contract_instance.deploycloneERC721ETHPair(target, 0, 0, 0, 0, 0, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.SudoswapCWIA + assert actual.target == target + + clones_proxy = contract_instance.deploycloneERC1155ETHPair(target, 0, 0, 0, 0, 0, sender=owner) + proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + actual = ethereum.get_proxy_info(proxy_address) + assert actual is not None + assert actual.type == ProxyType.SudoswapCWIA + assert actual.target == target From 23548db600767e86bd49d57a95eca8d627b904f1 Mon Sep 17 00:00:00 2001 From: The Dance Date: Fri, 17 Jan 2025 12:10:13 +0000 Subject: [PATCH 3/4] test: tests for OldCWIA proxy --- .../data/contracts/ethereum/local/OldCWIA.json | 15 ++++++++++----- tests/functional/data/sources/OldCWIA.sol | 4 ++-- tests/functional/test_proxy.py | 7 +++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/functional/data/contracts/ethereum/local/OldCWIA.json b/tests/functional/data/contracts/ethereum/local/OldCWIA.json index 922bb324f2..d9697775cc 100644 --- a/tests/functional/data/contracts/ethereum/local/OldCWIA.json +++ b/tests/functional/data/contracts/ethereum/local/OldCWIA.json @@ -315,6 +315,11 @@ }, { "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + }, { "internalType": "bytes", "name": "data", @@ -348,7 +353,7 @@ ], "contractName": "Template", "deploymentBytecode": { - "bytecode": "0x60806040525f805460ff19166001179055348015601a575f5ffd5b50610669806100285f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063332b3e641461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c366004610490565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610490565b610143565b5f5461008f9060ff1681565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061018392505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061034092505050565b5f5f825160026101939190610512565b90505f6101a1826043610512565b90505f6101af600b8361052b565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b6034820152600285610245919061052b565b9450845f610254836043610512565b90506020890193505b602082106102955783518152610274602082610512565b9050610281602085610512565b935061028e60208361052b565b915061025d565b5f60016102a384602061052b565b6102af90610100610621565b6102b9919061052b565b1990508085511682528260206102cf919061052b565b6102d99083610512565b91508760f01b825286845ff098506001600160a01b0389166103325760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f9081610350826038610512565b90505f61035e600b8361052b565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f90816103c9826038610512565b90506020880192505b6020861061040a57825181526103e9602082610512565b90506103f6602084610512565b925061040360208761052b565b95506103d2565b5f600161041888602061052b565b61042490610100610621565b61042e919061052b565b19905080845116825285835ff097506001600160a01b0388166104835760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b6044820152606401610329565b5050505050505092915050565b5f5f602083850312156104a1575f5ffd5b823567ffffffffffffffff8111156104b7575f5ffd5b8301601f810185136104c7575f5ffd5b803567ffffffffffffffff8111156104dd575f5ffd5b8560208284010111156104ee575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610525576105256104fe565b92915050565b81810381811115610525576105256104fe565b6001815b60018411156105795780850481111561055d5761055d6104fe565b600184161561056b57908102905b60019390931c928002610542565b935093915050565b5f8261058f57506001610525565b8161059b57505f610525565b81600181146105b157600281146105bb576105d7565b6001915050610525565b60ff8411156105cc576105cc6104fe565b50506001821b610525565b5060208310610133831016604e8410600b84101617156105fa575081810a610525565b6106065f19848461053e565b805f1904821115610619576106196104fe565b029392505050565b5f61062c8383610581565b939250505056fea2646970667358221220cb5bb3c3a6888cbff08b06e22489530072b67ef62743eb6ffdf63db6316064f664736f6c634300081c0033" + "bytecode": "0x60806040525f805460ff19166001179055348015601a575f5ffd5b50610721806100285f395ff3fe608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80639a32d86c1461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c36600461051b565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610577565b610144565b5f5461008f9060ff1681565b5f6100fe8484848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506101c992505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a19392505050565b5f6101843084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061038692505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f5f825160026101d991906105ca565b90505f6101e78260436105ca565b90505f6101f5600b836105e3565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b603482015260028561028b91906105e3565b9450845f61029a8360436105ca565b90506020890193505b602082106102db57835181526102ba6020826105ca565b90506102c76020856105ca565b93506102d46020836105e3565b91506102a3565b5f60016102e98460206105e3565b6102f5906101006106d9565b6102ff91906105e3565b19905080855116825282602061031591906105e3565b61031f90836105ca565b91508760f01b825286845ff098506001600160a01b0389166103785760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f90816103968260386105ca565b90505f6103a4600b836105e3565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f908161040f8260386105ca565b90506020880192505b60208610610450578251815261042f6020826105ca565b905061043c6020846105ca565b92506104496020876105e3565b9550610418565b5f600161045e8860206105e3565b61046a906101006106d9565b61047491906105e3565b19905080845116825285835ff097506001600160a01b0388166104c95760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b604482015260640161036f565b5050505050505092915050565b5f5f83601f8401126104e6575f5ffd5b50813567ffffffffffffffff8111156104fd575f5ffd5b602083019150836020828501011115610514575f5ffd5b9250929050565b5f5f5f6040848603121561052d575f5ffd5b83356001600160a01b0381168114610543575f5ffd5b9250602084013567ffffffffffffffff81111561055e575f5ffd5b61056a868287016104d6565b9497909650939450505050565b5f5f60208385031215610588575f5ffd5b823567ffffffffffffffff81111561059e575f5ffd5b6105aa858286016104d6565b90969095509350505050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156105dd576105dd6105b6565b92915050565b818103818111156105dd576105dd6105b6565b6001815b600184111561063157808504811115610615576106156105b6565b600184161561062357908102905b60019390931c9280026105fa565b935093915050565b5f82610647575060016105dd565b8161065357505f6105dd565b816001811461066957600281146106735761068f565b60019150506105dd565b60ff841115610684576106846105b6565b50506001821b6105dd565b5060208310610133831016604e8410600b84101617156106b2575081810a6105dd565b6106be5f1984846105f6565b805f19048211156106d1576106d16105b6565b029392505050565b5f6106e48383610639565b939250505056fea2646970667358221220394404610e65ce641681188db9b7f35580c7ee2d9b0db66bc00db27f8efd777e64736f6c634300081c0033" }, "devdoc": { "kind": "dev", @@ -358,14 +363,14 @@ "methodIdentifiers": { "IS_TEST()": "0xfa7626d4", "clone1(bytes)": "0xce57ed7b", - "clone2(bytes)": "0x332b3e64", + "clone2(address,bytes)": "0x9a32d86c", "failed()": "0xba414fa6" }, "runtimeBytecode": { - "bytecode": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c8063332b3e641461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c366004610490565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610490565b610143565b5f5461008f9060ff1681565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061018392505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f6100fe3084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061034092505050565b5f5f825160026101939190610512565b90505f6101a1826043610512565b90505f6101af600b8361052b565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b6034820152600285610245919061052b565b9450845f610254836043610512565b90506020890193505b602082106102955783518152610274602082610512565b9050610281602085610512565b935061028e60208361052b565b915061025d565b5f60016102a384602061052b565b6102af90610100610621565b6102b9919061052b565b1990508085511682528260206102cf919061052b565b6102d99083610512565b91508760f01b825286845ff098506001600160a01b0389166103325760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f9081610350826038610512565b90505f61035e600b8361052b565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f90816103c9826038610512565b90506020880192505b6020861061040a57825181526103e9602082610512565b90506103f6602084610512565b925061040360208761052b565b95506103d2565b5f600161041888602061052b565b61042490610100610621565b61042e919061052b565b19905080845116825285835ff097506001600160a01b0388166104835760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b6044820152606401610329565b5050505050505092915050565b5f5f602083850312156104a1575f5ffd5b823567ffffffffffffffff8111156104b7575f5ffd5b8301601f810185136104c7575f5ffd5b803567ffffffffffffffff8111156104dd575f5ffd5b8560208284010111156104ee575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52601160045260245ffd5b80820180821115610525576105256104fe565b92915050565b81810381811115610525576105256104fe565b6001815b60018411156105795780850481111561055d5761055d6104fe565b600184161561056b57908102905b60019390931c928002610542565b935093915050565b5f8261058f57506001610525565b8161059b57505f610525565b81600181146105b157600281146105bb576105d7565b6001915050610525565b60ff8411156105cc576105cc6104fe565b50506001821b610525565b5060208310610133831016604e8410600b84101617156105fa575081810a610525565b6106065f19848461053e565b805f1904821115610619576106196104fe565b029392505050565b5f61062c8383610581565b939250505056fea2646970667358221220cb5bb3c3a6888cbff08b06e22489530072b67ef62743eb6ffdf63db6316064f664736f6c634300081c0033" + "bytecode": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80639a32d86c1461004e578063ba414fa61461007e578063ce57ed7b1461009f578063fa7626d4146100b2575b5f5ffd5b61006161005c36600461051b565b6100be565b6040516001600160a01b0390911681526020015b60405180910390f35b5f5461008f90610100900460ff1681565b6040519015158152602001610075565b6100616100ad366004610577565b610144565b5f5461008f9060ff1681565b5f6100fe8484848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506101c992505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a19392505050565b5f6101843084848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061038692505050565b6040516001600160a01b03821681529091507f783540fb4221a3238720dc7038937d0d79982bcf895274aa6ad179f82cf0d53c9060200160405180910390a192915050565b5f5f825160026101d991906105ca565b90505f6101e78260436105ca565b90505f6101f5600b836105e3565b90505f5f6040519050613d6160f01b81528260f01b60028201526680600b3d3981f360c81b600482015264363d3d376160d81b600b8201528460f01b601082015268603836393d3d3d366160b81b60128201528460f01b601b82015262013d7360e81b601d8201528760601b60208201526e5af43d82803e903d91603657fd5bf360881b603482015260028561028b91906105e3565b9450845f61029a8360436105ca565b90506020890193505b602082106102db57835181526102ba6020826105ca565b90506102c76020856105ca565b93506102d46020836105e3565b91506102a3565b5f60016102e98460206105e3565b6102f5906101006106d9565b6102ff91906105e3565b19905080855116825282602061031591906105e3565b61031f90836105ca565b91508760f01b825286845ff098506001600160a01b0389166103785760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b60448201526064015b60405180910390fd5b505050505050505092915050565b80515f90816103968260386105ca565b90505f6103a4600b836105e3565b604051613d6160f01b815260f082901b60028201526680600b3d3981f360c81b600482015269363d3d373d3d3d363d7360b01b600b820152606088901b60158201526e5af43d82803e903d91602b57fd5bf360881b60298201529091505f908161040f8260386105ca565b90506020880192505b60208610610450578251815261042f6020826105ca565b905061043c6020846105ca565b92506104496020876105e3565b9550610418565b5f600161045e8860206105e3565b61046a906101006106d9565b61047491906105e3565b19905080845116825285835ff097506001600160a01b0388166104c95760405162461bcd60e51b815260206004820152600d60248201526c18dc99585d194819985a5b1959609a1b604482015260640161036f565b5050505050505092915050565b5f5f83601f8401126104e6575f5ffd5b50813567ffffffffffffffff8111156104fd575f5ffd5b602083019150836020828501011115610514575f5ffd5b9250929050565b5f5f5f6040848603121561052d575f5ffd5b83356001600160a01b0381168114610543575f5ffd5b9250602084013567ffffffffffffffff81111561055e575f5ffd5b61056a868287016104d6565b9497909650939450505050565b5f5f60208385031215610588575f5ffd5b823567ffffffffffffffff81111561059e575f5ffd5b6105aa858286016104d6565b90969095509350505050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156105dd576105dd6105b6565b92915050565b818103818111156105dd576105dd6105b6565b6001815b600184111561063157808504811115610615576106156105b6565b600184161561062357908102905b60019390931c9280026105fa565b935093915050565b5f82610647575060016105dd565b8161065357505f6105dd565b816001811461066957600281146106735761068f565b60019150506105dd565b60ff841115610684576106846105b6565b50506001821b6105dd565b5060208310610133831016604e8410600b84101617156106b2575081810a6105dd565b6106be5f1984846105f6565b805f19048211156106d1576106d16105b6565b029392505050565b5f6106e48383610639565b939250505056fea2646970667358221220394404610e65ce641681188db9b7f35580c7ee2d9b0db66bc00db27f8efd777e64736f6c634300081c0033" }, - "sourceId": "OldCWIA.sol", - "sourcemap": "29372:564:0:-:0;;;1601:26;;;-1:-1:-1;;1601:26:0;1623:4;1601:26;;;29372:564;;;;;;;;;;;;;;;;", + "sourceId": "tests/functional/data/sources/OldCWIA.sol", + "sourcemap": "29372:589:0:-:0;;;1601:26;;;-1:-1:-1;;1601:26:0;1623:4;1601:26;;;29372:589;;;;;;;;;;;;;;;;", "userdoc": { "kind": "user", "methods": {}, diff --git a/tests/functional/data/sources/OldCWIA.sol b/tests/functional/data/sources/OldCWIA.sol index 000a9544a0..051219802f 100644 --- a/tests/functional/data/sources/OldCWIA.sol +++ b/tests/functional/data/sources/OldCWIA.sol @@ -769,8 +769,8 @@ contract Template is ClonesWithImmutableArgs, ClonesWithCallData { emit Cloned(address(clonedGreeter)); } - function clone2(bytes calldata data) external returns (Template clonedGreeter) { - clonedGreeter = Template(ClonesWithCallData.cloneWithCallDataProvision(address(this), data)); + function clone2(address implementation, bytes calldata data) external returns (Template clonedGreeter) { + clonedGreeter = Template(ClonesWithCallData.cloneWithCallDataProvision(implementation, data)); emit Cloned(address(clonedGreeter)); } } diff --git a/tests/functional/test_proxy.py b/tests/functional/test_proxy.py index 74732cac24..98213a3a2d 100644 --- a/tests/functional/test_proxy.py +++ b/tests/functional/test_proxy.py @@ -88,11 +88,14 @@ def test_OldCWIA(get_contract_type, owner, ethereum, target): _type = get_contract_type("OldCWIA") contract = ContractContainer(_type) contract_instance = owner.deploy(contract) - clones_proxy = contract_instance.clone1(0, sender=owner) + clones_proxy = contract_instance.clone2(target, 0, sender=owner) + print(clones_proxy, "clones_proxy ") proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) + print("proxy address", proxy_address) actual = ethereum.get_proxy_info(proxy_address) + print("actual", actual) assert actual is not None - assert actual.type == ProxyType.Standard + assert actual.type == ProxyType.OldCWIA assert actual.target == target From bb934e0d881ed17c1044bd5537a90bf2efa022bf Mon Sep 17 00:00:00 2001 From: The Dance Date: Fri, 17 Jan 2025 12:16:42 +0000 Subject: [PATCH 4/4] fix:fix lint --- tests/functional/test_proxy.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/functional/test_proxy.py b/tests/functional/test_proxy.py index 98213a3a2d..1411b7ff71 100644 --- a/tests/functional/test_proxy.py +++ b/tests/functional/test_proxy.py @@ -89,11 +89,8 @@ def test_OldCWIA(get_contract_type, owner, ethereum, target): contract = ContractContainer(_type) contract_instance = owner.deploy(contract) clones_proxy = contract_instance.clone2(target, 0, sender=owner) - print(clones_proxy, "clones_proxy ") proxy_address = to_checksum_address("0x" + (clones_proxy.logs[0]["data"].hex())[-40:]) - print("proxy address", proxy_address) actual = ethereum.get_proxy_info(proxy_address) - print("actual", actual) assert actual is not None assert actual.type == ProxyType.OldCWIA assert actual.target == target