diff --git a/.circleci/config.yml b/.circleci/config.yml index c58230b66..793ccb74c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,12 +28,6 @@ step_setup_global_packages: &step_setup_global_packages name: "Set up global packages" command: | npm install -step_pull_solc_docker: &step_pull_solc_docker - run: - name: "Pull solc docker images" - command: | - docker pull ethereum/solc:0.5.4 - docker pull ethereum/solc:0.6.12 step_setup_slither: &step_setup_slither run: name: "Setup slither analyser https://github.com/crytic/slither" @@ -46,16 +40,13 @@ step_setup_solc_select: &step_setup_solc_select command: | sudo pip3 install solc-select solc-select install 0.5.4 - solc-select install 0.6.12 + solc-select install 0.8.3 jobs: unit-test: <<: *job_common steps: - checkout - <<: *step_restore_cache - - setup_remote_docker: - version: 19.03.13 - - <<: *step_pull_solc_docker - <<: *step_setup_global_packages - run: name: "Lint JavaScript" @@ -66,9 +57,6 @@ jobs: - run: name: "Compiling external library contracts" command: npm run compile:lib - - run: - name: "Compiling legacy contracts" - command: npm run compile:legacy - run: name: "Compiling contracts" command: npm run compile @@ -85,11 +73,14 @@ jobs: - run: name: "Running unit tests" command: npm run ganache >/dev/null 2>&1 & npm run test - # Save coverage artifacts + # Save test artifacts - store_artifacts: path: gas-usage-report.log destination: reports/gas-usage-report.log - run: npx codechecks + - run: + name: "Running integration tests" + command: npm run mainnet-fork >/dev/null 2>&1 & npm run test:integration - run: name: "Running coverage" command: | @@ -106,17 +97,9 @@ jobs: steps: - checkout - <<: *step_restore_cache - - setup_remote_docker: - version: 19.03.13 - - <<: *step_pull_solc_docker - <<: *step_setup_global_packages - <<: *step_setup_slither - <<: *step_setup_solc_select - - run: - name: "Check TokenPriceRegistry tokens for ERC20 compliance" - command: | - export PATH=/home/circleci/.solc-select:$PATH - npm run validate:erc20 - run: name: "Run slither on infrastructure contracts based on solc 0.5" command: | @@ -124,10 +107,10 @@ jobs: npm run security:slither:infrastructure_0.5 when: always - run: - name: "Run slither on infrastructure contracts based on solc 0.6" + name: "Run slither on infrastructure contracts based on solc 0.8" command: | export PATH=/home/circleci/.solc-select:$PATH - npm run security:slither:infrastructure_0.6 + npm run security:slither:infrastructure when: always - run: name: "Run slither on wallet modules contracts" diff --git a/.eslintignore b/.eslintignore index 277ae0f6c..46b7d3a94 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,4 +5,5 @@ contracts/* contracts-test/* coverage/* lib/* -scripts/coverage.js \ No newline at end of file +scripts/coverage.js +tmp \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7fb824f85..521899d57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ node_modules build -build-legacy tmp bin .outputParameter @@ -30,8 +29,6 @@ flat .env utils/config/*.json !utils/config/development.json -!utils/config/kovan.json -!utils/config/kovan-fork.json ganache-accounts.json ## solidity-coverage diff --git a/.solcover.js b/.solcover.js index b3c530c0f..1936543c6 100644 --- a/.solcover.js +++ b/.solcover.js @@ -1,9 +1,11 @@ module.exports = { client: require('ganache-cli'), + measureStatementCoverage: false, + measureFunctionCoverage: false, skipFiles: [ "../contracts-test", - "../contracts-legacy", - "../lib" + "../lib_0.5", + "../lib_0.7" ], providerOptions: { port: 8555, diff --git a/.solhint.json b/.solhint.json index e57d329d6..d2da45aa8 100644 --- a/.solhint.json +++ b/.solhint.json @@ -9,6 +9,6 @@ "max-line-length": ["error", 150], "func-param-name-mixedcase": "error", "modifier-name-mixedcase": "error", - "reason-string": ["warn", { "maxLength":32 }] + "reason-string": ["error", { "maxLength":32 }] } } diff --git a/README.md b/README.md index 488e178c0..31fd4044c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Argent Wallet Smart Contracts -The Argent wallet is an Ethereum Smart Contract based mobile wallet. The wallet's user keeps an Ethereum account (Externally Owned Account) secretly on his mobile device. This account is set as the owner of the Smart Contract. User's funds (ETH and ERC20 tokens) are stored on the Smart Contract. With that model, logic can be added to the wallet to improve both the user experience and the wallet security. For instance, the wallet is guarded, recoverable, lockable, protected by a daily limit and upgradable. +The Argent wallet is an Ethereum Smart Contract based mobile wallet. The wallet's user keeps an Ethereum account (Externally Owned Account) secretly on his mobile device. This account is set as the owner of the Smart Contract. User's funds (ETH and ERC20 tokens) are stored on the Smart Contract. With that model, logic can be added to the wallet to improve both the user experience and the wallet security. For instance, the wallet is guarded, recoverable, lockable, and upgradable. See full specifications [here](specifications/specifications.pdf) diff --git a/audit/Release 2.5.0 audit G0Group Mar2021.pdf b/audit/Release 2.5.0 audit G0Group Mar2021.pdf new file mode 100644 index 000000000..6c7df2cab Binary files /dev/null and b/audit/Release 2.5.0 audit G0Group Mar2021.pdf differ diff --git a/build-legacy/v1.3.0/BaseWallet.json b/build-legacy/v1.3.0/BaseWallet.json new file mode 100644 index 000000000..59eafde0a --- /dev/null +++ b/build-legacy/v1.3.0/BaseWallet.json @@ -0,0 +1,329 @@ +{ + "contractName": "BaseWallet", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "implementation", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "name": "enabled", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorised", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "modules", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "bool" + } + ], + "name": "AuthorisedModule", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": true, + "name": "method", + "type": "bytes4" + } + ], + "name": "EnabledStaticCall", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Invoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + } + ], + "name": "init", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_module", + "type": "address" + }, + { + "name": "_value", + "type": "bool" + } + ], + "name": "authoriseModule", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_module", + "type": "address" + }, + { + "name": "_method", + "type": "bytes4" + } + ], + "name": "enableStaticCall", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_target", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "invoke", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610efc806100206000396000f3fe6080604052600436106100ce576000357c0100000000000000000000000000000000000000000000000000000000900480635f54892b116100865780638f6f03321161006b5780638f6f0332146103fb578063d6eb1bbf1461048d578063f7e80e98146104d4576100ce565b80635f54892b1461039a5780638da5cb5b146103e6576100ce565b80631f17732d116100b75780631f17732d146102a15780633c5a3cea146102dc5780635c60da1b14610369576100ce565b806313af40351461021357806313da30b214610246575b600036111561021157600080357fffffffff0000000000000000000000000000000000000000000000000000000016815260036020526040902054600160a060020a031680151561018a5733600160a060020a0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a361020f565b600160a060020a03811660009081526002602052604090205460ff1615156101eb57604051600080516020610e5a8339815191528152600401808060200182810382526030815260200180610e7a6030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e80801561020a573d6000f35b3d6000fd5b505b005b34801561021f57600080fd5b506102116004803603602081101561023657600080fd5b5035600160a060020a03166104fb565b34801561025257600080fd5b506102116004803603604081101561026957600080fd5b508035600160a060020a031690602001357fffffffff0000000000000000000000000000000000000000000000000000000016610619565b3480156102ad57600080fd5b50610211600480360360408110156102c457600080fd5b50600160a060020a0381351690602001351515610758565b3480156102e857600080fd5b50610211600480360360408110156102ff57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561032a57600080fd5b82018360208201111561033c57600080fd5b8035906020019184602083028401116401000000008311171561035e57600080fd5b50909250905061093b565b34801561037557600080fd5b5061037e610c19565b60408051600160a060020a039092168252519081900360200190f35b3480156103a657600080fd5b5061037e600480360360208110156103bd57600080fd5b50357fffffffff0000000000000000000000000000000000000000000000000000000016610c28565b3480156103f257600080fd5b5061037e610c43565b34801561040757600080fd5b506102116004803603606081101561041e57600080fd5b600160a060020a038235169160208101359181019060608101604082013564010000000081111561044e57600080fd5b82018360208201111561046057600080fd5b8035906020019184600183028401116401000000008311171561048257600080fd5b509092509050610c52565b34801561049957600080fd5b506104c0600480360360208110156104b057600080fd5b5035600160a060020a0316610deb565b604080519115158252519081900360200190f35b3480156104e057600080fd5b506104e9610e00565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff16151561055357604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03811615156105b85760408051600080516020610e5a833981519152815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60018054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff16151561067157604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff1615156106d257604051600080516020610e5a8339815191528152600401808060200182810382526030815260200180610e7a6030913960400191505060405180910390fd5b7fffffffff000000000000000000000000000000000000000000000000000000008116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff1615156107b057604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff161515811515146109375760018115151415610882576004805460019081018255600160a060020a038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561086557600080fd5b505af1158015610879573d6000803e3d6000fd5b505050506108f5565b6004805460001901908190556000106108d457604051600080516020610e5a8339815191528152600401808060200182810382526028815260200180610e076028913960400191505060405180910390fd5b600160a060020a0382166000908152600260205260409020805460ff191690555b6040805182151581529051600160a060020a038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a25b5050565b600154600160a060020a03161580156109545750600454155b15156109af5760408051600080516020610e5a833981519152815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b600081116109f657604051600080516020610e5a833981519152815260040180806020018281038252602b815260200180610e2f602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038516179055600481905560005b81811015610c135760026000848484818110610a3e57fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff1615610abe5760408051600080516020610e5a833981519152815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610ad057fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff1916911515919091179055828282818110610b0d57fe5b90506020020135600160a060020a0316600160a060020a03166319ab453c306040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018082600160a060020a0316600160a060020a03168152602001915050600060405180830381600087803b158015610b9057600080fd5b505af1158015610ba4573d6000803e3d6000fd5b505050508282828181101515610bb657fe5b90506020020135600160a060020a0316600160a060020a03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e16001604051808215151515815260200191505060405180910390a2600101610a26565b50505050565b600054600160a060020a031681565b600360205260009081526040902054600160a060020a031681565b600154600160a060020a031681565b3360009081526002602052604090205460ff161515610caa57604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600084600160a060020a0316848484604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610d0a576040519150601f19603f3d011682016040523d82523d6000602084013e610d0f565b606091505b50509050801515610d6f5760408051600080516020610e5a833981519152815260206004820152601960248201527f42573a2063616c6c20746f20746172676574206661696c656400000000000000604482015290519081900360640190fd5b8385600160a060020a031633600160a060020a03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365868660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a45050505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6508c379a00000000000000000000000000000000000000000000000000000000042573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a165627a7a72305820c686c99cce30daf1bd33402618601ecdb406f7cb5d8a2249399d45df643f2be00029", + "deployedBytecode": "0x6080604052600436106100ce576000357c0100000000000000000000000000000000000000000000000000000000900480635f54892b116100865780638f6f03321161006b5780638f6f0332146103fb578063d6eb1bbf1461048d578063f7e80e98146104d4576100ce565b80635f54892b1461039a5780638da5cb5b146103e6576100ce565b80631f17732d116100b75780631f17732d146102a15780633c5a3cea146102dc5780635c60da1b14610369576100ce565b806313af40351461021357806313da30b214610246575b600036111561021157600080357fffffffff0000000000000000000000000000000000000000000000000000000016815260036020526040902054600160a060020a031680151561018a5733600160a060020a0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a361020f565b600160a060020a03811660009081526002602052604090205460ff1615156101eb57604051600080516020610e5a8339815191528152600401808060200182810382526030815260200180610e7a6030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e80801561020a573d6000f35b3d6000fd5b505b005b34801561021f57600080fd5b506102116004803603602081101561023657600080fd5b5035600160a060020a03166104fb565b34801561025257600080fd5b506102116004803603604081101561026957600080fd5b508035600160a060020a031690602001357fffffffff0000000000000000000000000000000000000000000000000000000016610619565b3480156102ad57600080fd5b50610211600480360360408110156102c457600080fd5b50600160a060020a0381351690602001351515610758565b3480156102e857600080fd5b50610211600480360360408110156102ff57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561032a57600080fd5b82018360208201111561033c57600080fd5b8035906020019184602083028401116401000000008311171561035e57600080fd5b50909250905061093b565b34801561037557600080fd5b5061037e610c19565b60408051600160a060020a039092168252519081900360200190f35b3480156103a657600080fd5b5061037e600480360360208110156103bd57600080fd5b50357fffffffff0000000000000000000000000000000000000000000000000000000016610c28565b3480156103f257600080fd5b5061037e610c43565b34801561040757600080fd5b506102116004803603606081101561041e57600080fd5b600160a060020a038235169160208101359181019060608101604082013564010000000081111561044e57600080fd5b82018360208201111561046057600080fd5b8035906020019184600183028401116401000000008311171561048257600080fd5b509092509050610c52565b34801561049957600080fd5b506104c0600480360360208110156104b057600080fd5b5035600160a060020a0316610deb565b604080519115158252519081900360200190f35b3480156104e057600080fd5b506104e9610e00565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff16151561055357604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03811615156105b85760408051600080516020610e5a833981519152815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60018054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff16151561067157604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff1615156106d257604051600080516020610e5a8339815191528152600401808060200182810382526030815260200180610e7a6030913960400191505060405180910390fd5b7fffffffff000000000000000000000000000000000000000000000000000000008116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff1615156107b057604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff161515811515146109375760018115151415610882576004805460019081018255600160a060020a038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561086557600080fd5b505af1158015610879573d6000803e3d6000fd5b505050506108f5565b6004805460001901908190556000106108d457604051600080516020610e5a8339815191528152600401808060200182810382526028815260200180610e076028913960400191505060405180910390fd5b600160a060020a0382166000908152600260205260409020805460ff191690555b6040805182151581529051600160a060020a038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a25b5050565b600154600160a060020a03161580156109545750600454155b15156109af5760408051600080516020610e5a833981519152815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b600081116109f657604051600080516020610e5a833981519152815260040180806020018281038252602b815260200180610e2f602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038516179055600481905560005b81811015610c135760026000848484818110610a3e57fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff1615610abe5760408051600080516020610e5a833981519152815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610ad057fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff1916911515919091179055828282818110610b0d57fe5b90506020020135600160a060020a0316600160a060020a03166319ab453c306040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018082600160a060020a0316600160a060020a03168152602001915050600060405180830381600087803b158015610b9057600080fd5b505af1158015610ba4573d6000803e3d6000fd5b505050508282828181101515610bb657fe5b90506020020135600160a060020a0316600160a060020a03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e16001604051808215151515815260200191505060405180910390a2600101610a26565b50505050565b600054600160a060020a031681565b600360205260009081526040902054600160a060020a031681565b600154600160a060020a031681565b3360009081526002602052604090205460ff161515610caa57604051600080516020610e5a8339815191528152600401808060200182810382526027815260200180610eaa6027913960400191505060405180910390fd5b600084600160a060020a0316848484604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610d0a576040519150601f19603f3d011682016040523d82523d6000602084013e610d0f565b606091505b50509050801515610d6f5760408051600080516020610e5a833981519152815260206004820152601960248201527f42573a2063616c6c20746f20746172676574206661696c656400000000000000604482015290519081900360640190fd5b8385600160a060020a031633600160a060020a03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365868660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a45050505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6508c379a00000000000000000000000000000000000000000000000000000000042573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a165627a7a72305820c686c99cce30daf1bd33402618601ecdb406f7cb5d8a2249399d45df643f2be00029", + "compiler": { + "name": "solc", + "version": "0.5.4+commit.9549d8ff.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-06T06:24:29.204Z", + "devdoc": { + "author": "Julien Niset - ", + "details": "Simple modular wallet that authorises modules to call its invoke() method. Based on https://gist.github.com/Arachnid/a619d31f6d32757a4328a428286da186 by", + "methods": { + "authoriseModule(address,bool)": { + "details": "Enables/Disables a module.", + "params": { + "_module": "The target module.", + "_value": "Set to true to authorise the module." + } + }, + "enableStaticCall(address,bytes4)": { + "details": "Enables a static method by specifying the target module to which the call must be delegated.", + "params": { + "_method": "The static method signature.", + "_module": "The target module." + } + }, + "init(address,address[])": { + "details": "Inits the wallet by setting the owner and authorising a list of modules.", + "params": { + "_modules": "The modules to authorise.", + "_owner": "The owner." + } + }, + "invoke(address,uint256,bytes)": { + "details": "Performs a generic transaction.", + "params": { + "_data": "The data of the transaction.", + "_target": "The address for the transaction.", + "_value": "The value of the transaction." + } + }, + "setOwner(address)": { + "details": "Sets a new owner for the wallet.", + "params": { + "_newOwner": "The new owner." + } + } + }, + "title": "BaseWallet" + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/build-legacy/v1.3.0/Proxy.json b/build-legacy/v1.3.0/Proxy.json new file mode 100644 index 000000000..50f31d6f4 --- /dev/null +++ b/build-legacy/v1.3.0/Proxy.json @@ -0,0 +1,54 @@ +{ + "contractName": "Proxy", + "abi": [ + { + "inputs": [ + { + "name": "_implementation", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506040516020806101808339810180604052602081101561003057600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060f0806100906000396000f3fe608060405260008036905014801560165750600034115b15609a573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a360c2565b6000543660008037600080366000845af43d6000803e806000811460bd573d6000f35b3d6000fd5b00fea165627a7a7230582070ebec5c4c74b7533dfbbd71a94073501502a5f4c37e500186762e3032e1ba5d0029", + "deployedBytecode": "0x608060405260008036905014801560165750600034115b15609a573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600081840152601f19601f820116905080830192505050935050505060405180910390a360c2565b6000543660008037600080366000845af43d6000803e806000811460bd573d6000f35b3d6000fd5b00fea165627a7a7230582070ebec5c4c74b7533dfbbd71a94073501502a5f4c37e500186762e3032e1ba5d0029", + "compiler": { + "name": "solc", + "version": "0.5.4+commit.9549d8ff.Emscripten.clang", + "optimizer": false, + "runs": 200 + }, + "networks": {}, + "schemaVersion": "1.2.0", + "updatedAt": "2021-04-08T06:14:26.183Z" +} \ No newline at end of file diff --git a/build-legacy/v1.6.0/BaseWallet.json b/build-legacy/v1.6.0/BaseWallet.json new file mode 100644 index 000000000..2745f34e7 --- /dev/null +++ b/build-legacy/v1.6.0/BaseWallet.json @@ -0,0 +1,334 @@ +{ + "contractName": "BaseWallet", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "implementation", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "name": "enabled", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorised", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "modules", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "bool" + } + ], + "name": "AuthorisedModule", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": true, + "name": "method", + "type": "bytes4" + } + ], + "name": "EnabledStaticCall", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "module", + "type": "address" + }, + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Invoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + } + ], + "name": "init", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_module", + "type": "address" + }, + { + "name": "_value", + "type": "bool" + } + ], + "name": "authoriseModule", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_module", + "type": "address" + }, + { + "name": "_method", + "type": "bytes4" + } + ], + "name": "enableStaticCall", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_target", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "invoke", + "outputs": [ + { + "name": "_result", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610f77806100206000396000f3fe6080604052600436106100ce576000357c0100000000000000000000000000000000000000000000000000000000900480635f54892b116100865780638f6f03321161006b5780638f6f0332146103fb578063d6eb1bbf14610502578063f7e80e9814610549576100ce565b80635f54892b1461039a5780638da5cb5b146103e6576100ce565b80631f17732d116100b75780631f17732d146102a15780633c5a3cea146102dc5780635c60da1b14610369576100ce565b806313af40351461021357806313da30b214610246575b600036111561021157600080357fffffffff0000000000000000000000000000000000000000000000000000000016815260036020526040902054600160a060020a031680151561018a5733600160a060020a0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a361020f565b600160a060020a03811660009081526002602052604090205460ff1615156101eb57604051600080516020610ed58339815191528152600401808060200182810382526030815260200180610ef56030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e80801561020a573d6000f35b3d6000fd5b505b005b34801561021f57600080fd5b506102116004803603602081101561023657600080fd5b5035600160a060020a0316610570565b34801561025257600080fd5b506102116004803603604081101561026957600080fd5b508035600160a060020a031690602001357fffffffff000000000000000000000000000000000000000000000000000000001661068e565b3480156102ad57600080fd5b50610211600480360360408110156102c457600080fd5b50600160a060020a03813516906020013515156107cd565b3480156102e857600080fd5b50610211600480360360408110156102ff57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561032a57600080fd5b82018360208201111561033c57600080fd5b8035906020019184602083028401116401000000008311171561035e57600080fd5b5090925090506109af565b34801561037557600080fd5b5061037e610cd9565b60408051600160a060020a039092168252519081900360200190f35b3480156103a657600080fd5b5061037e600480360360208110156103bd57600080fd5b50357fffffffff0000000000000000000000000000000000000000000000000000000016610ce8565b3480156103f257600080fd5b5061037e610d03565b34801561040757600080fd5b5061048d6004803603606081101561041e57600080fd5b600160a060020a038235169160208101359181019060608101604082013564010000000081111561044e57600080fd5b82018360208201111561046057600080fd5b8035906020019184600183028401116401000000008311171561048257600080fd5b509092509050610d12565b6040805160208082528351818301528351919283929083019185019080838360005b838110156104c75781810151838201526020016104af565b50505050905090810190601f1680156104f45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561050e57600080fd5b506105356004803603602081101561052557600080fd5b5035600160a060020a0316610e66565b604080519115158252519081900360200190f35b34801561055557600080fd5b5061055e610e7b565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff1615156105c857604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a038116151561062d5760408051600080516020610ed5833981519152815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60018054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff1615156106e657604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff16151561074757604051600080516020610ed58339815191528152600401808060200182810382526030815260200180610ef56030913960400191505060405180910390fd5b7fffffffff000000000000000000000000000000000000000000000000000000008116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff16151561082557604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff161515811515146109ab576040805182151581529051600160a060020a038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a260018115151415610938576004805460019081018255600160a060020a038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561091b57600080fd5b505af115801561092f573d6000803e3d6000fd5b505050506109ab565b60048054600019019081905560001061098a57604051600080516020610ed58339815191528152600401808060200182810382526028815260200180610e826028913960400191505060405180910390fd5b600160a060020a0382166000908152600260205260409020805460ff191690555b5050565b600154600160a060020a03161580156109c85750600454155b1515610a235760408051600080516020610ed5833981519152815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b60008111610a6a57604051600080516020610ed5833981519152815260040180806020018281038252602b815260200180610eaa602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038516179055600481905560005b81811015610c875760026000848484818110610ab257fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff1615610b325760408051600080516020610ed5833981519152815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610b4457fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff1916911515919091179055828282818110610b8157fe5b90506020020135600160a060020a0316600160a060020a03166319ab453c306040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018082600160a060020a0316600160a060020a03168152602001915050600060405180830381600087803b158015610c0457600080fd5b505af1158015610c18573d6000803e3d6000fd5b505050508282828181101515610c2a57fe5b90506020020135600160a060020a0316600160a060020a03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e16001604051808215151515815260200191505060405180910390a2600101610a9a565b50600030311115610cd457604080516020808252600090820181905291513031917f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef738919081900360600190a35b505050565b600054600160a060020a031681565b600360205260009081526040902054600160a060020a031681565b600154600160a060020a031681565b3360009081526002602052604090205460609060ff161515610d6d57604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600085600160a060020a0316858585604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610dcd576040519150601f19603f3d011682016040523d82523d6000602084013e610dd2565b606091505b5092509050801515610de8573d6000803e3d6000fd5b8486600160a060020a031633600160a060020a03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365878760405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a450949350505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6508c379a00000000000000000000000000000000000000000000000000000000042573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a165627a7a72305820e43850ee658d9f058191bf2b17c719e04fb454e4af36c33ef0832901baf56b300029", + "deployedBytecode": "0x6080604052600436106100ce576000357c0100000000000000000000000000000000000000000000000000000000900480635f54892b116100865780638f6f03321161006b5780638f6f0332146103fb578063d6eb1bbf14610502578063f7e80e9814610549576100ce565b80635f54892b1461039a5780638da5cb5b146103e6576100ce565b80631f17732d116100b75780631f17732d146102a15780633c5a3cea146102dc5780635c60da1b14610369576100ce565b806313af40351461021357806313da30b214610246575b600036111561021157600080357fffffffff0000000000000000000000000000000000000000000000000000000016815260036020526040902054600160a060020a031680151561018a5733600160a060020a0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a361020f565b600160a060020a03811660009081526002602052604090205460ff1615156101eb57604051600080516020610ed58339815191528152600401808060200182810382526030815260200180610ef56030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e80801561020a573d6000f35b3d6000fd5b505b005b34801561021f57600080fd5b506102116004803603602081101561023657600080fd5b5035600160a060020a0316610570565b34801561025257600080fd5b506102116004803603604081101561026957600080fd5b508035600160a060020a031690602001357fffffffff000000000000000000000000000000000000000000000000000000001661068e565b3480156102ad57600080fd5b50610211600480360360408110156102c457600080fd5b50600160a060020a03813516906020013515156107cd565b3480156102e857600080fd5b50610211600480360360408110156102ff57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561032a57600080fd5b82018360208201111561033c57600080fd5b8035906020019184602083028401116401000000008311171561035e57600080fd5b5090925090506109af565b34801561037557600080fd5b5061037e610cd9565b60408051600160a060020a039092168252519081900360200190f35b3480156103a657600080fd5b5061037e600480360360208110156103bd57600080fd5b50357fffffffff0000000000000000000000000000000000000000000000000000000016610ce8565b3480156103f257600080fd5b5061037e610d03565b34801561040757600080fd5b5061048d6004803603606081101561041e57600080fd5b600160a060020a038235169160208101359181019060608101604082013564010000000081111561044e57600080fd5b82018360208201111561046057600080fd5b8035906020019184600183028401116401000000008311171561048257600080fd5b509092509050610d12565b6040805160208082528351818301528351919283929083019185019080838360005b838110156104c75781810151838201526020016104af565b50505050905090810190601f1680156104f45780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561050e57600080fd5b506105356004803603602081101561052557600080fd5b5035600160a060020a0316610e66565b604080519115158252519081900360200190f35b34801561055557600080fd5b5061055e610e7b565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff1615156105c857604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a038116151561062d5760408051600080516020610ed5833981519152815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60018054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff1615156106e657604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff16151561074757604051600080516020610ed58339815191528152600401808060200182810382526030815260200180610ef56030913960400191505060405180910390fd5b7fffffffff000000000000000000000000000000000000000000000000000000008116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff16151561082557604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600160a060020a03821660009081526002602052604090205460ff161515811515146109ab576040805182151581529051600160a060020a038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a260018115151415610938576004805460019081018255600160a060020a038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561091b57600080fd5b505af115801561092f573d6000803e3d6000fd5b505050506109ab565b60048054600019019081905560001061098a57604051600080516020610ed58339815191528152600401808060200182810382526028815260200180610e826028913960400191505060405180910390fd5b600160a060020a0382166000908152600260205260409020805460ff191690555b5050565b600154600160a060020a03161580156109c85750600454155b1515610a235760408051600080516020610ed5833981519152815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b60008111610a6a57604051600080516020610ed5833981519152815260040180806020018281038252602b815260200180610eaa602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038516179055600481905560005b81811015610c875760026000848484818110610ab257fe5b60209081029290920135600160a060020a03168352508101919091526040016000205460ff1615610b325760408051600080516020610ed5833981519152815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610b4457fe5b60209081029290920135600160a060020a0316835250810191909152604001600020805460ff1916911515919091179055828282818110610b8157fe5b90506020020135600160a060020a0316600160a060020a03166319ab453c306040518263ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018082600160a060020a0316600160a060020a03168152602001915050600060405180830381600087803b158015610c0457600080fd5b505af1158015610c18573d6000803e3d6000fd5b505050508282828181101515610c2a57fe5b90506020020135600160a060020a0316600160a060020a03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e16001604051808215151515815260200191505060405180910390a2600101610a9a565b50600030311115610cd457604080516020808252600090820181905291513031917f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef738919081900360600190a35b505050565b600054600160a060020a031681565b600360205260009081526040902054600160a060020a031681565b600154600160a060020a031681565b3360009081526002602052604090205460609060ff161515610d6d57604051600080516020610ed58339815191528152600401808060200182810382526027815260200180610f256027913960400191505060405180910390fd5b600085600160a060020a0316858585604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610dcd576040519150601f19603f3d011682016040523d82523d6000602084013e610dd2565b606091505b5092509050801515610de8573d6000803e3d6000fd5b8486600160a060020a031633600160a060020a03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365878760405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a450949350505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6508c379a00000000000000000000000000000000000000000000000000000000042573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a165627a7a72305820e43850ee658d9f058191bf2b17c719e04fb454e4af36c33ef0832901baf56b300029", + "compiler": { + "name": "solc", + "version": "0.5.4+commit.9549d8ff.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-06T06:24:55.629Z", + "devdoc": { + "author": "Julien Niset - ", + "details": "Simple modular wallet that authorises modules to call its invoke() method.", + "methods": { + "authoriseModule(address,bool)": { + "details": "Enables/Disables a module.", + "params": { + "_module": "The target module.", + "_value": "Set to true to authorise the module." + } + }, + "enableStaticCall(address,bytes4)": { + "details": "Enables a static method by specifying the target module to which the call must be delegated.", + "params": { + "_method": "The static method signature.", + "_module": "The target module." + } + }, + "init(address,address[])": { + "details": "Inits the wallet by setting the owner and authorising a list of modules.", + "params": { + "_modules": "The modules to authorise.", + "_owner": "The owner." + } + }, + "invoke(address,uint256,bytes)": { + "details": "Performs a generic transaction.", + "params": { + "_data": "The data of the transaction.", + "_target": "The address for the transaction.", + "_value": "The value of the transaction." + } + }, + "setOwner(address)": { + "details": "Sets a new owner for the wallet.", + "params": { + "_newOwner": "The new owner." + } + } + }, + "title": "BaseWallet" + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/build-legacy/v1.6.0/Proxy.json b/build-legacy/v1.6.0/Proxy.json new file mode 100644 index 000000000..2c18beb77 --- /dev/null +++ b/build-legacy/v1.6.0/Proxy.json @@ -0,0 +1,61 @@ +{ + "contractName": "Proxy", + "abi": [ + { + "inputs": [ + { + "name": "_implementation", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506040516020806101478339810180604052602081101561003057600080fd5b505160008054600160a060020a03909216600160a060020a031990921691909117905560e6806100616000396000f3fe60806040523615801560115750600034115b156092573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a360b8565b6000543660008037600080366000845af43d6000803e80801560b3573d6000f35b3d6000fd5b00fea165627a7a72305820016c3f8808001d8a7dd22460d6a5ad8141dac31725bd1d1bd6127b0638562cbd0029", + "deployedBytecode": "0x60806040523615801560115750600034115b156092573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a360b8565b6000543660008037600080366000845af43d6000803e80801560b3573d6000f35b3d6000fd5b00fea165627a7a72305820016c3f8808001d8a7dd22460d6a5ad8141dac31725bd1d1bd6127b0638562cbd0029", + "compiler": { + "name": "solc", + "version": "0.5.4+commit.9549d8ff.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-07T14:54:55.326Z", + "devdoc": { + "author": "Julien Niset - ", + "details": "Basic proxy that delegates all calls to a fixed implementing contract. The implementing contract cannot be upgraded.", + "methods": {}, + "title": "Proxy" + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/build-legacy/v1.6.0/WalletFactory.json b/build-legacy/v1.6.0/WalletFactory.json new file mode 100644 index 000000000..0abbd7a1c --- /dev/null +++ b/build-legacy/v1.6.0/WalletFactory.json @@ -0,0 +1,586 @@ +{ + "contractName": "WalletFactory", + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_manager", + "type": "address" + } + ], + "name": "addManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_manager", + "type": "address" + } + ], + "name": "revokeManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ensManager", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "walletImplementation", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newOwner", + "type": "address" + } + ], + "name": "changeOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "moduleRegistry", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "guardianStorage", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "managers", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "name": "_moduleRegistry", + "type": "address" + }, + { + "name": "_walletImplementation", + "type": "address" + }, + { + "name": "_ensManager", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "addr", + "type": "address" + } + ], + "name": "ModuleRegistryChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "addr", + "type": "address" + } + ], + "name": "ENSManagerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "addr", + "type": "address" + } + ], + "name": "GuardianStorageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "wallet", + "type": "address" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "guardian", + "type": "address" + } + ], + "name": "WalletCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_manager", + "type": "address" + } + ], + "name": "ManagerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_manager", + "type": "address" + } + ], + "name": "ManagerRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_label", + "type": "string" + } + ], + "name": "createWallet", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_label", + "type": "string" + }, + { + "name": "_guardian", + "type": "address" + } + ], + "name": "createWalletWithGuardian", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_label", + "type": "string" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "createCounterfactualWallet", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_label", + "type": "string" + }, + { + "name": "_guardian", + "type": "address" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "createCounterfactualWalletWithGuardian", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "getAddressForCounterfactualWallet", + "outputs": [ + { + "name": "_wallet", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_modules", + "type": "address[]" + }, + { + "name": "_guardian", + "type": "address" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "getAddressForCounterfactualWalletWithGuardian", + "outputs": [ + { + "name": "_wallet", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_moduleRegistry", + "type": "address" + } + ], + "name": "changeModuleRegistry", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_ensManager", + "type": "address" + } + ], + "name": "changeENSManager", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_guardianStorage", + "type": "address" + } + ], + "name": "changeGuardianStorage", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_wallet", + "type": "address" + } + ], + "name": "init", + "outputs": [], + "payable": false, + "stateMutability": "pure", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b5060405160608061244f8339810180604052606081101561003057600080fd5b508051602082015160409092015160008054600160a060020a0319908116331790915560028054600160a060020a0394851690831617905560038054948416948216949094179093556004805492909116919092161790556123b8806100976000396000f3fe608060405234801561001057600080fd5b506004361061016e576000357c0100000000000000000000000000000000000000000000000000000000900480639d1df5ee116100ea578063bb1cef721161009e578063c6e79bbe11610083578063c6e79bbe1461064f578063d89784fc1461072a578063fdff9b4d146107325761016e565b8063bb1cef72146104df578063c3606c881461056e5761016e565b8063aff18575116100cf578063aff1857514610385578063b95459e414610457578063b97ccf2f1461045f5761016e565b80639d1df5ee14610339578063a6f9dae11461035f5761016e565b8063377e32e6116101415780638117abc1116101265780638117abc1146103035780638da5cb5b1461030b57806390ed991c146103135761016e565b8063377e32e6146102b95780635a6971f9146102df5761016e565b806308d668bc1461017357806319ab453c1461019b5780632d06177a146101c1578063350aaa9a146101e7575b600080fd5b6101996004803603602081101561018957600080fd5b5035600160a060020a031661076c565b005b610199600480360360208110156101b157600080fd5b5035600160a060020a0316610899565b610199600480360360208110156101d757600080fd5b5035600160a060020a031661089c565b610199600480360360808110156101fd57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561022857600080fd5b82018360208201111561023a57600080fd5b8035906020019184602083028401116401000000008311171561025c57600080fd5b91939092909160208101903564010000000081111561027a57600080fd5b82018360208201111561028c57600080fd5b803590602001918460018302840111640100000000831117156102ae57600080fd5b9193509150356109d9565b610199600480360360208110156102cf57600080fd5b5035600160a060020a0316610ac6565b6102e7610bdb565b60408051600160a060020a039092168252519081900360200190f35b6102e7610bea565b6102e7610bf9565b6101996004803603602081101561032957600080fd5b5035600160a060020a0316610c08565b6101996004803603602081101561034f57600080fd5b5035600160a060020a0316610d35565b6101996004803603602081101561037557600080fd5b5035600160a060020a0316610e62565b6101996004803603606081101561039b57600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156103c657600080fd5b8201836020820111156103d857600080fd5b803590602001918460208302840111640100000000831117156103fa57600080fd5b91939092909160208101903564010000000081111561041857600080fd5b82018360208201111561042a57600080fd5b8035906020019184600183028401116401000000008311171561044c57600080fd5b509092509050610f83565b6102e761106d565b6102e76004803603606081101561047557600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156104a057600080fd5b8201836020820111156104b257600080fd5b803590602001918460208302840111640100000000831117156104d457600080fd5b91935091503561107c565b6102e7600480360360808110156104f557600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561052057600080fd5b82018360208201111561053257600080fd5b8035906020019184602083028401116401000000008311171561055457600080fd5b9193509150600160a060020a0381351690602001356110c6565b610199600480360360a081101561058457600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156105af57600080fd5b8201836020820111156105c157600080fd5b803590602001918460208302840111640100000000831117156105e357600080fd5b91939092909160208101903564010000000081111561060157600080fd5b82018360208201111561061357600080fd5b8035906020019184600183028401116401000000008311171561063557600080fd5b9193509150600160a060020a038135169060200135611178565b6101996004803603608081101561066557600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561069057600080fd5b8201836020820111156106a257600080fd5b803590602001918460208302840111640100000000831117156106c457600080fd5b9193909290916020810190356401000000008111156106e257600080fd5b8201836020820111156106f457600080fd5b8035906020019184600183028401116401000000008311171561071657600080fd5b919350915035600160a060020a031661131e565b6102e76114b9565b6107586004803603602081101561074857600080fd5b5035600160a060020a03166114c8565b604080519115158252519081900360200190f35b600054600160a060020a031633146107d3576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610838576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60028054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f9bf4baeb20b6008af8dfd7fed5c50dce707a05623b022e5d61a00c7db7f90c729181900360200190a150565b50565b600054600160a060020a03163314610903576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610968576040805160008051602061234a833981519152815260206004820152601b60248201527f4d3a2041646472657373206d757374206e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b600160a060020a03811660009081526001602052604090205460ff16151561089957600160a060020a0381166000818152600160208190526040808320805460ff1916909217909155517f3b4a40cccf2058c593542587329dd385be4f0b588db5471fbd9598e56dd7093a9190a250565b3360009081526001602081905260409091205460ff16151514610a4b576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b610abe8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092018290525092508791506114dd9050565b505050505050565b600054600160a060020a03163314610b2d576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a03811660009081526001602081905260409091205460ff16151514610b925760405160008051602061234a83398151915281526004018080602001828103825260258152602001806122d26025913960400191505060405180910390fd5b600160a060020a038116600081815260016020526040808220805460ff19169055517fe5def11e0516f317f9c37b8835aec29fc01db4d4b6d6fecaca339d3596a29bc19190a250565b600454600160a060020a031681565b600354600160a060020a031681565b600054600160a060020a031681565b600054600160a060020a03163314610c6f576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610cd4576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60048054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f5b22021f5b1f5f8a744edb1f20f667875f22a1b29c4d9a46418ee25110c76cb89181900360200190a150565b600054600160a060020a03163314610d9c576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610e01576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60058054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fe897b5d59fcdf32efc14ac4f270d09939e2280487d6d5e156dcb41a29cb034399181900360200190a150565b600054600160a060020a03163314610ec9576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610f2e576040805160008051602061234a833981519152815260206004820152601860248201527f41646472657373206d757374206e6f74206265206e756c6c0000000000000000604482015290519081900360640190fd5b6000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038316908117825560405190917fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf3691a250565b3360009081526001602081905260409091205460ff16151514610ff5576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b6110668585858080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f89018190048102820181019092528781529250879150869081908401838280828437600092018290525092506115c3915050565b5050505050565b600254600160a060020a031681565b60006110bd85858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525092508791506116289050565b95945050505050565b6000600160a060020a038316151561112d576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b61116e868686808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508892508791506116289050565b9695505050505050565b3360009081526001602081905260409091205460ff161515146111ea576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a0316151561123b5760405160008051602061234a833981519152815260040180806020018281038252602381526020018061236a6023913960400191505060405180910390fd5b600160a060020a03821615156112a0576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b6113158787878080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284376000920191909152508892508791506114dd9050565b50505050505050565b3360009081526001602081905260409091205460ff16151514611390576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156113e15760405160008051602061234a833981519152815260040180806020018281038252602381526020018061236a6023913960400191505060405180910390fd5b600160a060020a0381161515611446576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b610abe8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152508792506115c3915050565b600554600160a060020a031681565b60016020526000908152604090205460ff1681565b6114e8858585611744565b60006114f682878786611964565b905060606040518060200161150a9061217d565b601f1982820381018352601f9091011660408190526003548251600160a060020a039091169160209081019182918501908083835b6020831061155e5780518252601f19909201916020918201910161153f565b51815160209384036101000a6000190180199092169116179052920193845250604080518085038152938201905282519294506000935085929150840183f59050803b15156115ac573d6000fd5b6115b98189898989611ab5565b5050505050505050565b6115ce848484611744565b600354604051600091600160a060020a0316906115ea9061217d565b600160a060020a03909116815260405190819003602001906000f080158015611617573d6000803e3d6000fd5b50905080610abe8187878787611ab5565b60008061163783878787611964565b905060606040518060200161164b9061217d565b601f1982820381018352601f9091011660408190526003548251600160a060020a039091169160209081019182918501908083835b6020831061169f5780518252601f199092019160209182019101611680565b51815160209384036101000a600019018019909216911617905292019384525060408051808503815284830182528051908301207fff0000000000000000000000000000000000000000000000000000000000000082860152306c010000000000000000000000000260418601526055850197909752607580850197909752805180850390970187526095909301909252508351930192909220979650505050505050565b600160a060020a03831615156117a9576040805160008051602061234a833981519152815260206004820152601860248201527f57463a206f776e65722063616e6e6f74206265206e756c6c0000000000000000604482015290519081900360640190fd5b81516000106117f15760405160008051602061234a83398151915281526004018080602001828103825260298152602001806123216029913960400191505060405180910390fd5b6002546040517f6bb18a54000000000000000000000000000000000000000000000000000000008152602060048201818152855160248401528551600160a060020a0390941693636bb18a549387938392604490920191818601910280838360005b8381101561186b578181015183820152602001611853565b505050509050019250505060206040518083038186803b15801561188e57600080fd5b505afa1580156118a2573d6000803e3d6000fd5b505050506040513d60208110156118b857600080fd5b505115156118ff5760405160008051602061234a833981519152815260040180806020018281038252602a8152602001806122f7602a913960400191505060405180910390fd5b80518190151561195e576040805160008051602061234a833981519152815260206004820152601d60248201527f57463a20454e53206c61626c65206d75737420626520646566696e6564000000604482015290519081900360640190fd5b50505050565b6000600160a060020a03821615156119ff578484846040516020018084815260200183600160a060020a0316600160a060020a03166c01000000000000000000000000028152601401828051906020019060200280838360005b838110156119d65781810151838201526020016119be565b505050509050019350505050604051602081830303815290604052805190602001209050611aad565b848484846040516020018085815260200184600160a060020a0316600160a060020a03166c01000000000000000000000000028152601401838051906020019060200280838360005b83811015611a60578181015183820152602001611a48565b5050505090500182600160a060020a0316600160a060020a03166c010000000000000000000000000281526014019450505050506040516020818303038152906040528051906020012090505b949350505050565b60608351600101604051908082528060200260200182016040528015611ae5578160200160208202803883390190505b50905030816000815181101515611af857fe5b600160a060020a0390921660209283029091019091015260005b8451811015611b63578481815181101515611b2957fe5b906020019060200201518282600101815181101515611b4457fe5b600160a060020a03909216602092830290910190910152600101611b12565b50604080517f3c5a3cea000000000000000000000000000000000000000000000000000000008152600160a060020a038781166004830190815260248301938452845160448401528451918a1693633c5a3cea938a9387939291606401906020808601910280838360005b83811015611be6578181015183820152602001611bce565b505050509050019350505050600060405180830381600087803b158015611c0c57600080fd5b505af1158015611c20573d6000803e3d6000fd5b50505050600160a060020a03821615611cbb57600554604080517fc6845210000000000000000000000000000000000000000000000000000000008152600160a060020a03898116600483015285811660248301529151919092169163c684521091604480830192600092919082900301818387803b158015611ca257600080fd5b505af1158015611cb6573d6000803e3d6000fd5b505050505b611cc58684611d94565b604080517f1f17732d0000000000000000000000000000000000000000000000000000000081523060048201526000602482018190529151600160a060020a03891692631f17732d926044808201939182900301818387803b158015611d2a57600080fd5b505af1158015611d3e573d6000803e3d6000fd5b5050505081600160a060020a031685600160a060020a031687600160a060020a03167fca0b7dde26052d34217ef1a0cee48085a07ca32da0a918609937a307d496bbf560405160405180910390a4505050505050565b6000600460009054906101000a9004600160a060020a0316600160a060020a031663adce1c5f6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b158015611e0057600080fd5b505afa158015611e14573d6000803e3d6000fd5b505050506040513d6020811015611e2a57600080fd5b50516004805460408051600160a060020a03928316602482018190529285166044808301919091528251808303909101815260649091018252602081810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0f5a54660000000000000000000000000000000000000000000000000000000017905282517f09d73442000000000000000000000000000000000000000000000000000000008152925195965090946000946309d734429380820193929190829003018186803b158015611efc57600080fd5b505afa158015611f10573d6000803e3d6000fd5b505050506040513d6020811015611f2657600080fd5b50516040517f8f6f0332000000000000000000000000000000000000000000000000000000008152600160a060020a0380831660048301908152600060248401819052606060448501908152875160648601528751959650928a1694638f6f03329487949293899390929091608401906020850190808383895b83811015611fb8578181015183820152602001611fa0565b50505050905090810190601f168015611fe55780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b15801561200657600080fd5b505af115801561201a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561204357600080fd5b81019080805164010000000081111561205b57600080fd5b8201602081018481111561206e57600080fd5b815164010000000081118282018710171561208857600080fd5b505060048054604080517f1e59c529000000000000000000000000000000000000000000000000000000008152600160a060020a038d811660248301529381019182528b5160448201528b51939092169650631e59c52995508a94508b935091829160640190602086019080838360005b838110156121115781810151838201526020016120f9565b50505050905090810190601f16801561213e5780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561215e57600080fd5b505af1158015612172573d6000803e3d6000fd5b505050505050505050565b6101478061218b8339019056fe608060405234801561001057600080fd5b506040516020806101478339810180604052602081101561003057600080fd5b505160008054600160a060020a03909216600160a060020a031990921691909117905560e6806100616000396000f3fe60806040523615801560115750600034115b156092573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a360b8565b6000543660008037600080366000845af43d6000803e80801560b3573d6000f35b3d6000fd5b00fea165627a7a72305820016c3f8808001d8a7dd22460d6a5ad8141dac31725bd1d1bd6127b0638562cbd00294d3a20546172676574206d75737420626520616e206578697374696e67206d616e6167657257463a206f6e65206f72206d6f7265206d6f64756c657320617265206e6f74207265676973746572656457463a2063616e6e6f742061737369676e2077697468206c657373207468616e2031206d6f64756c6508c379a000000000000000000000000000000000000000000000000000000000477561726469616e53746f726167652061646472657373206e6f7420646566696e6564a165627a7a7230582081e0f10e29201ba2bfb3ec3cbd31bc1eaf7fa5875720831dee50095794b64f520029", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061016e576000357c0100000000000000000000000000000000000000000000000000000000900480639d1df5ee116100ea578063bb1cef721161009e578063c6e79bbe11610083578063c6e79bbe1461064f578063d89784fc1461072a578063fdff9b4d146107325761016e565b8063bb1cef72146104df578063c3606c881461056e5761016e565b8063aff18575116100cf578063aff1857514610385578063b95459e414610457578063b97ccf2f1461045f5761016e565b80639d1df5ee14610339578063a6f9dae11461035f5761016e565b8063377e32e6116101415780638117abc1116101265780638117abc1146103035780638da5cb5b1461030b57806390ed991c146103135761016e565b8063377e32e6146102b95780635a6971f9146102df5761016e565b806308d668bc1461017357806319ab453c1461019b5780632d06177a146101c1578063350aaa9a146101e7575b600080fd5b6101996004803603602081101561018957600080fd5b5035600160a060020a031661076c565b005b610199600480360360208110156101b157600080fd5b5035600160a060020a0316610899565b610199600480360360208110156101d757600080fd5b5035600160a060020a031661089c565b610199600480360360808110156101fd57600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561022857600080fd5b82018360208201111561023a57600080fd5b8035906020019184602083028401116401000000008311171561025c57600080fd5b91939092909160208101903564010000000081111561027a57600080fd5b82018360208201111561028c57600080fd5b803590602001918460018302840111640100000000831117156102ae57600080fd5b9193509150356109d9565b610199600480360360208110156102cf57600080fd5b5035600160a060020a0316610ac6565b6102e7610bdb565b60408051600160a060020a039092168252519081900360200190f35b6102e7610bea565b6102e7610bf9565b6101996004803603602081101561032957600080fd5b5035600160a060020a0316610c08565b6101996004803603602081101561034f57600080fd5b5035600160a060020a0316610d35565b6101996004803603602081101561037557600080fd5b5035600160a060020a0316610e62565b6101996004803603606081101561039b57600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156103c657600080fd5b8201836020820111156103d857600080fd5b803590602001918460208302840111640100000000831117156103fa57600080fd5b91939092909160208101903564010000000081111561041857600080fd5b82018360208201111561042a57600080fd5b8035906020019184600183028401116401000000008311171561044c57600080fd5b509092509050610f83565b6102e761106d565b6102e76004803603606081101561047557600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156104a057600080fd5b8201836020820111156104b257600080fd5b803590602001918460208302840111640100000000831117156104d457600080fd5b91935091503561107c565b6102e7600480360360808110156104f557600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561052057600080fd5b82018360208201111561053257600080fd5b8035906020019184602083028401116401000000008311171561055457600080fd5b9193509150600160a060020a0381351690602001356110c6565b610199600480360360a081101561058457600080fd5b600160a060020a0382351691908101906040810160208201356401000000008111156105af57600080fd5b8201836020820111156105c157600080fd5b803590602001918460208302840111640100000000831117156105e357600080fd5b91939092909160208101903564010000000081111561060157600080fd5b82018360208201111561061357600080fd5b8035906020019184600183028401116401000000008311171561063557600080fd5b9193509150600160a060020a038135169060200135611178565b6101996004803603608081101561066557600080fd5b600160a060020a03823516919081019060408101602082013564010000000081111561069057600080fd5b8201836020820111156106a257600080fd5b803590602001918460208302840111640100000000831117156106c457600080fd5b9193909290916020810190356401000000008111156106e257600080fd5b8201836020820111156106f457600080fd5b8035906020019184600183028401116401000000008311171561071657600080fd5b919350915035600160a060020a031661131e565b6102e76114b9565b6107586004803603602081101561074857600080fd5b5035600160a060020a03166114c8565b604080519115158252519081900360200190f35b600054600160a060020a031633146107d3576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610838576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60028054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f9bf4baeb20b6008af8dfd7fed5c50dce707a05623b022e5d61a00c7db7f90c729181900360200190a150565b50565b600054600160a060020a03163314610903576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610968576040805160008051602061234a833981519152815260206004820152601b60248201527f4d3a2041646472657373206d757374206e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b600160a060020a03811660009081526001602052604090205460ff16151561089957600160a060020a0381166000818152600160208190526040808320805460ff1916909217909155517f3b4a40cccf2058c593542587329dd385be4f0b588db5471fbd9598e56dd7093a9190a250565b3360009081526001602081905260409091205460ff16151514610a4b576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b610abe8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8a018190048102820181019092528881529250889150879081908401838280828437600092018290525092508791506114dd9050565b505050505050565b600054600160a060020a03163314610b2d576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a03811660009081526001602081905260409091205460ff16151514610b925760405160008051602061234a83398151915281526004018080602001828103825260258152602001806122d26025913960400191505060405180910390fd5b600160a060020a038116600081815260016020526040808220805460ff19169055517fe5def11e0516f317f9c37b8835aec29fc01db4d4b6d6fecaca339d3596a29bc19190a250565b600454600160a060020a031681565b600354600160a060020a031681565b600054600160a060020a031681565b600054600160a060020a03163314610c6f576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610cd4576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60048054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517f5b22021f5b1f5f8a744edb1f20f667875f22a1b29c4d9a46418ee25110c76cb89181900360200190a150565b600054600160a060020a03163314610d9c576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610e01576040805160008051602061234a833981519152815260206004820152601a60248201527f57463a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b60058054600160a060020a03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fe897b5d59fcdf32efc14ac4f270d09939e2280487d6d5e156dcb41a29cb034399181900360200190a150565b600054600160a060020a03163314610ec9576040805160008051602061234a833981519152815260206004820152600d60248201527f4d757374206265206f776e657200000000000000000000000000000000000000604482015290519081900360640190fd5b600160a060020a0381161515610f2e576040805160008051602061234a833981519152815260206004820152601860248201527f41646472657373206d757374206e6f74206265206e756c6c0000000000000000604482015290519081900360640190fd5b6000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038316908117825560405190917fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf3691a250565b3360009081526001602081905260409091205460ff16151514610ff5576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b6110668585858080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f89018190048102820181019092528781529250879150869081908401838280828437600092018290525092506115c3915050565b5050505050565b600254600160a060020a031681565b60006110bd85858580806020026020016040519081016040528093929190818152602001838360200280828437600092018290525092508791506116289050565b95945050505050565b6000600160a060020a038316151561112d576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b61116e868686808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152508892508791506116289050565b9695505050505050565b3360009081526001602081905260409091205460ff161515146111ea576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a0316151561123b5760405160008051602061234a833981519152815260040180806020018281038252602381526020018061236a6023913960400191505060405180910390fd5b600160a060020a03821615156112a0576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b6113158787878080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8b0181900481028201810190925289815292508991508890819084018382808284376000920191909152508892508791506114dd9050565b50505050505050565b3360009081526001602081905260409091205460ff16151514611390576040805160008051602061234a833981519152815260206004820152601260248201527f4d3a204d757374206265206d616e616765720000000000000000000000000000604482015290519081900360640190fd5b600554600160a060020a031615156113e15760405160008051602061234a833981519152815260040180806020018281038252602381526020018061236a6023913960400191505060405180910390fd5b600160a060020a0381161515611446576040805160008051602061234a833981519152815260206004820152601b60248201527f57463a20677561726469616e2063616e6e6f74206265206e756c6c0000000000604482015290519081900360640190fd5b610abe8686868080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525050604080516020601f8a0181900481028201810190925288815292508891508790819084018382808284376000920191909152508792506115c3915050565b600554600160a060020a031681565b60016020526000908152604090205460ff1681565b6114e8858585611744565b60006114f682878786611964565b905060606040518060200161150a9061217d565b601f1982820381018352601f9091011660408190526003548251600160a060020a039091169160209081019182918501908083835b6020831061155e5780518252601f19909201916020918201910161153f565b51815160209384036101000a6000190180199092169116179052920193845250604080518085038152938201905282519294506000935085929150840183f59050803b15156115ac573d6000fd5b6115b98189898989611ab5565b5050505050505050565b6115ce848484611744565b600354604051600091600160a060020a0316906115ea9061217d565b600160a060020a03909116815260405190819003602001906000f080158015611617573d6000803e3d6000fd5b50905080610abe8187878787611ab5565b60008061163783878787611964565b905060606040518060200161164b9061217d565b601f1982820381018352601f9091011660408190526003548251600160a060020a039091169160209081019182918501908083835b6020831061169f5780518252601f199092019160209182019101611680565b51815160209384036101000a600019018019909216911617905292019384525060408051808503815284830182528051908301207fff0000000000000000000000000000000000000000000000000000000000000082860152306c010000000000000000000000000260418601526055850197909752607580850197909752805180850390970187526095909301909252508351930192909220979650505050505050565b600160a060020a03831615156117a9576040805160008051602061234a833981519152815260206004820152601860248201527f57463a206f776e65722063616e6e6f74206265206e756c6c0000000000000000604482015290519081900360640190fd5b81516000106117f15760405160008051602061234a83398151915281526004018080602001828103825260298152602001806123216029913960400191505060405180910390fd5b6002546040517f6bb18a54000000000000000000000000000000000000000000000000000000008152602060048201818152855160248401528551600160a060020a0390941693636bb18a549387938392604490920191818601910280838360005b8381101561186b578181015183820152602001611853565b505050509050019250505060206040518083038186803b15801561188e57600080fd5b505afa1580156118a2573d6000803e3d6000fd5b505050506040513d60208110156118b857600080fd5b505115156118ff5760405160008051602061234a833981519152815260040180806020018281038252602a8152602001806122f7602a913960400191505060405180910390fd5b80518190151561195e576040805160008051602061234a833981519152815260206004820152601d60248201527f57463a20454e53206c61626c65206d75737420626520646566696e6564000000604482015290519081900360640190fd5b50505050565b6000600160a060020a03821615156119ff578484846040516020018084815260200183600160a060020a0316600160a060020a03166c01000000000000000000000000028152601401828051906020019060200280838360005b838110156119d65781810151838201526020016119be565b505050509050019350505050604051602081830303815290604052805190602001209050611aad565b848484846040516020018085815260200184600160a060020a0316600160a060020a03166c01000000000000000000000000028152601401838051906020019060200280838360005b83811015611a60578181015183820152602001611a48565b5050505090500182600160a060020a0316600160a060020a03166c010000000000000000000000000281526014019450505050506040516020818303038152906040528051906020012090505b949350505050565b60608351600101604051908082528060200260200182016040528015611ae5578160200160208202803883390190505b50905030816000815181101515611af857fe5b600160a060020a0390921660209283029091019091015260005b8451811015611b63578481815181101515611b2957fe5b906020019060200201518282600101815181101515611b4457fe5b600160a060020a03909216602092830290910190910152600101611b12565b50604080517f3c5a3cea000000000000000000000000000000000000000000000000000000008152600160a060020a038781166004830190815260248301938452845160448401528451918a1693633c5a3cea938a9387939291606401906020808601910280838360005b83811015611be6578181015183820152602001611bce565b505050509050019350505050600060405180830381600087803b158015611c0c57600080fd5b505af1158015611c20573d6000803e3d6000fd5b50505050600160a060020a03821615611cbb57600554604080517fc6845210000000000000000000000000000000000000000000000000000000008152600160a060020a03898116600483015285811660248301529151919092169163c684521091604480830192600092919082900301818387803b158015611ca257600080fd5b505af1158015611cb6573d6000803e3d6000fd5b505050505b611cc58684611d94565b604080517f1f17732d0000000000000000000000000000000000000000000000000000000081523060048201526000602482018190529151600160a060020a03891692631f17732d926044808201939182900301818387803b158015611d2a57600080fd5b505af1158015611d3e573d6000803e3d6000fd5b5050505081600160a060020a031685600160a060020a031687600160a060020a03167fca0b7dde26052d34217ef1a0cee48085a07ca32da0a918609937a307d496bbf560405160405180910390a4505050505050565b6000600460009054906101000a9004600160a060020a0316600160a060020a031663adce1c5f6040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b158015611e0057600080fd5b505afa158015611e14573d6000803e3d6000fd5b505050506040513d6020811015611e2a57600080fd5b50516004805460408051600160a060020a03928316602482018190529285166044808301919091528251808303909101815260649091018252602081810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0f5a54660000000000000000000000000000000000000000000000000000000017905282517f09d73442000000000000000000000000000000000000000000000000000000008152925195965090946000946309d734429380820193929190829003018186803b158015611efc57600080fd5b505afa158015611f10573d6000803e3d6000fd5b505050506040513d6020811015611f2657600080fd5b50516040517f8f6f0332000000000000000000000000000000000000000000000000000000008152600160a060020a0380831660048301908152600060248401819052606060448501908152875160648601528751959650928a1694638f6f03329487949293899390929091608401906020850190808383895b83811015611fb8578181015183820152602001611fa0565b50505050905090810190601f168015611fe55780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b15801561200657600080fd5b505af115801561201a573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561204357600080fd5b81019080805164010000000081111561205b57600080fd5b8201602081018481111561206e57600080fd5b815164010000000081118282018710171561208857600080fd5b505060048054604080517f1e59c529000000000000000000000000000000000000000000000000000000008152600160a060020a038d811660248301529381019182528b5160448201528b51939092169650631e59c52995508a94508b935091829160640190602086019080838360005b838110156121115781810151838201526020016120f9565b50505050905090810190601f16801561213e5780820380516001836020036101000a031916815260200191505b509350505050600060405180830381600087803b15801561215e57600080fd5b505af1158015612172573d6000803e3d6000fd5b505050505050505050565b6101478061218b8339019056fe608060405234801561001057600080fd5b506040516020806101478339810180604052602081101561003057600080fd5b505160008054600160a060020a03909216600160a060020a031990921691909117905560e6806100616000396000f3fe60806040523615801560115750600034115b156092573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a360b8565b6000543660008037600080366000845af43d6000803e80801560b3573d6000f35b3d6000fd5b00fea165627a7a72305820016c3f8808001d8a7dd22460d6a5ad8141dac31725bd1d1bd6127b0638562cbd00294d3a20546172676574206d75737420626520616e206578697374696e67206d616e6167657257463a206f6e65206f72206d6f7265206d6f64756c657320617265206e6f74207265676973746572656457463a2063616e6e6f742061737369676e2077697468206c657373207468616e2031206d6f64756c6508c379a000000000000000000000000000000000000000000000000000000000477561726469616e53746f726167652061646472657373206e6f7420646566696e6564a165627a7a7230582081e0f10e29201ba2bfb3ec3cbd31bc1eaf7fa5875720831dee50095794b64f520029", + "compiler": { + "name": "solc", + "version": "0.5.4+commit.9549d8ff.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-06T06:24:55.380Z", + "devdoc": { + "author": "Julien Niset - ", + "details": "The WalletFactory contract creates and assigns wallets to accounts.", + "methods": { + "addManager(address)": { + "details": "Adds a manager.", + "params": { + "_manager": "The address of the manager." + } + }, + "changeENSManager(address)": { + "details": "Lets the owner change the address of the ENS manager contract.", + "params": { + "_ensManager": "The address of the ENS manager contract." + } + }, + "changeGuardianStorage(address)": { + "details": "Lets the owner change the address of the GuardianStorage contract.", + "params": { + "_guardianStorage": "The address of the GuardianStorage contract." + } + }, + "changeModuleRegistry(address)": { + "details": "Lets the owner change the address of the module registry contract.", + "params": { + "_moduleRegistry": "The address of the module registry contract." + } + }, + "changeOwner(address)": { + "details": "Lets the owner transfer ownership of the contract to a new owner.", + "params": { + "_newOwner": "The new owner." + } + }, + "constructor": { + "details": "Default constructor." + }, + "createCounterfactualWallet(address,address[],string,bytes32)": { + "details": "Lets the manager create a wallet for an owner account at a specific address. The wallet is initialised with a list of modules and an ENS. The wallet is created using the CREATE2 opcode.", + "params": { + "_label": "ENS label of the new wallet, e.g. franck.", + "_modules": "The list of modules.", + "_owner": "The account address.", + "_salt": "The salt." + } + }, + "createCounterfactualWalletWithGuardian(address,address[],string,address,bytes32)": { + "details": "Lets the manager create a wallet for an owner account at a specific address. The wallet is initialised with a list of modules, a first guardian, and an ENS. The wallet is created using the CREATE2 opcode.", + "params": { + "_guardian": "The guardian address.", + "_label": "ENS label of the new wallet, e.g. franck.", + "_modules": "The list of modules.", + "_owner": "The account address.", + "_salt": "The salt." + } + }, + "createWallet(address,address[],string)": { + "details": "Lets the manager create a wallet for an owner account. The wallet is initialised with a list of modules and an ENS.. The wallet is created using the CREATE opcode.", + "params": { + "_label": "ENS label of the new wallet, e.g. franck.", + "_modules": "The list of modules.", + "_owner": "The account address." + } + }, + "createWalletWithGuardian(address,address[],string,address)": { + "details": "Lets the manager create a wallet for an owner account. The wallet is initialised with a list of modules, a first guardian, and an ENS.. The wallet is created using the CREATE opcode.", + "params": { + "_guardian": "The guardian address.", + "_label": "ENS label of the new wallet, e.g. franck.", + "_modules": "The list of modules.", + "_owner": "The account address." + } + }, + "getAddressForCounterfactualWallet(address,address[],bytes32)": { + "details": "Gets the address of a counterfactual wallet.", + "params": { + "_modules": "The list of modules.", + "_owner": "The account address.", + "_salt": "The salt." + }, + "return": "the address that the wallet will have when created using CREATE2 and the same input parameters." + }, + "getAddressForCounterfactualWalletWithGuardian(address,address[],address,bytes32)": { + "details": "Gets the address of a counterfactual wallet with a first default guardian.", + "params": { + "_guardian": "The guardian address.", + "_modules": "The list of modules.", + "_owner": "The account address.", + "_salt": "The salt." + }, + "return": "the address that the wallet will have when created using CREATE2 and the same input parameters." + }, + "init(address)": { + "details": "Inits the module for a wallet by logging an event. The method can only be called by the wallet itself.", + "params": { + "_wallet": "The wallet." + } + }, + "revokeManager(address)": { + "details": "Revokes a manager.", + "params": { + "_manager": "The address of the manager." + } + } + }, + "title": "WalletFactory" + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/build-legacy/v2.4.0/BaseWallet.json b/build-legacy/v2.4.0/BaseWallet.json new file mode 100644 index 000000000..309323908 --- /dev/null +++ b/build-legacy/v2.4.0/BaseWallet.json @@ -0,0 +1,395 @@ +{ + "contractName": "BaseWallet", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "module", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "value", + "type": "bool" + } + ], + "name": "AuthorisedModule", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "module", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "method", + "type": "bytes4" + } + ], + "name": "EnabledStaticCall", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "module", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "Invoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorised", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "name": "enabled", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "modules", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "_modules", + "type": "address[]" + } + ], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_module", + "type": "address" + }, + { + "internalType": "bool", + "name": "_value", + "type": "bool" + } + ], + "name": "authoriseModule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_module", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "_method", + "type": "bytes4" + } + ], + "name": "enableStaticCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newOwner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "invoke", + "outputs": [ + { + "internalType": "bytes", + "name": "_result", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610e5d806100206000396000f3fe6080604052600436106100b55760003560e01c80635f54892b116100695780638f6f03321161004e5780638f6f03321461038d578063d6eb1bbf14610494578063f7e80e98146104db576100bc565b80635f54892b146103445780638da5cb5b14610378576100bc565b80631f17732d1161009a5780631f17732d1461024b5780633c5a3cea146102865780635c60da1b14610313576100bc565b806313af4035146101d357806313da30b214610208576100bc565b366100bc57005b600080356001600160e01b0319168152600360205260409020546001600160a01b03168061015557336001600160a01b0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a36101d0565b6001600160a01b03811660009081526002602052604090205460ff166101ac5760405162461bcd60e51b8152600401808060200182810382526030815260200180610dd16030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e8080156101cb573d6000f35b3d6000fd5b50005b3480156101df57600080fd5b50610206600480360360208110156101f657600080fd5b50356001600160a01b0316610502565b005b34801561021457600080fd5b506102066004803603604081101561022b57600080fd5b5080356001600160a01b031690602001356001600160e01b03191661060c565b34801561025757600080fd5b506102066004803603604081101561026e57600080fd5b506001600160a01b038135169060200135151561071f565b34801561029257600080fd5b50610206600480360360408110156102a957600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156102d457600080fd5b8201836020820111156102e657600080fd5b8035906020019184602083028401116401000000008311171561030857600080fd5b5090925090506108ec565b34801561031f57600080fd5b50610328610be1565b604080516001600160a01b039092168252519081900360200190f35b34801561035057600080fd5b506103286004803603602081101561036757600080fd5b50356001600160e01b031916610bf0565b34801561038457600080fd5b50610328610c0b565b34801561039957600080fd5b5061041f600480360360608110156103b057600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156103e057600080fd5b8201836020820111156103f257600080fd5b8035906020019184600183028401116401000000008311171561041457600080fd5b509092509050610c1a565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610459578181015183820152602001610441565b50505050905090810190601f1680156104865780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156104a057600080fd5b506104c7600480360360208110156104b757600080fd5b50356001600160a01b0316610d62565b604080519115158252519081900360200190f35b3480156104e757600080fd5b506104f0610d77565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff166105505760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b0381166105ab576040805162461bcd60e51b815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b600180546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff1661065a5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b03821660009081526002602052604090205460ff166106b15760405162461bcd60e51b8152600401808060200182810382526030815260200180610dd16030913960400191505060405180910390fd5b6001600160e01b03198116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff1661076d5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b03821660009081526002602052604090205460ff161515811515146108e85760408051821515815290516001600160a01b038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a2600181151514156108805760048054600190810182556001600160a01b038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561086357600080fd5b505af1158015610877573d6000803e3d6000fd5b505050506108e8565b6004805460001901908190556108c75760405162461bcd60e51b8152600401808060200182810382526028815260200180610d7e6028913960400191505060405180910390fd5b6001600160a01b0382166000908152600260205260409020805460ff191690555b5050565b6001546001600160a01b03161580156109055750600454155b610956576040805162461bcd60e51b815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b806109925760405162461bcd60e51b815260040180806020018281038252602b815260200180610da6602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038516179055600481905560005b81811015610b9457600260008484848181106109da57fe5b602090810292909201356001600160a01b03168352508101919091526040016000205460ff1615610a52576040805162461bcd60e51b815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610a6457fe5b905060200201356001600160a01b03166001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550828282818110610ab757fe5b905060200201356001600160a01b03166001600160a01b03166319ab453c306040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b158015610b1557600080fd5b505af1158015610b29573d6000803e3d6000fd5b50505050828282818110610b3957fe5b905060200201356001600160a01b03166001600160a01b03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1600160405180821515815260200191505060405180910390a26001016109c2565b504715610bdc576040805160208082526000908201819052915147917f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef738919081900360600190a35b505050565b6000546001600160a01b031681565b6003602052600090815260409020546001600160a01b031681565b6001546001600160a01b031681565b3360009081526002602052604090205460609060ff16610c6b5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6000856001600160a01b0316858585604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610ccb576040519150601f19603f3d011682016040523d82523d6000602084013e610cd0565b606091505b509250905080610ce4573d6000803e3d6000fd5b84866001600160a01b0316336001600160a01b03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365878760405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a450949350505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6542573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a2646970667358221220fc2967831c26b409efcec8a81ffa21a8d3af4eed4828c03fc8af55ae68cae30b64736f6c634300060c0033", + "deployedBytecode": "0x6080604052600436106100b55760003560e01c80635f54892b116100695780638f6f03321161004e5780638f6f03321461038d578063d6eb1bbf14610494578063f7e80e98146104db576100bc565b80635f54892b146103445780638da5cb5b14610378576100bc565b80631f17732d1161009a5780631f17732d1461024b5780633c5a3cea146102865780635c60da1b14610313576100bc565b806313af4035146101d357806313da30b214610208576100bc565b366100bc57005b600080356001600160e01b0319168152600360205260409020546001600160a01b03168061015557336001600160a01b0316347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a36101d0565b6001600160a01b03811660009081526002602052604090205460ff166101ac5760405162461bcd60e51b8152600401808060200182810382526030815260200180610dd16030913960400191505060405180910390fd5b3660008037600080366000845afa3d6000803e8080156101cb573d6000f35b3d6000fd5b50005b3480156101df57600080fd5b50610206600480360360208110156101f657600080fd5b50356001600160a01b0316610502565b005b34801561021457600080fd5b506102066004803603604081101561022b57600080fd5b5080356001600160a01b031690602001356001600160e01b03191661060c565b34801561025757600080fd5b506102066004803603604081101561026e57600080fd5b506001600160a01b038135169060200135151561071f565b34801561029257600080fd5b50610206600480360360408110156102a957600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156102d457600080fd5b8201836020820111156102e657600080fd5b8035906020019184602083028401116401000000008311171561030857600080fd5b5090925090506108ec565b34801561031f57600080fd5b50610328610be1565b604080516001600160a01b039092168252519081900360200190f35b34801561035057600080fd5b506103286004803603602081101561036757600080fd5b50356001600160e01b031916610bf0565b34801561038457600080fd5b50610328610c0b565b34801561039957600080fd5b5061041f600480360360608110156103b057600080fd5b6001600160a01b03823516916020810135918101906060810160408201356401000000008111156103e057600080fd5b8201836020820111156103f257600080fd5b8035906020019184600183028401116401000000008311171561041457600080fd5b509092509050610c1a565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610459578181015183820152602001610441565b50505050905090810190601f1680156104865780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156104a057600080fd5b506104c7600480360360208110156104b757600080fd5b50356001600160a01b0316610d62565b604080519115158252519081900360200190f35b3480156104e757600080fd5b506104f0610d77565b60408051918252519081900360200190f35b3360009081526002602052604090205460ff166105505760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b0381166105ab576040805162461bcd60e51b815260206004820152601a60248201527f42573a20616464726573732063616e6e6f74206265206e756c6c000000000000604482015290519081900360640190fd5b600180546001600160a01b03831673ffffffffffffffffffffffffffffffffffffffff19909116811790915560408051918252517fa2ea9883a321a3e97b8266c2b078bfeec6d50c711ed71f874a90d500ae2eaf369181900360200190a150565b3360009081526002602052604090205460ff1661065a5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b03821660009081526002602052604090205460ff166106b15760405162461bcd60e51b8152600401808060200182810382526030815260200180610dd16030913960400191505060405180910390fd5b6001600160e01b03198116600081815260036020526040808220805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038716908117909155905190917fd04b9de96b5ba21173fd97c509db394c9e07a59ffb10c26da7f6ce38a8102fcb91a35050565b3360009081526002602052604090205460ff1661076d5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6001600160a01b03821660009081526002602052604090205460ff161515811515146108e85760408051821515815290516001600160a01b038416917f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1919081900360200190a2600181151514156108805760048054600190810182556001600160a01b038416600081815260026020526040808220805460ff191690941790935582517f19ab453c0000000000000000000000000000000000000000000000000000000081523094810194909452915190926319ab453c92602480830193919282900301818387803b15801561086357600080fd5b505af1158015610877573d6000803e3d6000fd5b505050506108e8565b6004805460001901908190556108c75760405162461bcd60e51b8152600401808060200182810382526028815260200180610d7e6028913960400191505060405180910390fd5b6001600160a01b0382166000908152600260205260409020805460ff191690555b5050565b6001546001600160a01b03161580156109055750600454155b610956576040805162461bcd60e51b815260206004820152601e60248201527f42573a2077616c6c657420616c726561647920696e697469616c697365640000604482015290519081900360640190fd5b806109925760405162461bcd60e51b815260040180806020018281038252602b815260200180610da6602b913960400191505060405180910390fd5b6001805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b038516179055600481905560005b81811015610b9457600260008484848181106109da57fe5b602090810292909201356001600160a01b03168352508101919091526040016000205460ff1615610a52576040805162461bcd60e51b815260206004820152601b60248201527f42573a206d6f64756c6520697320616c72656164792061646465640000000000604482015290519081900360640190fd5b600160026000858585818110610a6457fe5b905060200201356001600160a01b03166001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550828282818110610ab757fe5b905060200201356001600160a01b03166001600160a01b03166319ab453c306040518263ffffffff1660e01b815260040180826001600160a01b03168152602001915050600060405180830381600087803b158015610b1557600080fd5b505af1158015610b29573d6000803e3d6000fd5b50505050828282818110610b3957fe5b905060200201356001600160a01b03166001600160a01b03167f8da3ff870ae294081392139550e167f1f31f277f22015ee22fbffdbd7758f4e1600160405180821515815260200191505060405180910390a26001016109c2565b504715610bdc576040805160208082526000908201819052915147917f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef738919081900360600190a35b505050565b6000546001600160a01b031681565b6003602052600090815260409020546001600160a01b031681565b6001546001600160a01b031681565b3360009081526002602052604090205460609060ff16610c6b5760405162461bcd60e51b8152600401808060200182810382526027815260200180610e016027913960400191505060405180910390fd5b6000856001600160a01b0316858585604051808383808284376040519201945060009350909150508083038185875af1925050503d8060008114610ccb576040519150601f19603f3d011682016040523d82523d6000602084013e610cd0565b606091505b509250905080610ce4573d6000803e3d6000fd5b84866001600160a01b0316336001600160a01b03167f7d2476ab50663f025cff0be85655bcf355f62768615c0c478f3cd5293f807365878760405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a450949350505050565b60026020526000908152604090205460ff1681565b6004548156fe42573a2077616c6c6574206d7573742068617665206174206c65617374206f6e65206d6f64756c6542573a20636f6e737472756374696f6e207265717569726573206174206c656173742031206d6f64756c6542573a206d75737420626520616e20617574686f7269736564206d6f64756c6520666f72207374617469632063616c6c42573a206d73672e73656e646572206e6f7420616e20617574686f72697a6564206d6f64756c65a2646970667358221220fc2967831c26b409efcec8a81ffa21a8d3af4eed4828c03fc8af55ae68cae30b64736f6c634300060c0033", + "immutableReferences": {}, + "compiler": { + "name": "solc", + "version": "0.6.12+commit.27d51765.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-06T06:25:03.500Z", + "devdoc": { + "author": "Julien Niset - ", + "kind": "dev", + "methods": { + "authoriseModule(address,bool)": { + "params": { + "_module": "The target module.", + "_value": "Set to `true` to authorise the module." + } + }, + "enableStaticCall(address,bytes4)": { + "params": { + "_method": "The static method signature.", + "_module": "The target module." + } + }, + "init(address,address[])": { + "params": { + "_modules": "The modules to authorise.", + "_owner": "The owner." + } + }, + "invoke(address,uint256,bytes)": { + "params": { + "_data": "The data of the transaction.", + "_target": "The address for the transaction.", + "_value": "The value of the transaction." + } + }, + "setOwner(address)": { + "params": { + "_newOwner": "The new owner." + } + } + }, + "stateVariables": { + "authorised": { + "params": { + "_module": "The module address to check." + }, + "return": "`true` if the module is authorised, otherwise `false`." + }, + "enabled": { + "params": { + "_sig": "The signature of the static call." + }, + "return": "the module doing the redirection" + }, + "modules": { + "return": "The number of authorised modules." + }, + "owner": { + "return": "The wallet owner address." + } + }, + "title": "BaseWallet", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "authoriseModule(address,bool)": { + "notice": "Enables/Disables a module." + }, + "authorised(address)": { + "notice": "Checks if a module is authorised on the wallet." + }, + "enableStaticCall(address,bytes4)": { + "notice": "Enables a static method by specifying the target module to which the call must be delegated." + }, + "enabled(bytes4)": { + "notice": "Returns the module responsible for a static call redirection." + }, + "init(address,address[])": { + "notice": "Inits the wallet by setting the owner and authorising a list of modules." + }, + "invoke(address,uint256,bytes)": { + "notice": "Performs a generic transaction." + }, + "modules()": { + "notice": "Returns the number of authorised modules." + }, + "owner()": { + "notice": "Returns the wallet owner." + }, + "setOwner(address)": { + "notice": "Sets a new owner for the wallet." + } + }, + "notice": "Simple modular wallet that authorises modules to call its invoke() method.", + "version": 1 + } +} \ No newline at end of file diff --git a/build-legacy/v2.4.0/Proxy.json b/build-legacy/v2.4.0/Proxy.json new file mode 100644 index 000000000..77abc4b65 --- /dev/null +++ b/build-legacy/v2.4.0/Proxy.json @@ -0,0 +1,71 @@ +{ + "contractName": "Proxy", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "Received", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b506040516101403803806101408339818101604052602081101561003357600080fd5b5051600080546001600160a01b039092166001600160a01b031990921691909117905560dc806100646000396000f3fe6080604052366083573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a3005b600080543682833781823684845af490503d82833e80801560a2573d83f35b3d83fdfea2646970667358221220be24b93190906d77639a34f833222a884fa6380809e0cf98eb798a254b62846964736f6c634300060c0033", + "deployedBytecode": "0x6080604052366083573373ffffffffffffffffffffffffffffffffffffffff16347f606834f57405380c4fb88d1f4850326ad3885f014bab3b568dfbf7a041eef73860003660405180806020018281038252848482818152602001925080828437600083820152604051601f909101601f19169092018290039550909350505050a3005b600080543682833781823684845af490503d82833e80801560a2573d83f35b3d83fdfea2646970667358221220be24b93190906d77639a34f833222a884fa6380809e0cf98eb798a254b62846964736f6c634300060c0033", + "compiler": { + "name": "solc", + "version": "0.6.12+commit.27d51765.Linux.g++" + }, + "networks": {}, + "schemaVersion": "3.3.0", + "updatedAt": "2021-04-08T05:25:33.592Z", + "devdoc": { + "author": "Julien Niset - ", + "kind": "dev", + "methods": {}, + "title": "Proxy", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "notice": "Basic proxy that delegates all calls to a fixed implementing contract. The implementing contract cannot be upgraded.", + "version": 1 + } +} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/BaseModule.sol b/contracts-legacy/v1.3.0/BaseModule.sol deleted file mode 100644 index 55cdff55f..000000000 --- a/contracts-legacy/v1.3.0/BaseModule.sol +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./SafeMath.sol"; -import "./BaseWallet.sol"; -import "./ModuleRegistry.sol"; -import "./GuardianStorage.sol"; -import "./Module.sol"; - -/** - * @title BaseModule - * @dev Basic module that contains some methods common to all modules. - * @author Julien Niset - - */ -contract BaseModule is Module { - - // Empty calldata - bytes constant internal EMPTY_BYTES = ""; - - // The adddress of the module registry. - ModuleRegistry internal registry; - // The address of the Guardian storage - GuardianStorage internal guardianStorage; - - /** - * @dev Throws if the wallet is locked. - */ - modifier onlyWhenUnlocked(BaseWallet _wallet) { - verifyUnlocked(_wallet); - _; - } - - event ModuleCreated(bytes32 name); - event ModuleInitialised(address wallet); - - constructor(ModuleRegistry _registry, GuardianStorage _guardianStorage, bytes32 _name) public { - registry = _registry; - guardianStorage = _guardianStorage; - emit ModuleCreated(_name); - } - - /** - * @dev Throws if the sender is not the target wallet of the call. - */ - modifier onlyWallet(BaseWallet _wallet) { - require(msg.sender == address(_wallet), "BM: caller must be wallet"); - _; - } - - /** - * @dev Throws if the sender is not the owner of the target wallet or the module itself. - */ - modifier onlyWalletOwner(BaseWallet _wallet) { - // Wrapping in an internal method reduces deployment cost by avoiding duplication of inlined code - verifyWalletOwner(_wallet); - _; - } - - /** - * @dev Throws if the sender is not the owner of the target wallet. - */ - modifier strictOnlyWalletOwner(BaseWallet _wallet) { - require(isOwner(_wallet, msg.sender), "BM: msg.sender must be an owner for the wallet"); - _; - } - - /** - * @dev Inits the module for a wallet by logging an event. - * The method can only be called by the wallet itself. - * @param _wallet The wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - emit ModuleInitialised(address(_wallet)); - } - - /** - * @dev Adds a module to a wallet. First checks that the module is registered. - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external strictOnlyWalletOwner(_wallet) { - require(registry.isRegisteredModule(address(_module)), "BM: module is not registered"); - _wallet.authoriseModule(address(_module), true); - } - - /** - * @dev Utility method enbaling anyone to recover ERC20 token sent to the - * module by mistake and transfer them to the Module Registry. - * @param _token The token to recover. - */ - function recoverToken(address _token) external { - uint total = ERC20(_token).balanceOf(address(this)); - bool success = ERC20(_token).transfer(address(registry), total); - require(success, "BM: recover token transfer failed"); - } - - /** - * @dev Verify that the wallet is unlocked. - * @param _wallet The target wallet. - */ - function verifyUnlocked(BaseWallet _wallet) internal view { - require(!guardianStorage.isLocked(_wallet), "BM: wallet locked"); - } - - /** - * @dev Verify that the caller is the module or the wallet owner. - * @param _wallet The target wallet. - */ - function verifyWalletOwner(BaseWallet _wallet) internal view { - require(msg.sender == address(this) || isOwner(_wallet, msg.sender), "BM: must be wallet owner"); - } - - /** - * @dev Helper method to check if an address is the owner of a target wallet. - * @param _wallet The target wallet. - * @param _addr The address. - */ - function isOwner(BaseWallet _wallet, address _addr) internal view returns (bool) { - return _wallet.owner() == _addr; - } - - /** - * @dev Helper method to invoke a wallet. - * @param _wallet The target wallet. - * @param _to The target address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { - bool success; - // solium-disable-next-line security/no-call-value - (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data)); - if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values - (_res) = abi.decode(_res, (bytes)); - } else if (_res.length > 0) { - // solium-disable-next-line security/no-inline-assembly - assembly { - returndatacopy(0, 0, returndatasize) - revert(0, returndatasize) - } - } else if (!success) { - revert("BM: wallet invoke reverted"); - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/BaseWallet.sol b/contracts-legacy/v1.3.0/BaseWallet.sol deleted file mode 100644 index 0503d8ae7..000000000 --- a/contracts-legacy/v1.3.0/BaseWallet.sol +++ /dev/null @@ -1,137 +0,0 @@ -pragma solidity ^0.5.4; - -import "./Module.sol"; - -/** - * @title BaseWallet - * @dev Simple modular wallet that authorises modules to call its invoke() method. - * Based on https://gist.github.com/Arachnid/a619d31f6d32757a4328a428286da186 by - * @author Julien Niset - - */ -contract BaseWallet { - - // The implementation of the proxy - address public implementation; - // The owner of the wallet - address public owner; - // The authorised modules - mapping (address => bool) public authorised; - // The enabled static calls - mapping (bytes4 => address) public enabled; - // The number of modules - uint public modules; - - event AuthorisedModule(address indexed module, bool value); - event EnabledStaticCall(address indexed module, bytes4 indexed method); - event Invoked(address indexed module, address indexed target, uint indexed value, bytes data); - event Received(uint indexed value, address indexed sender, bytes data); - event OwnerChanged(address owner); - - /** - * @dev Throws if the sender is not an authorised module. - */ - modifier moduleOnly { - require(authorised[msg.sender], "BW: msg.sender not an authorized module"); - _; - } - - /** - * @dev Inits the wallet by setting the owner and authorising a list of modules. - * @param _owner The owner. - * @param _modules The modules to authorise. - */ - function init(address _owner, address[] calldata _modules) external { - require(owner == address(0) && modules == 0, "BW: wallet already initialised"); - require(_modules.length > 0, "BW: construction requires at least 1 module"); - owner = _owner; - modules = _modules.length; - for(uint256 i = 0; i < _modules.length; i++) { - require(authorised[_modules[i]] == false, "BW: module is already added"); - authorised[_modules[i]] = true; - Module(_modules[i]).init(this); - emit AuthorisedModule(_modules[i], true); - } - } - - /** - * @dev Enables/Disables a module. - * @param _module The target module. - * @param _value Set to true to authorise the module. - */ - function authoriseModule(address _module, bool _value) external moduleOnly { - if (authorised[_module] != _value) { - if(_value == true) { - modules += 1; - authorised[_module] = true; - Module(_module).init(this); - } - else { - modules -= 1; - require(modules > 0, "BW: wallet must have at least one module"); - delete authorised[_module]; - } - emit AuthorisedModule(_module, _value); - } - } - - /** - * @dev Enables a static method by specifying the target module to which the call - * must be delegated. - * @param _module The target module. - * @param _method The static method signature. - */ - function enableStaticCall(address _module, bytes4 _method) external moduleOnly { - require(authorised[_module], "BW: must be an authorised module for static call"); - enabled[_method] = _module; - emit EnabledStaticCall(_module, _method); - } - - /** - * @dev Sets a new owner for the wallet. - * @param _newOwner The new owner. - */ - function setOwner(address _newOwner) external moduleOnly { - require(_newOwner != address(0), "BW: address cannot be null"); - owner = _newOwner; - emit OwnerChanged(_newOwner); - } - - /** - * @dev Performs a generic transaction. - * @param _target The address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly { - // solium-disable-next-line security/no-call-value - (bool success, ) = _target.call.value(_value)(_data); - require(success, "BW: call to target failed"); - emit Invoked(msg.sender, _target, _value, _data); - } - - /** - * @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to - * implement specific static methods. It delegates the static call to a target contract if the data corresponds - * to an enabled method, or logs the call otherwise. - */ - function() external payable { - if(msg.data.length > 0) { - address module = enabled[msg.sig]; - if(module == address(0)) { - emit Received(msg.value, msg.sender, msg.data); - } - else { - require(authorised[module], "BW: must be an authorised module for static call"); - // solium-disable-next-line security/no-inline-assembly - assembly { - calldatacopy(0, 0, calldatasize()) - let result := staticcall(gas, module, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } - } - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/ERC20.sol b/contracts-legacy/v1.3.0/ERC20.sol deleted file mode 100644 index 516240990..000000000 --- a/contracts-legacy/v1.3.0/ERC20.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * ERC20 contract interface. - */ -contract ERC20 { - function totalSupply() public view returns (uint); - function decimals() public view returns (uint); - function balanceOf(address tokenOwner) public view returns (uint balance); - function allowance(address tokenOwner, address spender) public view returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/GuardianStorage.sol b/contracts-legacy/v1.3.0/GuardianStorage.sol deleted file mode 100644 index 6711dee20..000000000 --- a/contracts-legacy/v1.3.0/GuardianStorage.sol +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseWallet.sol"; -import "./Storage.sol"; -import "./IGuardianStorage.sol"; - -/** - * @title GuardianStorage - * @dev Contract storing the state of wallets related to guardians and lock. - * The contract only defines basic setters and getters with no logic. Only modules authorised - * for a wallet can modify its state. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract GuardianStorage is IGuardianStorage, Storage { - - struct GuardianStorageConfig { - // the list of guardians - address[] guardians; - // the info about guardians - mapping (address => GuardianInfo) info; - // the lock's release timestamp - uint256 lock; - // the module that set the last lock - address locker; - } - - struct GuardianInfo { - bool exists; - uint128 index; - } - - // wallet specific storage - mapping (address => GuardianStorageConfig) internal configs; - - // *************** External Functions ********************* // - - /** - * @dev Lets an authorised module add a guardian to a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(BaseWallet _wallet, address _guardian) external onlyModule(_wallet) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - config.info[_guardian].exists = true; - config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1); - } - - /** - * @dev Lets an authorised module revoke a guardian from a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(BaseWallet _wallet, address _guardian) external onlyModule(_wallet) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - address lastGuardian = config.guardians[config.guardians.length - 1]; - if (_guardian != lastGuardian) { - uint128 targetIndex = config.info[_guardian].index; - config.guardians[targetIndex] = lastGuardian; - config.info[lastGuardian].index = targetIndex; - } - config.guardians.length--; - delete config.info[_guardian]; - } - - /** - * @dev Returns the number of guardians for a wallet. - * @param _wallet The target wallet. - * @return the number of guardians. - */ - function guardianCount(BaseWallet _wallet) external view returns (uint256) { - return configs[address(_wallet)].guardians.length; - } - - /** - * @dev Gets the list of guaridans for a wallet. - * @param _wallet The target wallet. - * @return the list of guardians. - */ - function getGuardians(BaseWallet _wallet) external view returns (address[] memory) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - address[] memory guardians = new address[](config.guardians.length); - for (uint256 i = 0; i < config.guardians.length; i++) { - guardians[i] = config.guardians[i]; - } - return guardians; - } - - /** - * @dev Checks if an account is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The account. - * @return true if the account is a guardian for a wallet. - */ - function isGuardian(BaseWallet _wallet, address _guardian) external view returns (bool) { - return configs[address(_wallet)].info[_guardian].exists; - } - - /** - * @dev Lets an authorised module set the lock for a wallet. - * @param _wallet The target wallet. - * @param _releaseAfter The epoch time at which the lock should automatically release. - */ - function setLock(BaseWallet _wallet, uint256 _releaseAfter) external onlyModule(_wallet) { - configs[address(_wallet)].lock = _releaseAfter; - if (_releaseAfter != 0 && msg.sender != configs[address(_wallet)].locker) { - configs[address(_wallet)].locker = msg.sender; - } - } - - /** - * @dev Checks if the lock is set for a wallet. - * @param _wallet The target wallet. - * @return true if the lock is set for the wallet. - */ - function isLocked(BaseWallet _wallet) external view returns (bool) { - return configs[address(_wallet)].lock > now; - } - - /** - * @dev Gets the time at which the lock of a wallet will release. - * @param _wallet The target wallet. - * @return the time at which the lock of a wallet will release, or zero if there is no lock set. - */ - function getLock(BaseWallet _wallet) external view returns (uint256) { - return configs[address(_wallet)].lock; - } - - /** - * @dev Gets the address of the last module that modified the lock for a wallet. - * @param _wallet The target wallet. - * @return the address of the last module that modified the lock for a wallet. - */ - function getLocker(BaseWallet _wallet) external view returns (address) { - return configs[address(_wallet)].locker; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/GuardianUtils.sol b/contracts-legacy/v1.3.0/GuardianUtils.sol deleted file mode 100644 index 0cfc0e77f..000000000 --- a/contracts-legacy/v1.3.0/GuardianUtils.sol +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -library GuardianUtils { - - /** - * @dev Checks if an address is an account guardian or an account authorised to sign on behalf of a smart-contract guardian - * given a list of guardians. - * @param _guardians the list of guardians - * @param _guardian the address to test - * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found. - */ - function isGuardian(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) { - if (_guardians.length == 0 || _guardian == address(0)) { - return (false, _guardians); - } - bool isFound = false; - address[] memory updatedGuardians = new address[](_guardians.length - 1); - uint256 index = 0; - for (uint256 i = 0; i < _guardians.length; i++) { - if (!isFound) { - // check if _guardian is an account guardian - if (_guardian == _guardians[i]) { - isFound = true; - continue; - } - // check if _guardian is the owner of a smart contract guardian - if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) { - isFound = true; - continue; - } - } - if (index < updatedGuardians.length) { - updatedGuardians[index] = _guardians[i]; - index++; - } - } - return isFound ? (true, updatedGuardians) : (false, _guardians); - } - - /** - * @dev Checks if an address is a contract. - * @param _addr The address. - */ - function isContract(address _addr) internal view returns (bool) { - uint32 size; - // solium-disable-next-line security/no-inline-assembly - assembly { - size := extcodesize(_addr) - } - return (size > 0); - } - - /** - * @dev Checks if an address is the owner of a guardian contract. - * The method does not revert if the call to the owner() method consumes more then 5000 gas. - * @param _guardian The guardian contract - * @param _owner The owner to verify. - */ - function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) { - address owner = address(0); - bytes4 sig = bytes4(keccak256("owner()")); - // solium-disable-next-line security/no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr,sig) - let result := staticcall(5000, _guardian, ptr, 0x20, ptr, 0x20) - if eq(result, 1) { - owner := mload(ptr) - } - } - return owner == _owner; - } -} diff --git a/contracts-legacy/v1.3.0/IGuardianStorage.sol b/contracts-legacy/v1.3.0/IGuardianStorage.sol deleted file mode 100644 index 8b5974ec2..000000000 --- a/contracts-legacy/v1.3.0/IGuardianStorage.sol +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseWallet.sol"; - -interface IGuardianStorage{ - - /** - * @dev Lets an authorised module add a guardian to a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(BaseWallet _wallet, address _guardian) external; - - /** - * @dev Lets an authorised module revoke a guardian from a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(BaseWallet _wallet, address _guardian) external; - - /** - * @dev Checks if an account is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The account. - * @return true if the account is a guardian for a wallet. - */ - function isGuardian(BaseWallet _wallet, address _guardian) external view returns (bool); -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/Module.sol b/contracts-legacy/v1.3.0/Module.sol deleted file mode 100644 index 1a862d70e..000000000 --- a/contracts-legacy/v1.3.0/Module.sol +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseWallet.sol"; - -/** - * @title Module - * @dev Interface for a module. - * A module MUST implement the addModule() method to ensure that a wallet with at least one module - * can never end up in a "frozen" state. - * @author Julien Niset - - */ -interface Module { - - /** - * @dev Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. - * @param _wallet The wallet. - */ - function init(BaseWallet _wallet) external; - - /** - * @dev Adds a module to a wallet. - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external; - - /** - * @dev Utility method to recover any ERC20 token that was sent to the - * module by mistake. - * @param _token The token to recover. - */ - function recoverToken(address _token) external; -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/ModuleRegistry.sol b/contracts-legacy/v1.3.0/ModuleRegistry.sol deleted file mode 100644 index 87b9ac097..000000000 --- a/contracts-legacy/v1.3.0/ModuleRegistry.sol +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../contracts/infrastructure/base/Owned.sol"; -import "../../lib/other/ERC20.sol"; - -/** - * @title ModuleRegistry - * @dev Registry of authorised modules. - * Modules must be registered before they can be authorised on a wallet. - * @author Julien Niset - - */ -contract ModuleRegistry is Owned { - - mapping (address => Info) internal modules; - mapping (address => Info) internal upgraders; - - event ModuleRegistered(address indexed module, bytes32 name); - event ModuleDeRegistered(address module); - event UpgraderRegistered(address indexed upgrader, bytes32 name); - event UpgraderDeRegistered(address upgrader); - - struct Info { - bool exists; - bytes32 name; - } - - /** - * @dev Registers a module. - * @param _module The module. - * @param _name The unique name of the module. - */ - function registerModule(address _module, bytes32 _name) external onlyOwner { - require(!modules[_module].exists, "MR: module already exists"); - modules[_module] = Info({exists: true, name: _name}); - emit ModuleRegistered(_module, _name); - } - - /** - * @dev Deregisters a module. - * @param _module The module. - */ - function deregisterModule(address _module) external onlyOwner { - require(modules[_module].exists, "MR: module does not exist"); - delete modules[_module]; - emit ModuleDeRegistered(_module); - } - - /** - * @dev Registers an upgrader. - * @param _upgrader The upgrader. - * @param _name The unique name of the upgrader. - */ - function registerUpgrader(address _upgrader, bytes32 _name) external onlyOwner { - require(!upgraders[_upgrader].exists, "MR: upgrader already exists"); - upgraders[_upgrader] = Info({exists: true, name: _name}); - emit UpgraderRegistered(_upgrader, _name); - } - - /** - * @dev Deregisters an upgrader. - * @param _upgrader The _upgrader. - */ - function deregisterUpgrader(address _upgrader) external onlyOwner { - require(upgraders[_upgrader].exists, "MR: upgrader does not exist"); - delete upgraders[_upgrader]; - emit UpgraderDeRegistered(_upgrader); - } - - /** - * @dev Utility method enbaling the owner of the registry to claim any ERC20 token that was sent to the - * registry. - * @param _token The token to recover. - */ - function recoverToken(address _token) external onlyOwner { - uint total = ERC20(_token).balanceOf(address(this)); - ERC20(_token).transfer(msg.sender, total); - } - - /** - * @dev Gets the name of a module from its address. - * @param _module The module address. - * @return the name. - */ - function moduleInfo(address _module) external view returns (bytes32) { - return modules[_module].name; - } - - /** - * @dev Gets the name of an upgrader from its address. - * @param _upgrader The upgrader address. - * @return the name. - */ - function upgraderInfo(address _upgrader) external view returns (bytes32) { - return upgraders[_upgrader].name; - } - - /** - * @dev Checks if a module is registered. - * @param _module The module address. - * @return true if the module is registered. - */ - function isRegisteredModule(address _module) external view returns (bool) { - return modules[_module].exists; - } - - /** - * @dev Checks if a list of modules are registered. - * @param _modules The list of modules address. - * @return true if all the modules are registered. - */ - function isRegisteredModule(address[] calldata _modules) external view returns (bool) { - for (uint i = 0; i < _modules.length; i++) { - if (!modules[_modules[i]].exists) { - return false; - } - } - return true; - } - - /** - * @dev Checks if an upgrader is registered. - * @param _upgrader The upgrader address. - * @return true if the upgrader is registered. - */ - function isRegisteredUpgrader(address _upgrader) external view returns (bool) { - return upgraders[_upgrader].exists; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/OnlyOwnerModule.sol b/contracts-legacy/v1.3.0/OnlyOwnerModule.sol deleted file mode 100644 index 414c814f6..000000000 --- a/contracts-legacy/v1.3.0/OnlyOwnerModule.sol +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseModule.sol"; -import "./RelayerModule.sol"; -import "./BaseWallet.sol"; - -/** - * @title OnlyOwnerModule - * @dev Module that extends BaseModule and RelayerModule for modules where the execute() method - * must be called with one signature frm the owner. - * @author Julien Niset - - */ -contract OnlyOwnerModule is BaseModule, RelayerModule { - - // bytes4 private constant IS_ONLY_OWNER_MODULE = bytes4(keccak256("isOnlyOwnerModule()")); - - /** - * @dev Returns a constant that indicates that the module is an OnlyOwnerModule. - * @return The constant bytes4(keccak256("isOnlyOwnerModule()")) - */ - function isOnlyOwnerModule() external pure returns (bytes4) { - // return IS_ONLY_OWNER_MODULE; - return this.isOnlyOwnerModule.selector; - } - - /** - * @dev Adds a module to a wallet. First checks that the module is registered. - * Unlike its overrided parent, this method can be called via the RelayerModule's execute() - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external onlyWalletOwner(_wallet) { - require(registry.isRegisteredModule(address(_module)), "BM: module is not registered"); - _wallet.authoriseModule(address(_module), true); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides to use the incremental nonce and save some gas - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 /* _signHash */) internal returns (bool) { - return checkAndUpdateNonce(_wallet, _nonce); - } - - function getRequiredSignatures(BaseWallet /* _wallet */, bytes memory /* _data */) public view returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/RelayerModule.sol b/contracts-legacy/v1.3.0/RelayerModule.sol deleted file mode 100644 index f6769b023..000000000 --- a/contracts-legacy/v1.3.0/RelayerModule.sol +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseWallet.sol"; -import "./BaseModule.sol"; -import "./GuardianUtils.sol"; - -/** - * @title RelayerModule - * @dev Base module containing logic to execute transactions signed by eth-less accounts and sent by a relayer. - * It is subclassed by all modules. - * @author Julien Niset , Olivier VDB - */ -contract RelayerModule is BaseModule { - - uint256 constant internal BLOCKBOUND = 10000; - - mapping (address => RelayerConfig) public relayer; - - struct RelayerConfig { - uint256 nonce; - mapping (bytes32 => bool) executedTx; - } - - enum OwnerSignature { - Required, - Optional, - Disallowed - } - - event TransactionExecuted(address indexed wallet, bool indexed success, bytes returnData, bytes32 signedHash); - - /** - * @dev Throws if the call did not go through the execute() method. - */ - modifier onlyExecute { - require(msg.sender == address(this), "RM: must be called via execute()"); - _; - } - - /* ***************** Abstract methods ************************* */ - - /** - * @dev Gets the number of valid signatures that must be provided to execute a - * specific relayed transaction. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @return The number of required signatures and the wallet owner signature requirement. - */ - function getRequiredSignatures(BaseWallet _wallet, bytes memory _data) public view returns (uint256, OwnerSignature); - - /* ***************** External methods ************************* */ - - /** - * @dev Executes a relayed transaction. - * @param _wallet The target wallet. - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _signatures The signatures as a concatenated byte array. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function execute( - BaseWallet _wallet, - bytes calldata _data, - uint256 _nonce, - bytes calldata _signatures, - uint256 _gasPrice, - uint256 _gasLimit - ) - external - returns (bool success) - { - uint startGas = gasleft(); - bytes32 signHash = getSignHash(address(this), address(_wallet), 0, _data, _nonce, _gasPrice, _gasLimit); - require(checkAndUpdateUniqueness(_wallet, _nonce, signHash), "RM: Duplicate request"); - require(verifyData(address(_wallet), _data), "RM: Target of _data != _wallet"); - (uint256 requiredSignatures, OwnerSignature ownerSignatureRequirement) = getRequiredSignatures(_wallet, _data); - require(requiredSignatures * 65 == _signatures.length, "RM: Wrong number of signatures"); - require(requiredSignatures == 0 || validateSignatures(_wallet, signHash, _signatures, ownerSignatureRequirement), - "RM: Invalid signatures"); - // The correctness of the refund is checked on the next line using an `if` instead of a `require` - // in order to prevent a failing refund from being replayable in the future. - bytes memory returnData; - if (verifyRefund(_wallet, _gasLimit, _gasPrice, requiredSignatures)) { - // solium-disable-next-line security/no-call-value - (success, returnData) = address(this).call(_data); - refund(_wallet, startGas - gasleft(), _gasPrice, _gasLimit, requiredSignatures, msg.sender); - } else { - returnData = bytes("RM: refund failed"); - } - - emit TransactionExecuted(address(_wallet), success, returnData, signHash); - } - - /** - * @dev Gets the current nonce for a wallet. - * @param _wallet The target wallet. - */ - function getNonce(BaseWallet _wallet) external view returns (uint256 nonce) { - return relayer[address(_wallet)].nonce; - } - - /* ***************** Internal & Private methods ************************* */ - - /** - * @dev Generates the signed hash of a relayed transaction according to ERC 1077. - * @param _from The starting address for the relayed transaction (should be the module) - * @param _to The destination address for the relayed transaction (should be the wallet) - * @param _value The value for the relayed transaction - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function getSignHash( - address _from, - address _to, - uint256 _value, - bytes memory _data, - uint256 _nonce, - uint256 _gasPrice, - uint256 _gasLimit - ) - internal - pure - returns (bytes32) - { - return keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256(abi.encodePacked(byte(0x19), byte(0), _from, _to, _value, _data, _nonce, _gasPrice, _gasLimit)) - )); - } - - /** - * @dev Checks if the relayed transaction is unique. - * @param _wallet The target wallet. - * @param _nonce The nonce - * @param _signHash The signed hash of the transaction - */ - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 _signHash) internal returns (bool) { - if (relayer[address(_wallet)].executedTx[_signHash] == true) { - return false; - } - relayer[address(_wallet)].executedTx[_signHash] = true; - return true; - } - - /** - * @dev Checks that a nonce has the correct format and is valid. - * It must be constructed as nonce = {block number}{timestamp} where each component is 16 bytes. - * @param _wallet The target wallet. - * @param _nonce The nonce - */ - function checkAndUpdateNonce(BaseWallet _wallet, uint256 _nonce) internal returns (bool) { - if (_nonce <= relayer[address(_wallet)].nonce) { - return false; - } - uint256 nonceBlock = (_nonce & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128; - if (nonceBlock > block.number + BLOCKBOUND) { - return false; - } - relayer[address(_wallet)].nonce = _nonce; - return true; - } - - /** - * @dev Validates the signatures provided with a relayed transaction. - * The method MUST throw if one or more signatures are not valid. - * @param _wallet The target wallet. - * @param _signHash The signed hash representing the relayed transaction. - * @param _signatures The signatures as a concatenated byte array. - * @param _option An enum indicating whether the owner is required, optional or disallowed. - * @return A boolean indicating whether the signatures are valid. - */ - function validateSignatures( - BaseWallet _wallet, - bytes32 _signHash, - bytes memory _signatures, - OwnerSignature _option - ) - internal view returns (bool) - { - address lastSigner = address(0); - address[] memory guardians; - if (_option != OwnerSignature.Required || _signatures.length > 65) { - guardians = guardianStorage.getGuardians(_wallet); // guardians are only read if they may be needed - } - bool isGuardian; - - for (uint8 i = 0; i < _signatures.length / 65; i++) { - address signer = recoverSigner(_signHash, _signatures, i); - - if (i == 0) { - if (_option == OwnerSignature.Required) { - // First signer must be owner - if (isOwner(_wallet, signer)) { - continue; - } - return false; - } else if (_option == OwnerSignature.Optional) { - // First signer can be owner - if (isOwner(_wallet, signer)) { - continue; - } - } - } - if (signer <= lastSigner) { - return false; // Signers must be different - } - lastSigner = signer; - (isGuardian, guardians) = GuardianUtils.isGuardian(guardians, signer); - if (!isGuardian) { - return false; - } - } - return true; - } - - /** - * @dev Recovers the signer at a given position from a list of concatenated signatures. - * @param _signedHash The signed hash - * @param _signatures The concatenated signatures. - * @param _index The index of the signature to recover. - */ - function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { - uint8 v; - bytes32 r; - bytes32 s; - // we jump 32 (0x20) as the first slot of bytes contains the length - // we jump 65 (0x41) per signature - // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask - // solium-disable-next-line security/no-inline-assembly - assembly { - r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) - s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) - v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) - } - require(v == 27 || v == 28); // solium-disable-line error-reason - return ecrecover(_signedHash, v, r, s); - } - - /** - * @dev Refunds the gas used to the Relayer. - * For security reasons the default behavior is to not refund calls with 0 or 1 signatures. - * @param _wallet The target wallet. - * @param _gasUsed The gas used. - * @param _gasPrice The gas price for the refund. - * @param _gasLimit The gas limit for the refund. - * @param _signatures The number of signatures used in the call. - * @param _relayer The address of the Relayer. - */ - function refund( - BaseWallet _wallet, - uint _gasUsed, - uint _gasPrice, - uint _gasLimit, - uint _signatures, - address _relayer - ) - internal - { - uint256 amount = 29292 + _gasUsed; // 21000 (transaction) + 7620 (execution of refund) + 672 to log the event + _gasUsed - // only refund if gas price not null, more than 1 signatures, gas less than gasLimit - if (_gasPrice > 0 && _signatures > 1 && amount <= _gasLimit) { - if (_gasPrice > tx.gasprice) { - amount = amount * tx.gasprice; - } else { - amount = amount * _gasPrice; - } - invokeWallet(address(_wallet), _relayer, amount, EMPTY_BYTES); - } - } - - /** - * @dev Returns false if the refund is expected to fail. - * @param _wallet The target wallet. - * @param _gasUsed The expected gas used. - * @param _gasPrice The expected gas price for the refund. - */ - function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) { - if (_gasPrice > 0 && - _signatures > 1 && - (address(_wallet).balance < _gasUsed * _gasPrice || _wallet.authorised(address(this)) == false)) { - return false; - } - return true; - } - - /** - * @dev Parses the data to extract the method signature. - */ - function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { - require(_data.length >= 4, "RM: Invalid functionPrefix"); - // solium-disable-next-line security/no-inline-assembly - assembly { - prefix := mload(add(_data, 0x20)) - } - } - - /** - * @dev Checks that the wallet address provided as the first parameter of the relayed data is the same - * as the wallet passed as the input of the execute() method. - @return false if the addresses are different. - */ - function verifyData(address _wallet, bytes memory _data) private pure returns (bool) { - require(_data.length >= 36, "RM: Invalid dataWallet"); - address dataWallet; - // solium-disable-next-line security/no-inline-assembly - assembly { - //_data = {length:32}{sig:4}{_wallet:32}{...} - dataWallet := mload(add(_data, 0x24)) - } - return dataWallet == _wallet; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.3.0/SafeMath.sol b/contracts-legacy/v1.3.0/SafeMath.sol deleted file mode 100644 index 932733e91..000000000 --- a/contracts-legacy/v1.3.0/SafeMath.sol +++ /dev/null @@ -1,107 +0,0 @@ -pragma solidity ^0.5.0; - -/** - * @dev Wrappers over Solidity's arithmetic operations with added overflow - * checks. - * - * Arithmetic operations in Solidity wrap on overflow. This can easily result - * in bugs, because programmers usually assume that an overflow raises an - * error, which is the standard behavior in high level programming languages. - * `SafeMath` restores this intuition by reverting the transaction when an - * operation overflows. - * - * Using this library instead of the unchecked operations eliminates an entire - * class of bugs, so it's recommended to use it always. - */ -library SafeMath { - /** - * @dev Returns the addition of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `+` operator. - * - * Requirements: - * - Addition cannot overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a, "SafeMath: addition overflow"); - - return c; - } - - /** - * @dev Returns the subtraction of two unsigned integers, reverting on - * overflow (when the result is negative). - * - * Counterpart to Solidity's `-` operator. - * - * Requirements: - * - Subtraction cannot overflow. - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - require(b <= a, "SafeMath: subtraction overflow"); - uint256 c = a - b; - - return c; - } - - /** - * @dev Returns the multiplication of two unsigned integers, reverting on - * overflow. - * - * Counterpart to Solidity's `*` operator. - * - * Requirements: - * - Multiplication cannot overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 - if (a == 0) { - return 0; - } - - uint256 c = a * b; - require(c / a == b, "SafeMath: multiplication overflow"); - - return c; - } - - /** - * @dev Returns the integer division of two unsigned integers. Reverts on - * division by zero. The result is rounded towards zero. - * - * Counterpart to Solidity's `/` operator. Note: this function uses a - * `revert` opcode (which leaves remaining gas untouched) while Solidity - * uses an invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - The divisor cannot be zero. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - // Solidity only automatically asserts when dividing by 0 - require(b > 0, "SafeMath: division by zero"); - uint256 c = a / b; - // assert(a == b * c + a % b); // There is no case in which this doesn't hold - - return c; - } - - /** - * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), - * Reverts when dividing by zero. - * - * Counterpart to Solidity's `%` operator. This function uses a `revert` - * opcode (which leaves remaining gas untouched) while Solidity uses an - * invalid opcode to revert (consuming all remaining gas). - * - * Requirements: - * - The divisor cannot be zero. - */ - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - require(b != 0, "SafeMath: modulo by zero"); - return a % b; - } -} diff --git a/contracts-legacy/v1.3.0/Storage.sol b/contracts-legacy/v1.3.0/Storage.sol deleted file mode 100644 index e7e709715..000000000 --- a/contracts-legacy/v1.3.0/Storage.sol +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseWallet.sol"; - -/** - * @title Storage - * @dev Base contract for the storage of a wallet. - * @author Julien Niset - - */ -contract Storage { - - /** - * @dev Throws if the caller is not an authorised module. - */ - modifier onlyModule(BaseWallet _wallet) { - require(_wallet.authorised(msg.sender), "TS: must be an authorized module to call this method"); - _; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/base/Managed.sol b/contracts-legacy/v1.6.0/contracts/base/Managed.sol deleted file mode 100644 index 0d7fc6844..000000000 --- a/contracts-legacy/v1.6.0/contracts/base/Managed.sol +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./Owned.sol"; - -/** - * @title Managed - * @dev Basic contract that defines a set of managers. Only the owner can add/remove managers. - * @author Julien Niset - - */ -contract Managed is Owned { - - // The managers - mapping (address => bool) public managers; - - /** - * @dev Throws if the sender is not a manager. - */ - modifier onlyManager { - require(managers[msg.sender] == true, "M: Must be manager"); - _; - } - - event ManagerAdded(address indexed _manager); - event ManagerRevoked(address indexed _manager); - - /** - * @dev Adds a manager. - * @param _manager The address of the manager. - */ - function addManager(address _manager) external onlyOwner { - require(_manager != address(0), "M: Address must not be null"); - if (managers[_manager] == false) { - managers[_manager] = true; - emit ManagerAdded(_manager); - } - } - - /** - * @dev Revokes a manager. - * @param _manager The address of the manager. - */ - function revokeManager(address _manager) external onlyOwner { - require(managers[_manager] == true, "M: Target must be an existing manager"); - delete managers[_manager]; - emit ManagerRevoked(_manager); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/base/Owned.sol b/contracts-legacy/v1.6.0/contracts/base/Owned.sol deleted file mode 100644 index d7f0dd658..000000000 --- a/contracts-legacy/v1.6.0/contracts/base/Owned.sol +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @title Owned - * @dev Basic contract to define an owner. - * @author Julien Niset - - */ -contract Owned { - - // The owner - address public owner; - - event OwnerChanged(address indexed _newOwner); - - /** - * @dev Throws if the sender is not the owner. - */ - modifier onlyOwner { - require(msg.sender == owner, "Must be owner"); - _; - } - - constructor() public { - owner = msg.sender; - } - - /** - * @dev Lets the owner transfer ownership of the contract to a new owner. - * @param _newOwner The new owner. - */ - function changeOwner(address _newOwner) external onlyOwner { - require(_newOwner != address(0), "Address must not be null"); - owner = _newOwner; - emit OwnerChanged(_newOwner); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/defi/Invest.sol b/contracts-legacy/v1.6.0/contracts/defi/Invest.sol deleted file mode 100644 index 8785a5b04..000000000 --- a/contracts-legacy/v1.6.0/contracts/defi/Invest.sol +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; - -/** - * @title Interface for a contract that can invest tokens in order to earn an interest. - * @author Julien Niset - - */ -interface Invest { - - event InvestmentAdded(address indexed _wallet, address _token, uint256 _invested, uint256 _period); - event InvestmentRemoved(address indexed _wallet, address _token, uint256 _fraction); - - /** - * @dev Invest tokens for a given period. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _amount The amount of tokens to invest. - * @param _period The period over which the tokens may be locked in the investment (optional). - * @return The exact amount of tokens that have been invested. - */ - function addInvestment( - BaseWallet _wallet, - address _token, - uint256 _amount, - uint256 _period - ) - external - returns (uint256 _invested); - - /** - * @dev Exit invested postions. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _fraction The fraction of invested tokens to exit in per 10000. - */ - function removeInvestment( - BaseWallet _wallet, - address _token, - uint256 _fraction - ) - external; - - /** - * @dev Get the amount of investment in a given token. - * @param _wallet The target wallet. - * @param _token The token address. - * @return The value in tokens of the investment (including interests) and the time at which the investment can be removed. - */ - function getInvestment( - BaseWallet _wallet, - address _token - ) - external - view - returns (uint256 _tokenValue, uint256 _periodEnd); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/defi/Loan.sol b/contracts-legacy/v1.6.0/contracts/defi/Loan.sol deleted file mode 100644 index ff9111be8..000000000 --- a/contracts-legacy/v1.6.0/contracts/defi/Loan.sol +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; - -/** - * @title Interface for a contract that can loan tokens to a wallet. - * @author Julien Niset - - */ -interface Loan { - - event LoanOpened( - address indexed _wallet, - bytes32 indexed _loanId, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount); - event LoanClosed(address indexed _wallet, bytes32 indexed _loanId); - event CollateralAdded(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event CollateralRemoved(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event DebtAdded(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - event DebtRemoved(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - - /** - * @dev Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of tokens borrowed. - * @return (optional) An ID for the loan when the provider enables users to create multiple distinct loans. - */ - function openLoan( - BaseWallet _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - returns (bytes32 _loanId); - - /** - * @dev Closes a collateralized loan by repaying all debts (plus interest) and redeeming all collateral (plus interest). - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - */ - function closeLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external; - - /** - * @dev Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external; - - /** - * @dev Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external; - - /** - * @dev Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external; - - /** - * @dev Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - * @param _debtToken The token to repay. - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external; - - /** - * @dev Gets information about a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the loan if any, 0 otherwise. - * @return a status [0: no loan, 1: loan is safe, 2: loan is unsafe and can be liquidated, 3: unable to provide info] - * and a value (in ETH) representing the value that could still be borrowed when status = 1; or the value of the collateral - * that should be added to avoid liquidation when status = 2. - */ - function getLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external - view - returns (uint8 _status, uint256 _ethValue); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/CompoundRegistry.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/CompoundRegistry.sol deleted file mode 100644 index 7f6f5b6ba..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/CompoundRegistry.sol +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../base/Owned.sol"; - -/** - * @title CompoundRegistry - * @dev Simple registry containing a mapping between underlying assets and their corresponding cToken. - * @author Julien Niset - - */ -contract CompoundRegistry is Owned { - - address[] tokens; - - mapping (address => CTokenInfo) internal cToken; - - struct CTokenInfo { - bool exists; - uint128 index; - address market; - } - - event CTokenAdded(address indexed _underlying, address indexed _cToken); - event CTokenRemoved(address indexed _underlying); - - /** - * @dev Adds a new cToken to the registry. - * @param _underlying The underlying asset. - * @param _cToken The cToken. - */ - function addCToken(address _underlying, address _cToken) external onlyOwner { - require(!cToken[_underlying].exists, "CR: cToken already added"); - cToken[_underlying].exists = true; - cToken[_underlying].index = uint128(tokens.push(_underlying) - 1); - cToken[_underlying].market = _cToken; - emit CTokenAdded(_underlying, _cToken); - } - - /** - * @dev Removes a cToken from the registry. - * @param _underlying The underlying asset. - */ - function removeCToken(address _underlying) external onlyOwner { - require(cToken[_underlying].exists, "CR: cToken does not exist"); - address last = tokens[tokens.length - 1]; - if (_underlying != last) { - uint128 targetIndex = cToken[_underlying].index; - tokens[targetIndex] = last; - cToken[last].index = targetIndex; - } - tokens.length --; - delete cToken[_underlying]; - emit CTokenRemoved(_underlying); - } - - /** - * @dev Gets the cToken for a given underlying asset. - * @param _underlying The underlying asset. - */ - function getCToken(address _underlying) external view returns (address) { - return cToken[_underlying].market; - } - - /** - * @dev Gets the list of supported underlyings. - */ - function listUnderlyings() external view returns (address[] memory) { - address[] memory underlyings = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { - underlyings[i] = tokens[i]; - } - return underlyings; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/MakerRegistry.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/MakerRegistry.sol deleted file mode 100644 index 0913abb24..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/MakerRegistry.sol +++ /dev/null @@ -1,93 +0,0 @@ -pragma solidity ^0.5.4; -import "../base/Owned.sol"; -import "../../lib/maker/MakerInterfaces.sol"; - -/** - * @title MakerRegistry - * @dev Simple registry containing a mapping between token collaterals and their corresponding Maker Join adapters. - * @author Olivier VDB - - */ -contract MakerRegistry is Owned { - - VatLike public vat; - address[] public tokens; - mapping (address => Collateral) public collaterals; - mapping (bytes32 => address) public collateralTokensByIlks; - - struct Collateral { - bool exists; - uint128 index; - JoinLike join; - bytes32 ilk; - } - - event CollateralAdded(address indexed _token); - event CollateralRemoved(address indexed _token); - - constructor(VatLike _vat) public { - vat = _vat; - } - - /** - * @dev Adds a new token as possible CDP collateral. - * @param _joinAdapter The Join Adapter for the token. - */ - function addCollateral(JoinLike _joinAdapter) external onlyOwner { - require(vat.wards(address(_joinAdapter)) == 1, "MR: _joinAdapter not authorised in vat"); - address token = address(_joinAdapter.gem()); - require(!collaterals[token].exists, "MR: collateral already added"); - collaterals[token].exists = true; - collaterals[token].index = uint128(tokens.push(token) - 1); - collaterals[token].join = _joinAdapter; - bytes32 ilk = _joinAdapter.ilk(); - collaterals[token].ilk = ilk; - collateralTokensByIlks[ilk] = token; - emit CollateralAdded(token); - } - - /** - * @dev Removes a token as possible CDP collateral. - * @param _token The token to remove as collateral. - */ - function removeCollateral(address _token) external onlyOwner { - require(collaterals[_token].exists, "MR: collateral does not exist"); - delete collateralTokensByIlks[collaterals[_token].ilk]; - - address last = tokens[tokens.length - 1]; - if (_token != last) { - uint128 targetIndex = collaterals[_token].index; - tokens[targetIndex] = last; - collaterals[last].index = targetIndex; - } - tokens.length --; - delete collaterals[_token]; - emit CollateralRemoved(_token); - } - - /** - * @dev Gets the list of supported collaterals. - */ - function getCollateralTokens() external view returns (address[] memory _tokens) { - _tokens = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { - _tokens[i] = tokens[i]; - } - return _tokens; - } - - /** - * @dev Gets the ilk for a given token collateral. - * @param _token The token collateral. - */ - function getIlk(address _token) external view returns (bytes32 _ilk) { - _ilk = collaterals[_token].ilk; - } - - /** - * @dev Gets the join adapter and collateral token for a given ilk. - */ - function getCollateral(bytes32 _ilk) external view returns (JoinLike _join, GemLike _token) { - _token = GemLike(collateralTokensByIlks[_ilk]); - _join = collaterals[address(_token)].join; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ModuleRegistry.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ModuleRegistry.sol deleted file mode 100644 index 5801bee69..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ModuleRegistry.sol +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../base/Owned.sol"; -import "../../lib/other/ERC20.sol"; - -/** - * @title ModuleRegistry - * @dev Registry of authorised modules. - * Modules must be registered before they can be authorised on a wallet. - * @author Julien Niset - - */ -contract ModuleRegistry is Owned { - - mapping (address => Info) internal modules; - mapping (address => Info) internal upgraders; - - event ModuleRegistered(address indexed module, bytes32 name); - event ModuleDeRegistered(address module); - event UpgraderRegistered(address indexed upgrader, bytes32 name); - event UpgraderDeRegistered(address upgrader); - - struct Info { - bool exists; - bytes32 name; - } - - /** - * @dev Registers a module. - * @param _module The module. - * @param _name The unique name of the module. - */ - function registerModule(address _module, bytes32 _name) external onlyOwner { - require(!modules[_module].exists, "MR: module already exists"); - modules[_module] = Info({exists: true, name: _name}); - emit ModuleRegistered(_module, _name); - } - - /** - * @dev Deregisters a module. - * @param _module The module. - */ - function deregisterModule(address _module) external onlyOwner { - require(modules[_module].exists, "MR: module does not exist"); - delete modules[_module]; - emit ModuleDeRegistered(_module); - } - - /** - * @dev Registers an upgrader. - * @param _upgrader The upgrader. - * @param _name The unique name of the upgrader. - */ - function registerUpgrader(address _upgrader, bytes32 _name) external onlyOwner { - require(!upgraders[_upgrader].exists, "MR: upgrader already exists"); - upgraders[_upgrader] = Info({exists: true, name: _name}); - emit UpgraderRegistered(_upgrader, _name); - } - - /** - * @dev Deregisters an upgrader. - * @param _upgrader The _upgrader. - */ - function deregisterUpgrader(address _upgrader) external onlyOwner { - require(upgraders[_upgrader].exists, "MR: upgrader does not exist"); - delete upgraders[_upgrader]; - emit UpgraderDeRegistered(_upgrader); - } - - /** - * @dev Utility method enbaling the owner of the registry to claim any ERC20 token that was sent to the - * registry. - * @param _token The token to recover. - */ - function recoverToken(address _token) external onlyOwner { - uint total = ERC20(_token).balanceOf(address(this)); - ERC20(_token).transfer(msg.sender, total); - } - - /** - * @dev Gets the name of a module from its address. - * @param _module The module address. - * @return the name. - */ - function moduleInfo(address _module) external view returns (bytes32) { - return modules[_module].name; - } - - /** - * @dev Gets the name of an upgrader from its address. - * @param _upgrader The upgrader address. - * @return the name. - */ - function upgraderInfo(address _upgrader) external view returns (bytes32) { - return upgraders[_upgrader].name; - } - - /** - * @dev Checks if a module is registered. - * @param _module The module address. - * @return true if the module is registered. - */ - function isRegisteredModule(address _module) external view returns (bool) { - return modules[_module].exists; - } - - /** - * @dev Checks if a list of modules are registered. - * @param _modules The list of modules address. - * @return true if all the modules are registered. - */ - function isRegisteredModule(address[] calldata _modules) external view returns (bool) { - for (uint i = 0; i < _modules.length; i++) { - if (!modules[_modules[i]].exists) { - return false; - } - } - return true; - } - - /** - * @dev Checks if an upgrader is registered. - * @param _upgrader The upgrader address. - * @return true if the upgrader is registered. - */ - function isRegisteredUpgrader(address _upgrader) external view returns (bool) { - return upgraders[_upgrader].exists; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/MultiSigWallet.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/MultiSigWallet.sol deleted file mode 100644 index 0124d3bb1..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/MultiSigWallet.sol +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @title MultiSig - * @dev Simple MultiSig using off-chain signing. - * @author Julien Niset - - */ -contract MultiSigWallet { - - uint constant public MAX_OWNER_COUNT = 10; - - // Incrementing counter to prevent replay attacks - uint256 public nonce; - // The threshold - uint256 public threshold; - // The number of owners - uint256 public ownersCount; - // Mapping to check if an address is an owner - mapping (address => bool) public isOwner; - - // Events - event OwnerAdded(address indexed owner); - event OwnerRemoved(address indexed owner); - event ThresholdChanged(uint256 indexed newThreshold); - event Executed(address indexed destination, uint256 indexed value, bytes data); - event Received(uint256 indexed value, address indexed from); - - /** - * @dev Throws if the calling account is not the multisig. - * @dev Mainly used for enforcing the use of internal functions through the "execute" function - */ - modifier onlyWallet() { - require(msg.sender == address(this), "MSW: Calling account is not wallet"); - _; - } - - /** - * @dev Constructor. - * @param _threshold The threshold of the multisig. - * @param _owners The initial set of owners of the multisig. - */ - constructor(uint256 _threshold, address[] memory _owners) public { - require(_owners.length > 0 && _owners.length <= MAX_OWNER_COUNT, "MSW: Not enough or too many owners"); - require(_threshold > 0 && _threshold <= _owners.length, "MSW: Invalid threshold"); - ownersCount = _owners.length; - threshold = _threshold; - for (uint256 i = 0; i < _owners.length; i++) { - isOwner[_owners[i]] = true; - emit OwnerAdded(_owners[i]); - } - emit ThresholdChanged(_threshold); - } - - /** - * @dev Only entry point of the multisig. The method will execute any transaction provided that it - * receieved enough signatures from the wallet owners. - * @param _to The destination address for the transaction to execute. - * @param _value The value parameter for the transaction to execute. - * @param _data The data parameter for the transaction to execute. - * @param _signatures Concatenated signatures ordered based on increasing signer's address. - */ - function execute(address _to, uint _value, bytes memory _data, bytes memory _signatures) public { - uint8 v; - bytes32 r; - bytes32 s; - uint256 count = _signatures.length / 65; - require(count >= threshold, "MSW: Not enough signatures"); - bytes32 txHash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), _to, _value, _data, nonce)); - nonce += 1; - uint256 valid = 0; - address lastSigner = address(0); - for (uint256 i = 0; i < count; i++) { - (v,r,s) = splitSignature(_signatures, i); - address recovered = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32",txHash)), v, r, s); - require(recovered > lastSigner, "MSW: Badly ordered signatures"); // make sure signers are different - lastSigner = recovered; - if (isOwner[recovered]) { - valid += 1; - if (valid >= threshold) { - // solium-disable-next-line security/no-call-value - (bool success,) = _to.call.value(_value)(_data); - require(success, "MSW: External call failed"); - emit Executed(_to, _value, _data); - return; - } - } - } - // If not enough signatures for threshold, then the transaction is not executed - revert("MSW: Not enough valid signatures"); - } - - /** - * @dev Adds an owner to the multisig. This method can only be called by the multisig itself - * (i.e. it must go through the execute method and be confirmed by the owners). - * @param _owner The address of the new owner. - */ - function addOwner(address _owner) public onlyWallet { - require(ownersCount < MAX_OWNER_COUNT, "MSW: MAX_OWNER_COUNT reached"); - require(isOwner[_owner] == false, "MSW: Already owner"); - ownersCount += 1; - isOwner[_owner] = true; - emit OwnerAdded(_owner); - } - - /** - * @dev Removes an owner from the multisig. This method can only be called by the multisig itself - * (i.e. it must go through the execute method and be confirmed by the owners). - * @param _owner The address of the owner to be removed. - */ - function removeOwner(address _owner) public onlyWallet { - require(ownersCount > threshold, "MSW: Too few owners left"); - require(isOwner[_owner] == true, "MSW: Not an owner"); - ownersCount -= 1; - delete isOwner[_owner]; - emit OwnerRemoved(_owner); - } - - /** - * @dev Changes the threshold of the multisig. This method can only be called by the multisig itself - * (i.e. it must go through the execute method and be confirmed by the owners). - * @param _newThreshold The new threshold. - */ - function changeThreshold(uint256 _newThreshold) public onlyWallet { - require(_newThreshold > 0 && _newThreshold <= ownersCount, "MSW: Invalid new threshold"); - threshold = _newThreshold; - emit ThresholdChanged(_newThreshold); - } - - /** - * @dev Parses the signatures and extract (r, s, v) for a signature at a given index. - * A signature is {bytes32 r}{bytes32 s}{uint8 v} in compact form where the signatures are concatenated. - * @param _signatures concatenated signatures - * @param _index which signature to read (0, 1, 2, ...) - */ - function splitSignature(bytes memory _signatures, uint256 _index) internal pure returns (uint8 v, bytes32 r, bytes32 s) { - // we jump 32 (0x20) as the first slot of bytes contains the length - // we jump 65 (0x41) per signature - // for v we load 32 bytes ending with v (the first 31 come from s) tehn apply a mask - // solium-disable-next-line security/no-inline-assembly - assembly { - r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) - s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) - v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) - } - require(v == 27 || v == 28, "MSW: Invalid v"); - } - - /** - * @dev Fallback function to allow the multisig to receive ETH, which will fail if not implemented - */ - function () external payable { - emit Received(msg.value, msg.sender); - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/TokenPriceProvider.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/TokenPriceProvider.sol deleted file mode 100644 index 94e457c8b..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/TokenPriceProvider.sol +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../lib/utils/SafeMath.sol"; -import "../../lib/other/ERC20.sol"; -import "../base/Managed.sol"; -import "../../lib/other/KyberNetwork.sol"; - -contract TokenPriceProvider is Managed { - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - using SafeMath for uint256; - - mapping(address => uint256) public cachedPrices; - - // Address of the KyberNetwork contract - KyberNetwork public kyberNetwork; - - constructor(KyberNetwork _kyberNetwork) public { - kyberNetwork = _kyberNetwork; - } - - function setPrice(ERC20 _token, uint256 _price) public onlyManager { - cachedPrices[address(_token)] = _price; - } - - function setPriceForTokenList(ERC20[] calldata _tokens, uint256[] calldata _prices) external onlyManager { - for (uint16 i = 0; i < _tokens.length; i++) { - setPrice(_tokens[i], _prices[i]); - } - } - - /** - * @dev Converts the value of _amount tokens in ether. - * @param _amount the amount of tokens to convert (in 'token wei' twei) - * @param _token the ERC20 token contract - * @return the ether value (in wei) of _amount tokens with contract _token - */ - function getEtherValue(uint256 _amount, address _token) external view returns (uint256) { - uint256 decimals = ERC20(_token).decimals(); - uint256 price = cachedPrices[_token]; - return price.mul(_amount).div(10**decimals); - } - - // - // The following is added to be backward-compatible with Argent's old backend - // - - function setKyberNetwork(KyberNetwork _kyberNetwork) external onlyManager { - kyberNetwork = _kyberNetwork; - } - - function syncPrice(ERC20 _token) external { - require(address(kyberNetwork) != address(0), "Kyber sync is disabled"); - (uint256 expectedRate,) = kyberNetwork.getExpectedRate(_token, ERC20(ETH_TOKEN_ADDRESS), 10000); - cachedPrices[address(_token)] = expectedRate; - } - - function syncPriceForTokenList(ERC20[] calldata _tokens) external { - require(address(kyberNetwork) != address(0), "Kyber sync is disabled"); - for (uint16 i = 0; i < _tokens.length; i++) { - (uint256 expectedRate,) = kyberNetwork.getExpectedRate(_tokens[i], ERC20(ETH_TOKEN_ADDRESS), 10000); - cachedPrices[address(_tokens[i])] = expectedRate; - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/WalletFactory.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/WalletFactory.sol deleted file mode 100644 index 8a87457f5..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/WalletFactory.sol +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/Proxy.sol"; -import "../wallet/BaseWallet.sol"; -import "../base/Owned.sol"; -import "../base/Managed.sol"; -import "../infrastructure/ens/IENSManager.sol"; -import "../infrastructure/ModuleRegistry.sol"; -import "../modules/storage/IGuardianStorage.sol"; - -/** - * @title WalletFactory - * @dev The WalletFactory contract creates and assigns wallets to accounts. - * @author Julien Niset - - */ -contract WalletFactory is Owned, Managed { - - // The address of the module dregistry - address public moduleRegistry; - // The address of the base wallet implementation - address public walletImplementation; - // The address of the ENS manager - address public ensManager; - // The address of the GuardianStorage - address public guardianStorage; - - // *************** Events *************************** // - - event ModuleRegistryChanged(address addr); - event ENSManagerChanged(address addr); - event GuardianStorageChanged(address addr); - event WalletCreated(address indexed wallet, address indexed owner, address indexed guardian); - - // *************** Modifiers *************************** // - - /** - * @dev Throws if the guardian storage address is not set. - */ - modifier guardianStorageDefined { - require(guardianStorage != address(0), "GuardianStorage address not defined"); - _; - } - - // *************** Constructor ********************** // - - /** - * @dev Default constructor. - */ - constructor(address _moduleRegistry, address _walletImplementation, address _ensManager) public { - moduleRegistry = _moduleRegistry; - walletImplementation = _walletImplementation; - ensManager = _ensManager; - } - - // *************** External Functions ********************* // - - /** - * @dev Lets the manager create a wallet for an owner account. - * The wallet is initialised with a list of modules and an ENS.. - * The wallet is created using the CREATE opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - */ - function createWallet( - address _owner, - address[] calldata _modules, - string calldata _label - ) - external - onlyManager - { - _createWallet(_owner, _modules, _label, address(0)); - } - - /** - * @dev Lets the manager create a wallet for an owner account. - * The wallet is initialised with a list of modules, a first guardian, and an ENS.. - * The wallet is created using the CREATE opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _guardian The guardian address. - */ - function createWalletWithGuardian( - address _owner, - address[] calldata _modules, - string calldata _label, - address _guardian - ) - external - onlyManager - guardianStorageDefined - { - require(_guardian != (address(0)), "WF: guardian cannot be null"); - _createWallet(_owner, _modules, _label, _guardian); - } - - /** - * @dev Lets the manager create a wallet for an owner account at a specific address. - * The wallet is initialised with a list of modules and an ENS. - * The wallet is created using the CREATE2 opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _salt The salt. - */ - function createCounterfactualWallet( - address _owner, - address[] calldata _modules, - string calldata _label, - bytes32 _salt - ) - external - onlyManager - { - _createCounterfactualWallet(_owner, _modules, _label, address(0), _salt); - } - - /** - * @dev Lets the manager create a wallet for an owner account at a specific address. - * The wallet is initialised with a list of modules, a first guardian, and an ENS. - * The wallet is created using the CREATE2 opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _guardian The guardian address. - * @param _salt The salt. - */ - function createCounterfactualWalletWithGuardian( - address _owner, - address[] calldata _modules, - string calldata _label, - address _guardian, - bytes32 _salt - ) - external - onlyManager - guardianStorageDefined - { - require(_guardian != (address(0)), "WF: guardian cannot be null"); - _createCounterfactualWallet(_owner, _modules, _label, _guardian, _salt); - } - - /** - * @dev Gets the address of a counterfactual wallet. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _salt The salt. - * @return the address that the wallet will have when created using CREATE2 and the same input parameters. - */ - function getAddressForCounterfactualWallet( - address _owner, - address[] calldata _modules, - bytes32 _salt - ) - external - view - returns (address _wallet) - { - _wallet = _getAddressForCounterfactualWallet(_owner, _modules, address(0), _salt); - } - - /** - * @dev Gets the address of a counterfactual wallet with a first default guardian. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _guardian The guardian address. - * @param _salt The salt. - * @return the address that the wallet will have when created using CREATE2 and the same input parameters. - */ - function getAddressForCounterfactualWalletWithGuardian( - address _owner, - address[] calldata _modules, - address _guardian, - bytes32 _salt - ) - external - view - returns (address _wallet) - { - require(_guardian != (address(0)), "WF: guardian cannot be null"); - _wallet = _getAddressForCounterfactualWallet(_owner, _modules, _guardian, _salt); - } - - /** - * @dev Lets the owner change the address of the module registry contract. - * @param _moduleRegistry The address of the module registry contract. - */ - function changeModuleRegistry(address _moduleRegistry) external onlyOwner { - require(_moduleRegistry != address(0), "WF: address cannot be null"); - moduleRegistry = _moduleRegistry; - emit ModuleRegistryChanged(_moduleRegistry); - } - - /** - * @dev Lets the owner change the address of the ENS manager contract. - * @param _ensManager The address of the ENS manager contract. - */ - function changeENSManager(address _ensManager) external onlyOwner { - require(_ensManager != address(0), "WF: address cannot be null"); - ensManager = _ensManager; - emit ENSManagerChanged(_ensManager); - } - - /** - * @dev Lets the owner change the address of the GuardianStorage contract. - * @param _guardianStorage The address of the GuardianStorage contract. - */ - function changeGuardianStorage(address _guardianStorage) external onlyOwner { - require(_guardianStorage != address(0), "WF: address cannot be null"); - guardianStorage = _guardianStorage; - emit GuardianStorageChanged(_guardianStorage); - } - - /** - * @dev Inits the module for a wallet by logging an event. - * The method can only be called by the wallet itself. - * @param _wallet The wallet. - */ - function init(BaseWallet _wallet) external pure { // solium-disable-line no-empty-blocks - //do nothing - } - - // *************** Internal Functions ********************* // - - /** - * @dev Helper method to create a wallet for an owner account. - * The wallet is initialised with a list of modules, a first guardian, and an ENS. - * The wallet is created using the CREATE opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _guardian (Optional) The guardian address. - */ - function _createWallet(address _owner, address[] memory _modules, string memory _label, address _guardian) internal { - _validateInputs(_owner, _modules, _label); - Proxy proxy = new Proxy(walletImplementation); - address payable wallet = address(proxy); - _configureWallet(BaseWallet(wallet), _owner, _modules, _label, _guardian); - } - - /** - * @dev Helper method to create a wallet for an owner account at a specific address. - * The wallet is initialised with a list of modules, a first guardian, and an ENS. - * The wallet is created using the CREATE2 opcode. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _guardian The guardian address. - * @param _salt The salt. - */ - function _createCounterfactualWallet( - address _owner, - address[] memory _modules, - string memory _label, - address _guardian, - bytes32 _salt - ) - internal - { - _validateInputs(_owner, _modules, _label); - bytes32 newsalt = _newSalt(_salt, _owner, _modules, _guardian); - bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(walletImplementation)); - address payable wallet; - // solium-disable-next-line security/no-inline-assembly - assembly { - wallet := create2(0, add(code, 0x20), mload(code), newsalt) - if iszero(extcodesize(wallet)) { revert(0, returndatasize) } - } - _configureWallet(BaseWallet(wallet), _owner, _modules, _label, _guardian); - } - - /** - * @dev Helper method to configure a wallet for a set of input parameters. - * @param _wallet The target wallet - * @param _owner The account address. - * @param _modules The list of modules. - * @param _label ENS label of the new wallet, e.g. franck. - * @param _guardian (Optional) The guardian address. - */ - function _configureWallet( - BaseWallet _wallet, - address _owner, - address[] memory _modules, - string memory _label, - address _guardian - ) - internal - { - // add the factory to modules so it can claim the reverse ENS or add a guardian - address[] memory extendedModules = new address[](_modules.length + 1); - extendedModules[0] = address(this); - for (uint i = 0; i < _modules.length; i++) { - extendedModules[i + 1] = _modules[i]; - } - // initialise the wallet with the owner and the extended modules - _wallet.init(_owner, extendedModules); - // add guardian if needed - if (_guardian != address(0)) { - IGuardianStorage(guardianStorage).addGuardian(_wallet, _guardian); - } - // register ENS - _registerWalletENS(address(_wallet), _label); - // remove the factory from the authorised modules - _wallet.authoriseModule(address(this), false); - // emit event - emit WalletCreated(address(_wallet), _owner, _guardian); - } - - /** - * @dev Gets the address of a counterfactual wallet. - * @param _owner The account address. - * @param _modules The list of modules. - * @param _salt The salt. - * @param _guardian (Optional) The guardian address. - * @return the address that the wallet will have when created using CREATE2 and the same input parameters. - */ - function _getAddressForCounterfactualWallet( - address _owner, - address[] memory _modules, - address _guardian, - bytes32 _salt - ) - internal - view - returns (address _wallet) - { - bytes32 newsalt = _newSalt(_salt, _owner, _modules, _guardian); - bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(walletImplementation)); - bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), newsalt, keccak256(code))); - _wallet = address(uint160(uint256(hash))); - } - - /** - * @dev Generates a new salt based on a provided salt, an owner, a list of modules and an optional guardian. - * @param _salt The slat provided. - * @param _owner The owner address. - * @param _modules The list of modules. - * @param _guardian The guardian address. - */ - function _newSalt(bytes32 _salt, address _owner, address[] memory _modules, address _guardian) internal pure returns (bytes32) { - if (_guardian == address(0)) { - return keccak256(abi.encodePacked(_salt, _owner, _modules)); - } else { - return keccak256(abi.encodePacked(_salt, _owner, _modules, _guardian)); - } - } - - /** - * @dev Throws if the owner and the modules are not valid. - * @param _owner The owner address. - * @param _modules The list of modules. - */ - function _validateInputs(address _owner, address[] memory _modules, string memory _label) internal view { - require(_owner != address(0), "WF: owner cannot be null"); - require(_modules.length > 0, "WF: cannot assign with less than 1 module"); - require(ModuleRegistry(moduleRegistry).isRegisteredModule(_modules), "WF: one or more modules are not registered"); - bytes memory labelBytes = bytes(_label); - require(labelBytes.length != 0, "WF: ENS lable must be defined"); - } - - /** - * @dev Register an ENS subname to a wallet. - * @param _wallet The wallet address. - * @param _label ENS label of the new wallet (e.g. franck). - */ - function _registerWalletENS(address payable _wallet, string memory _label) internal { - // claim reverse - address ensResolver = IENSManager(ensManager).ensResolver(); - bytes memory methodData = abi.encodeWithSignature("claimWithResolver(address,address)", ensManager, ensResolver); - address ensReverseRegistrar = IENSManager(ensManager).getENSReverseRegistrar(); - BaseWallet(_wallet).invoke(ensReverseRegistrar, 0, methodData); - // register with ENS manager - IENSManager(ensManager).register(_label, _wallet); - } -} diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSManager.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSManager.sol deleted file mode 100644 index 8cf1b1447..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSManager.sol +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../../lib/ens/ENS.sol"; -import "../../../lib/utils/strings.sol"; -import "./IENSManager.sol"; -import "./ENSResolver.sol"; -import "./ENSReverseRegistrar.sol"; -import "../../base/Managed.sol"; - -/** - * @title ArgentENSManager - * @dev Implementation of an ENS manager that orchestrates the complete - * registration of subdomains for a single root (e.g. argent.eth). - * The contract defines a manager role who is the only role that can trigger the registration of - * a new subdomain. - * @author Julien Niset - - */ -contract ArgentENSManager is IENSManager, Owned, Managed { - - using strings for *; - - // The managed root name - string public rootName; - // The managed root node - bytes32 public rootNode; - - ENS public ensRegistry; - ENSResolver public ensResolver; - - // namehash('addr.reverse') - bytes32 constant public ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; - - // *************** Constructor ********************** // - - /** - * @dev Constructor that sets the ENS root name and root node to manage. - * @param _rootName The root name (e.g. argentx.eth). - * @param _rootNode The node of the root name (e.g. namehash(argentx.eth)). - * @param _ensRegistry The address of the ENS registry - * @param _ensResolver The address of the ENS resolver - */ - constructor(string memory _rootName, bytes32 _rootNode, address _ensRegistry, address _ensResolver) public { - rootName = _rootName; - rootNode = _rootNode; - ensRegistry = ENS(_ensRegistry); - ensResolver = ENSResolver(_ensResolver); - } - - // *************** External Functions ********************* // - - /** - * @dev This function must be called when the ENS Manager contract is replaced - * and the address of the new Manager should be provided. - * @param _newOwner The address of the new ENS manager that will manage the root node. - */ - function changeRootnodeOwner(address _newOwner) external onlyOwner { - ensRegistry.setOwner(rootNode, _newOwner); - emit RootnodeOwnerChange(rootNode, _newOwner); - } - - /** - * @dev Lets the owner change the address of the ENS resolver contract. - * @param _ensResolver The address of the ENS resolver contract. - */ - function changeENSResolver(address _ensResolver) external onlyOwner { - require(_ensResolver != address(0), "WF: address cannot be null"); - ensResolver = ENSResolver(_ensResolver); - emit ENSResolverChanged(_ensResolver); - } - - /** - * @dev Lets the manager assign an ENS subdomain of the root node to a target address. - * Registers both the forward and reverse ENS. - * @param _label The subdomain label. - * @param _owner The owner of the subdomain. - */ - function register(string calldata _label, address _owner) external onlyManager { - bytes32 labelNode = keccak256(abi.encodePacked(_label)); - bytes32 node = keccak256(abi.encodePacked(rootNode, labelNode)); - address currentOwner = ensRegistry.owner(node); - require(currentOwner == address(0), "AEM: _label is alrealdy owned"); - - // Forward ENS - ensRegistry.setSubnodeRecord(rootNode, labelNode, _owner, address(ensResolver), 0); - ensResolver.setAddr(node, _owner); - - // Reverse ENS - strings.slice[] memory parts = new strings.slice[](2); - parts[0] = _label.toSlice(); - parts[1] = rootName.toSlice(); - string memory name = ".".toSlice().join(parts); - ENSReverseRegistrar reverseRegistrar = ENSReverseRegistrar(_getENSReverseRegistrar()); - bytes32 reverseNode = reverseRegistrar.node(_owner); - ensResolver.setName(reverseNode, name); - - emit Registered(_owner, name); - } - - /** - * @dev Gets the official ENS reverse registrar. - * @return Address of the ENS reverse registrar. - */ - function getENSReverseRegistrar() external view returns (address) { - return _getENSReverseRegistrar(); - } - - // *************** Public Functions ********************* // - - /** - * @dev Returns true is a given subnode is available. - * @param _subnode The target subnode. - * @return true if the subnode is available. - */ - function isAvailable(bytes32 _subnode) public view returns (bool) { - bytes32 node = keccak256(abi.encodePacked(rootNode, _subnode)); - address currentOwner = ensRegistry.owner(node); - if (currentOwner == address(0)) { - return true; - } - return false; - } - - function _getENSReverseRegistrar() internal view returns (address) { - return ensRegistry.owner(ADDR_REVERSE_NODE); - } -} diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSResolver.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSResolver.sol deleted file mode 100644 index 6dd008c6c..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ArgentENSResolver.sol +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../base/Owned.sol"; -import "../../base/Managed.sol"; -import "./ENSResolver.sol"; - -/** - * @title ArgentENSResolver - * @dev Basic implementation of a Resolver. - * The contract defines a manager role who is the only role that can add a new name - * to the list of resolved names. - * @author Julien Niset - - */ -contract ArgentENSResolver is Owned, Managed, ENSResolver { - - bytes4 constant SUPPORT_INTERFACE_ID = 0x01ffc9a7; - bytes4 constant ADDR_INTERFACE_ID = 0x3b3b57de; - bytes4 constant NAME_INTERFACE_ID = 0x691f3431; - - // mapping between namehash and resolved records - mapping (bytes32 => Record) records; - - struct Record { - address addr; - string name; - } - - // *************** Public Functions ********************* // - - /** - * @dev Lets the manager set the address associated with an ENS node. - * @param _node The node to update. - * @param _addr The address to set. - */ - function setAddr(bytes32 _node, address _addr) public onlyManager { - records[_node].addr = _addr; - emit AddrChanged(_node, _addr); - } - - /** - * @dev Lets the manager set the name associated with an ENS node. - * @param _node The node to update. - * @param _name The name to set. - */ - function setName(bytes32 _node, string memory _name) public onlyManager { - records[_node].name = _name; - emit NameChanged(_node, _name); - } - - /** - * @dev Gets the address associated to an ENS node. - * @param _node The target node. - * @return the address of the target node. - */ - function addr(bytes32 _node) public view returns (address) { - return records[_node].addr; - } - - /** - * @dev Gets the name associated to an ENS node. - * @param _node The target ENS node. - * @return the name of the target ENS node. - */ - function name(bytes32 _node) public view returns (string memory) { - return records[_node].name; - } - - /** - * @dev Returns true if the resolver implements the interface specified by the provided hash. - * @param _interfaceID The ID of the interface to check for. - * @return True if the contract implements the requested interface. - */ - function supportsInterface(bytes4 _interfaceID) public pure returns (bool) { - return _interfaceID == SUPPORT_INTERFACE_ID || _interfaceID == ADDR_INTERFACE_ID || _interfaceID == NAME_INTERFACE_ID; - } - -} diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSResolver.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSResolver.sol deleted file mode 100644 index bec0e3cc6..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSResolver.sol +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @dev ENS Resolver interface. - */ -contract ENSResolver { - event AddrChanged(bytes32 indexed _node, address _addr); - event NameChanged(bytes32 indexed _node, string _name); - - function addr(bytes32 _node) public view returns (address); - function setAddr(bytes32 _node, address _addr) public; - function name(bytes32 _node) public view returns (string memory); - function setName(bytes32 _node, string memory _name) public; -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSReverseRegistrar.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSReverseRegistrar.sol deleted file mode 100644 index 8bb7ba354..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/ENSReverseRegistrar.sol +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @dev ENS Reverse Registrar interface. - */ -contract ENSReverseRegistrar { - function claim(address _owner) public returns (bytes32); - function claimWithResolver(address _owner, address _resolver) public returns (bytes32); - function setName(string memory _name) public returns (bytes32); - function node(address _addr) public pure returns (bytes32); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/IENSManager.sol b/contracts-legacy/v1.6.0/contracts/infrastructure/ens/IENSManager.sol deleted file mode 100644 index e1f6bb5d4..000000000 --- a/contracts-legacy/v1.6.0/contracts/infrastructure/ens/IENSManager.sol +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @dev Interface for an ENS Mananger. - */ -interface IENSManager { - event RootnodeOwnerChange(bytes32 indexed _rootnode, address indexed _newOwner); - event ENSResolverChanged(address addr); - event Registered(address indexed _owner, string _ens); - event Unregistered(string _ens); - - function changeRootnodeOwner(address _newOwner) external; - function register(string calldata _label, address _owner) external; - function isAvailable(bytes32 _subnode) external view returns(bool); - function getENSReverseRegistrar() external view returns (address); - function ensResolver() external view returns (address); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/legacy/LegacyBaseWallet.sol b/contracts-legacy/v1.6.0/contracts/legacy/LegacyBaseWallet.sol deleted file mode 100644 index 16f570efa..000000000 --- a/contracts-legacy/v1.6.0/contracts/legacy/LegacyBaseWallet.sol +++ /dev/null @@ -1,159 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title LegacyBaseWallet - * @dev Simple modular wallet that authorises modules to call its invoke() method. - * Based on https://gist.github.com/Arachnid/a619d31f6d32757a4328a428286da186 by - * @author Julien Niset - - */ - - interface LegacyModule { - - /** - * @dev Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. - * @param _wallet The wallet. - */ - function init(LegacyBaseWallet _wallet) external; - - /** - * @dev Adds a module to a wallet. - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(LegacyBaseWallet _wallet, LegacyModule _module) external; - - /** - * @dev Utility method to recover any ERC20 token that was sent to the - * module by mistake. - * @param _token The token to recover. - */ - function recoverToken(address _token) external; -} - -contract LegacyBaseWallet { - - // The implementation of the proxy - address public implementation; - // The owner of the wallet - address public owner; - // The authorised modules - mapping (address => bool) public authorised; - // The enabled static calls - mapping (bytes4 => address) public enabled; - // The number of modules - uint public modules; - - event AuthorisedModule(address indexed module, bool value); - event EnabledStaticCall(address indexed module, bytes4 indexed method); - event Invoked(address indexed module, address indexed target, uint indexed value, bytes data); - event Received(uint indexed value, address indexed sender, bytes data); - event OwnerChanged(address owner); - - /** - * @dev Throws if the sender is not an authorised module. - */ - modifier moduleOnly { - require(authorised[msg.sender], "BW: msg.sender not an authorized module"); - _; - } - - /** - * @dev Inits the wallet by setting the owner and authorising a list of modules. - * @param _owner The owner. - * @param _modules The modules to authorise. - */ - function init(address _owner, address[] calldata _modules) external { - require(owner == address(0) && modules == 0, "BW: wallet already initialised"); - require(_modules.length > 0, "BW: construction requires at least 1 module"); - owner = _owner; - modules = _modules.length; - for(uint256 i = 0; i < _modules.length; i++) { - require(authorised[_modules[i]] == false, "BW: module is already added"); - authorised[_modules[i]] = true; - LegacyModule(_modules[i]).init(this); - emit AuthorisedModule(_modules[i], true); - } - } - - /** - * @dev Enables/Disables a module. - * @param _module The target module. - * @param _value Set to true to authorise the module. - */ - function authoriseModule(address _module, bool _value) external moduleOnly { - if (authorised[_module] != _value) { - if(_value == true) { - modules += 1; - authorised[_module] = true; - LegacyModule(_module).init(this); - } - else { - modules -= 1; - require(modules > 0, "BW: wallet must have at least one module"); - delete authorised[_module]; - } - emit AuthorisedModule(_module, _value); - } - } - - /** - * @dev Enables a static method by specifying the target module to which the call - * must be delegated. - * @param _module The target module. - * @param _method The static method signature. - */ - function enableStaticCall(address _module, bytes4 _method) external moduleOnly { - require(authorised[_module], "BW: must be an authorised module for static call"); - enabled[_method] = _module; - emit EnabledStaticCall(_module, _method); - } - - /** - * @dev Sets a new owner for the wallet. - * @param _newOwner The new owner. - */ - function setOwner(address _newOwner) external moduleOnly { - require(_newOwner != address(0), "BW: address cannot be null"); - owner = _newOwner; - emit OwnerChanged(_newOwner); - } - - /** - * @dev Performs a generic transaction. - * @param _target The address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly { - // solium-disable-next-line security/no-call-value - (bool success, ) = _target.call.value(_value)(_data); - require(success, "BW: call to target failed"); - emit Invoked(msg.sender, _target, _value, _data); - } - - /** - * @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to - * implement specific static methods. It delegates the static call to a target contract if the data corresponds - * to an enabled method, or logs the call otherwise. - */ - function() external payable { - if(msg.data.length > 0) { - address module = enabled[msg.sig]; - if(module == address(0)) { - emit Received(msg.value, msg.sender, msg.data); - } - else { - require(authorised[module], "BW: must be an authorised module for static call"); - // solium-disable-next-line security/no-inline-assembly - assembly { - calldatacopy(0, 0, calldatasize()) - let result := staticcall(gas, module, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } - } - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/legacy/LegacyTransferManager.sol b/contracts-legacy/v1.6.0/contracts/legacy/LegacyTransferManager.sol deleted file mode 100644 index e925de49e..000000000 --- a/contracts-legacy/v1.6.0/contracts/legacy/LegacyTransferManager.sol +++ /dev/null @@ -1,501 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "../modules/common/BaseModule.sol"; -import "../modules/common/RelayerModule.sol"; -import "../modules/common/OnlyOwnerModule.sol"; -import "../modules/common/BaseTransfer.sol"; -import "../modules/common/LimitManager.sol"; -import "../infrastructure/TokenPriceProvider.sol"; -import "../modules/storage/TransferStorage.sol"; -import "../../lib/other/ERC20.sol"; - -/** - * @title LegacyTransferManager - * @dev Copy of TransferManager module as from release 1.5 - */ -contract LegacyTransferManager is BaseModule, RelayerModule, OnlyOwnerModule, BaseTransfer, LimitManager { - - bytes32 constant NAME = "TransferManager"; - - bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES = bytes4(keccak256("isValidSignature(bytes,bytes)")); - bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES32 = bytes4(keccak256("isValidSignature(bytes32,bytes)")); - - enum ActionType { Transfer } - - using SafeMath for uint256; - - struct TokenManagerConfig { - // Mapping between pending action hash and their timestamp - mapping (bytes32 => uint256) pendingActions; - } - - // wallet specific storage - mapping (address => TokenManagerConfig) internal configs; - - // The security period - uint256 public securityPeriod; - // The execution window - uint256 public securityWindow; - // The Token storage - TransferStorage public transferStorage; - // The Token price provider - TokenPriceProvider public priceProvider; - // The previous limit manager needed to migrate the limits - LimitManager public oldLimitManager; - - // *************** Events *************************** // - - event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter); - event RemovedFromWhitelist(address indexed wallet, address indexed target); - event PendingTransferCreated(address indexed wallet, bytes32 indexed id, uint256 indexed executeAfter, - address token, address to, uint256 amount, bytes data); - event PendingTransferExecuted(address indexed wallet, bytes32 indexed id); - event PendingTransferCanceled(address indexed wallet, bytes32 indexed id); - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - TransferStorage _transferStorage, - GuardianStorage _guardianStorage, - address _priceProvider, - uint256 _securityPeriod, - uint256 _securityWindow, - uint256 _defaultLimit, - LimitManager _oldLimitManager - ) - BaseModule(_registry, _guardianStorage, NAME) - LimitManager(_defaultLimit) - public - { - transferStorage = _transferStorage; - priceProvider = TokenPriceProvider(_priceProvider); - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - oldLimitManager = _oldLimitManager; - } - - /** - * @dev Inits the module for a wallet by setting up the isValidSignature (EIP 1271) - * static call redirection from the wallet to the module and copying all the parameters - * of the daily limit from the previous implementation of the LimitManager module. - * @param _wallet The target wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - - // setup static calls - _wallet.enableStaticCall(address(this), ERC1271_ISVALIDSIGNATURE_BYTES); - _wallet.enableStaticCall(address(this), ERC1271_ISVALIDSIGNATURE_BYTES32); - - // setup default limit for new deployment - if (address(oldLimitManager) == address(0)) { - super.init(_wallet); - return; - } - // get limit from previous LimitManager - uint256 current = oldLimitManager.getCurrentLimit(_wallet); - (uint256 pending, uint64 changeAfter) = oldLimitManager.getPendingLimit(_wallet); - // setup default limit for new wallets - if (current == 0 && changeAfter == 0) { - super.init(_wallet); - return; - } - // migrate existing limit for existing wallets - if (current == pending) { - limits[address(_wallet)].limit.current = uint128(current); - } else { - limits[address(_wallet)].limit = Limit(uint128(current), uint128(pending), changeAfter); - } - // migrate daily pending if we are within a rolling period - (uint256 unspent, uint64 periodEnd) = oldLimitManager.getDailyUnspent(_wallet); - // solium-disable-next-line security/no-block-members - if (periodEnd > now) { - limits[address(_wallet)].dailySpent = DailySpent(uint128(current.sub(unspent)), periodEnd); - } - } - - // *************** External/Public Functions ********************* // - - /** - * @dev lets the owner transfer tokens (ETH or ERC20) from a wallet. - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _to The destination address - * @param _amount The amoutn of token to transfer - * @param _data The data for the transaction - */ - function transferToken( - BaseWallet _wallet, - address _token, - address _to, - uint256 _amount, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _to)) { - // transfer to whitelist - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - uint256 etherAmount = (_token == ETH_TOKEN) ? _amount : priceProvider.getEtherValue(_amount, _token); - if (checkAndUpdateDailySpent(_wallet, etherAmount)) { - // transfer under the limit - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - // transfer above the limit - (bytes32 id, uint256 executeAfter) = addPendingAction(ActionType.Transfer, _wallet, _token, _to, _amount, _data); - emit PendingTransferCreated(address(_wallet), id, executeAfter, _token, _to, _amount, _data); - } - } - } - - /** - * @dev lets the owner approve an allowance of ERC20 tokens for a spender (dApp). - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _spender The address of the spender - * @param _amount The amount of tokens to approve - */ - function approveToken( - BaseWallet _wallet, - address _token, - address _spender, - uint256 _amount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _spender)) { - // approve to whitelist - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // get current alowance - uint256 currentAllowance = ERC20(_token).allowance(address(_wallet), _spender); - if (_amount <= currentAllowance) { - // approve if we reduce the allowance - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // check if delta is under the limit - uint delta = _amount - currentAllowance; - uint256 deltaInEth = priceProvider.getEtherValue(delta, _token); - require(checkAndUpdateDailySpent(_wallet, deltaInEth), "TM: Approve above daily limit"); - // approve if under the limit - doApproveToken(_wallet, _token, _spender, _amount); - } - } - } - - /** - * @dev lets the owner call a contract. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - * @param _value The amount of ETH to transfer as part of call - * @param _data The encoded method data - */ - function callContract( - BaseWallet _wallet, - address _contract, - uint256 _value, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Make sure we don't call a module, the wallet itself, or a supported ERC20 - authoriseContractCall(_wallet, _contract); - - if (isWhitelisted(_wallet, _contract)) { - // call to whitelist - doCallContract(_wallet, _contract, _value, _data); - } else { - require(checkAndUpdateDailySpent(_wallet, _value), "TM: Call contract above daily limit"); - // call under the limit - doCallContract(_wallet, _contract, _value, _data); - } - } - - /** - * @dev lets the owner do an ERC20 approve followed by a call to a contract. - * We assume that the contract will pull the tokens and does not require ETH. - * @param _wallet The target wallet. - * @param _token The token to approve. - * @param _contract The address of the contract. - * @param _amount The amount of ERC20 tokens to approve. - * @param _data The encoded method data - */ - function approveTokenAndCallContract( - BaseWallet _wallet, - address _token, - address _contract, - uint256 _amount, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Make sure we don't call a module, the wallet itself, or a supported ERC20 - authoriseContractCall(_wallet, _contract); - - if (isWhitelisted(_wallet, _contract)) { - doApproveToken(_wallet, _token, _contract, _amount); - doCallContract(_wallet, _contract, 0, _data); - } else { - // get current alowance - uint256 currentAllowance = ERC20(_token).allowance(address(_wallet), _contract); - if (_amount <= currentAllowance) { - // no need to approve more - doCallContract(_wallet, _contract, 0, _data); - } else { - // check if delta is under the limit - uint delta = _amount - currentAllowance; - uint256 deltaInEth = priceProvider.getEtherValue(delta, _token); - require(checkAndUpdateDailySpent(_wallet, deltaInEth), "TM: Approve above daily limit"); - // approve if under the limit - doApproveToken(_wallet, _token, _contract, _amount); - doCallContract(_wallet, _contract, 0, _data); - } - } - } - - /** - * @dev Adds an address to the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to add. - */ - function addToWhitelist( - BaseWallet _wallet, - address _target - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(!isWhitelisted(_wallet, _target), "TT: target already whitelisted"); - // solium-disable-next-line security/no-block-members - uint256 whitelistAfter = now.add(securityPeriod); - transferStorage.setWhitelist(_wallet, _target, whitelistAfter); - emit AddedToWhitelist(address(_wallet), _target, uint64(whitelistAfter)); - } - - /** - * @dev Removes an address from the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to remove. - */ - function removeFromWhitelist( - BaseWallet _wallet, - address _target - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(isWhitelisted(_wallet, _target), "TT: target not whitelisted"); - transferStorage.setWhitelist(_wallet, _target, 0); - emit RemovedFromWhitelist(address(_wallet), _target); - } - - /** - * @dev Executes a pending transfer for a wallet. - * The method can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _token The token of the pending transfer. - * @param _to The destination address of the pending transfer. - * @param _amount The amount of token to transfer of the pending transfer. - * @param _data The data associated to the pending transfer. - * @param _block The block at which the pending transfer was created. - */ - function executePendingTransfer( - BaseWallet _wallet, - address _token, - address _to, - uint _amount, - bytes calldata _data, - uint _block - ) - external - onlyWhenUnlocked(_wallet) - { - bytes32 id = keccak256(abi.encodePacked(ActionType.Transfer, _token, _to, _amount, _data, _block)); - uint executeAfter = configs[address(_wallet)].pendingActions[id]; - require(executeAfter > 0, "TT: unknown pending transfer"); - uint executeBefore = executeAfter.add(securityWindow); - // solium-disable-next-line security/no-block-members - require(executeAfter <= now && now <= executeBefore, "TT: transfer outside of the execution window"); - delete configs[address(_wallet)].pendingActions[id]; - doTransfer(_wallet, _token, _to, _amount, _data); - emit PendingTransferExecuted(address(_wallet), id); - } - - function cancelPendingTransfer( - BaseWallet _wallet, - bytes32 _id - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(configs[address(_wallet)].pendingActions[_id] > 0, "TT: unknown pending action"); - delete configs[address(_wallet)].pendingActions[_id]; - emit PendingTransferCanceled(address(_wallet), _id); - } - - /** - * @dev Lets the owner of a wallet change its daily limit. - * The limit is expressed in ETH. Changes to the limit take 24 hours. - * @param _wallet The target wallet. - * @param _newLimit The new limit. - */ - function changeLimit(BaseWallet _wallet, uint256 _newLimit) external onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - changeLimit(_wallet, _newLimit, securityPeriod); - } - - /** - * @dev Convenience method to disable the limit - * The limit is disabled by setting it to an arbitrary large value. - * @param _wallet The target wallet. - */ - function disableLimit(BaseWallet _wallet) external onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - disableLimit(_wallet, securityPeriod); - } - - /** - * @dev Checks if an address is whitelisted for a wallet. - * @param _wallet The target wallet. - * @param _target The address. - * @return true if the address is whitelisted. - */ - function isWhitelisted(BaseWallet _wallet, address _target) public view returns (bool _isWhitelisted) { - uint whitelistAfter = transferStorage.getWhitelist(_wallet, _target); - // solium-disable-next-line security/no-block-members - return whitelistAfter > 0 && whitelistAfter < now; - } - - /** - * @dev Gets the info of a pending transfer for a wallet. - * @param _wallet The target wallet. - * @param _id The pending transfer ID. - * @return the epoch time at which the pending transfer can be executed. - */ - function getPendingTransfer(BaseWallet _wallet, bytes32 _id) external view returns (uint64 _executeAfter) { - _executeAfter = uint64(configs[address(_wallet)].pendingActions[_id]); - } - - /** - * @dev Implementation of EIP 1271. - * Should return whether the signature provided is valid for the provided data. - * @param _data Arbitrary length data signed on the behalf of address(this) - * @param _signature Signature byte array associated with _data - */ - function isValidSignature(bytes calldata _data, bytes calldata _signature) external view returns (bytes4) { - bytes32 msgHash = keccak256(abi.encodePacked(_data)); - isValidSignature(msgHash, _signature); - return ERC1271_ISVALIDSIGNATURE_BYTES; - } - - /** - * @dev Implementation of EIP 1271. - * Should return whether the signature provided is valid for the provided data. - * @param _msgHash Hash of a message signed on the behalf of address(this) - * @param _signature Signature byte array associated with _msgHash - */ - function isValidSignature(bytes32 _msgHash, bytes memory _signature) public view returns (bytes4) { - require(_signature.length == 65, "TM: invalid signature length"); - address signer = recoverSigner(_msgHash, _signature, 0); - require(isOwner(BaseWallet(msg.sender), signer), "TM: Invalid signer"); - return ERC1271_ISVALIDSIGNATURE_BYTES32; - } - - // *************** Internal Functions ********************* // - - /** - * @dev Creates a new pending action for a wallet. - * @param _action The target action. - * @param _wallet The target wallet. - * @param _token The target token for the action. - * @param _to The recipient of the action. - * @param _amount The amount of token associated to the action. - * @param _data The data associated to the action. - * @return the identifier for the new pending action and the time when the action can be executed - */ - function addPendingAction( - ActionType _action, - BaseWallet _wallet, - address _token, - address _to, - uint _amount, - bytes memory _data - ) - internal - returns (bytes32 id, uint256 executeAfter) - { - id = keccak256(abi.encodePacked(_action, _token, _to, _amount, _data, block.number)); - require(configs[address(_wallet)].pendingActions[id] == 0, "TM: duplicate pending action"); - // solium-disable-next-line security/no-block-members - executeAfter = now.add(securityPeriod); - configs[address(_wallet)].pendingActions[id] = executeAfter; - } - - /** - * @dev Make sure a contract call is not trying to call a module, the wallet itself, or a supported ERC20. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - */ - function authoriseContractCall(BaseWallet _wallet, address _contract) internal view { - require( - _contract != address(_wallet) && // not the wallet itself - !_wallet.authorised(_contract) && // not an authorised module - (priceProvider.cachedPrices(_contract) == 0 || isLimitDisabled(_wallet)), // not an ERC20 listed in the provider (or limit disabled) - "TM: Forbidden contract"); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides refund to add the refund in the daily limit. - function refund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _gasLimit, uint _signatures, address _relayer) internal { - // 21000 (transaction) + 7620 (execution of refund) + 7324 (execution of updateDailySpent) + 672 to log the event + _gasUsed - uint256 amount = 36616 + _gasUsed; - if (_gasPrice > 0 && _signatures > 0 && amount <= _gasLimit) { - if (_gasPrice > tx.gasprice) { - amount = amount * tx.gasprice; - } else { - amount = amount * _gasPrice; - } - checkAndUpdateDailySpent(_wallet, amount); - invokeWallet(address(_wallet), _relayer, amount, EMPTY_BYTES); - } - } - - // Overrides verifyRefund to add the refund in the daily limit. - function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) { - if (_gasPrice > 0 && _signatures > 0 && ( - address(_wallet).balance < _gasUsed * _gasPrice || - isWithinDailyLimit(_wallet, getCurrentLimit(_wallet), _gasUsed * _gasPrice) == false || - _wallet.authorised(address(this)) == false - )) - { - return false; - } - return true; - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/ApprovedTransfer.sol b/contracts-legacy/v1.6.0/contracts/modules/ApprovedTransfer.sol deleted file mode 100644 index e8b1c3a08..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/ApprovedTransfer.sol +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModuleV2.sol"; -import "./common/BaseTransfer.sol"; - -/** - * @title ApprovedTransfer - * @dev Module to transfer tokens (ETH or ERC20) with the approval of guardians. - * @author Julien Niset - - */ -contract ApprovedTransfer is BaseModule, RelayerModuleV2, BaseTransfer { - - bytes32 constant NAME = "ApprovedTransfer"; - - constructor(ModuleRegistry _registry, GuardianStorage _guardianStorage) BaseModule(_registry, _guardianStorage, NAME) public { - - } - - /** - * @dev transfers tokens (ETH or ERC20) from a wallet. - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _to The destination address - * @param _amount The amoutnof token to transfer - * @param _data The data for the transaction (only for ETH transfers) - */ - function transferToken( - BaseWallet _wallet, - address _token, - address _to, - uint256 _amount, - bytes calldata _data - ) - external - onlyExecute - onlyWhenUnlocked(_wallet) - { - doTransfer(_wallet, _token, _to, _amount, _data); - } - - /** - * @dev call a contract. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - * @param _value The amount of ETH to transfer as part of call - * @param _data The encoded method data - */ - function callContract( - BaseWallet _wallet, - address _contract, - uint256 _value, - bytes calldata _data - ) - external - onlyExecute - onlyWhenUnlocked(_wallet) - { - require(!_wallet.authorised(_contract) && _contract != address(_wallet), "AT: Forbidden contract"); - doCallContract(_wallet, _contract, _value, _data); - } - - /** - * @dev lets the owner do an ERC20 approve followed by a call to a contract. - * The address to approve may be different than the contract to call. - * We assume that the contract does not require ETH. - * @param _wallet The target wallet. - * @param _token The token to approve. - * @param _spender The address to approve. - * @param _amount The amount of ERC20 tokens to approve. - * @param _contract The contract to call. - * @param _data The encoded method data - */ - function approveTokenAndCallContract( - BaseWallet _wallet, - address _token, - address _spender, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyExecute - onlyWhenUnlocked(_wallet) - { - require(!_wallet.authorised(_contract) && _contract != address(_wallet), "AT: Forbidden contract"); - doApproveTokenAndCallContract(_wallet, _token, _spender, _amount, _contract, _data); - } - - // *************** Implementation of RelayerModule methods ********************* // - - function validateSignatures( - BaseWallet _wallet, - bytes memory /* _data */, - bytes32 _signHash, - bytes memory _signatures - ) - internal - view - returns (bool) - { - return validateSignatures(_wallet, _signHash, _signatures, OwnerSignature.Required); - } - - function getRequiredSignatures(BaseWallet _wallet, bytes memory /* _data */) public view returns (uint256) { - // owner + [n/2] guardians - return 1 + SafeMath.ceil(guardianStorage.guardianCount(_wallet), 2); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/CompoundManager.sol b/contracts-legacy/v1.6.0/contracts/modules/CompoundManager.sol deleted file mode 100644 index 0762803e6..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/CompoundManager.sol +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; -import "./common/OnlyOwnerModule.sol"; -import "../defi/Loan.sol"; -import "../defi/Invest.sol"; -import "../infrastructure/CompoundRegistry.sol"; - -interface IComptroller { - function enterMarkets(address[] calldata _cTokens) external returns (uint[] memory); - function exitMarket(address _cToken) external returns (uint); - function getAssetsIn(address _account) external view returns (address[] memory); - function getAccountLiquidity(address _account) external view returns (uint, uint, uint); - function checkMembership(address account, ICToken cToken) external view returns (bool); -} - -interface ICToken { - function comptroller() external view returns (address); - function underlying() external view returns (address); - function symbol() external view returns (string memory); - function exchangeRateCurrent() external returns (uint256); - function exchangeRateStored() external view returns (uint256); - function balanceOf(address _account) external view returns (uint256); - function borrowBalanceCurrent(address _account) external returns (uint256); - function borrowBalanceStored(address _account) external view returns (uint256); -} - -/** - * @title CompoundManager - * @dev Module to invest and borrow tokens with CompoundV2 - * @author Julien Niset - - */ -contract CompoundManager is Loan, Invest, BaseModule, RelayerModule, OnlyOwnerModule { - - bytes32 constant NAME = "CompoundManager"; - - // The Compound IComptroller contract - IComptroller public comptroller; - // The registry mapping underlying with cTokens - CompoundRegistry public compoundRegistry; - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - using SafeMath for uint256; - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - IComptroller _comptroller, - CompoundRegistry _compoundRegistry - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - comptroller = _comptroller; - compoundRegistry = _compoundRegistry; - } - - /* ********************************** Implementation of Loan ************************************* */ - - /** - * @dev Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of tokens borrowed. - * @return bytes32(0) as Compound does not allow the creation of multiple loans. - */ - function openLoan( - BaseWallet _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - address[] memory markets = new address[](2); - markets[0] = compoundRegistry.getCToken(_collateral); - markets[1] = compoundRegistry.getCToken(_debtToken); - invokeWallet(address(_wallet), address(comptroller), 0, abi.encodeWithSignature("enterMarkets(address[])", markets)); - mint(_wallet, markets[0], _collateral, _collateralAmount); - borrow(_wallet, markets[1], _debtAmount); - emit LoanOpened(address(_wallet), _loanId, _collateral, _collateralAmount, _debtToken, _debtAmount); - } - - /** - * @dev Closes the collateralized loan in all markets by repaying all debts (plus interest). Note that it does not redeem the collateral. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - */ - function closeLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - address[] memory markets = comptroller.getAssetsIn(address(_wallet)); - for (uint i = 0; i < markets.length; i++) { - address cToken = markets[i]; - uint debt = ICToken(cToken).borrowBalanceCurrent(address(_wallet)); - if (debt > 0) { - repayBorrow(_wallet, cToken, debt); - uint collateral = ICToken(cToken).balanceOf(address(_wallet)); - if (collateral == 0) { - invokeWallet(address(_wallet), address(comptroller), 0, abi.encodeWithSignature("exitMarket(address)", address(cToken))); - } - } - } - emit LoanClosed(address(_wallet), _loanId); - } - - /** - * @dev Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - address cToken = compoundRegistry.getCToken(_collateral); - enterMarketIfNeeded(_wallet, cToken, address(comptroller)); - mint(_wallet, cToken, _collateral, _collateralAmount); - emit CollateralAdded(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - address cToken = compoundRegistry.getCToken(_collateral); - redeemUnderlying(_wallet, cToken, _collateralAmount); - exitMarketIfNeeded(_wallet, cToken, address(comptroller)); - emit CollateralRemoved(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - address dToken = compoundRegistry.getCToken(_debtToken); - enterMarketIfNeeded(_wallet, dToken, address(comptroller)); - borrow(_wallet, dToken, _debtAmount); - emit DebtAdded(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _debtToken The token to repay. - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - address dToken = compoundRegistry.getCToken(_debtToken); - repayBorrow(_wallet, dToken, _debtAmount); - exitMarketIfNeeded(_wallet, dToken, address(comptroller)); - emit DebtRemoved(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Gets information about the loan status on Compound. - * @param _wallet The target wallet. - * @return a status [0: no loan, 1: loan is safe, 2: loan is unsafe and can be liquidated] - * and a value (in ETH) representing the value that could still be borrowed when status = 1; or the value of the collateral - * that should be added to avoid liquidation when status = 2. - */ - function getLoan( - BaseWallet _wallet, - bytes32 /* _loanId */ - ) - external - view - returns (uint8 _status, uint256 _ethValue) - { - (uint error, uint liquidity, uint shortfall) = comptroller.getAccountLiquidity(address(_wallet)); - require(error == 0, "Compound: failed to get account liquidity"); - if (liquidity > 0) { - return (1, liquidity); - } - if (shortfall > 0) { - return (2, shortfall); - } - return (0,0); - } - - /* ********************************** Implementation of Invest ************************************* */ - - /** - * @dev Invest tokens for a given period. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _amount The amount of tokens to invest. - * @param _period The period over which the tokens may be locked in the investment (optional). - * @return The exact amount of tokens that have been invested. - */ - function addInvestment( - BaseWallet _wallet, - address _token, - uint256 _amount, - uint256 _period - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns (uint256 _invested) - { - address cToken = compoundRegistry.getCToken(_token); - mint(_wallet, cToken, _token, _amount); - _invested = _amount; - emit InvestmentAdded(address(_wallet), _token, _amount, _period); - } - - /** - * @dev Exit invested postions. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _fraction The fraction of invested tokens to exit in per 10000. - */ - function removeInvestment( - BaseWallet _wallet, - address _token, - uint256 _fraction - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_fraction <= 10000, "CompoundV2: invalid fraction value"); - address cToken = compoundRegistry.getCToken(_token); - uint shares = ICToken(cToken).balanceOf(address(_wallet)); - redeem(_wallet, cToken, shares.mul(_fraction).div(10000)); - emit InvestmentRemoved(address(_wallet), _token, _fraction); - } - - /** - * @dev Get the amount of investment in a given token. - * @param _wallet The target wallet. - * @param _token The token address. - * @return The value in tokens of the investment (including interests) and the time at which the investment can be removed. - */ - function getInvestment( - BaseWallet _wallet, - address _token - ) - external - view - returns (uint256 _tokenValue, uint256 _periodEnd) - { - address cToken = compoundRegistry.getCToken(_token); - uint amount = ICToken(cToken).balanceOf(address(_wallet)); - uint exchangeRateMantissa = ICToken(cToken).exchangeRateStored(); - _tokenValue = amount.mul(exchangeRateMantissa).div(10 ** 18); - _periodEnd = 0; - } - - /* ****************************************** Compound wrappers ******************************************* */ - - /** - * @dev Adds underlying tokens to a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _token The underlying token. - * @param _amount The amount of underlying token to add. - */ - function mint(BaseWallet _wallet, address _cToken, address _token, uint256 _amount) internal { - require(_cToken != address(0), "Compound: No market for target token"); - require(_amount > 0, "Compound: amount cannot be 0"); - if (_token == ETH_TOKEN_ADDRESS) { - invokeWallet(address(_wallet), _cToken, _amount, abi.encodeWithSignature("mint()")); - } else { - invokeWallet(address(_wallet), _token, 0, abi.encodeWithSignature("approve(address,uint256)", _cToken, _amount)); - invokeWallet(address(_wallet), _cToken, 0, abi.encodeWithSignature("mint(uint256)", _amount)); - } - } - - /** - * @dev Redeems underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of cToken to redeem. - */ - function redeem(BaseWallet _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "Compound: No market for target token"); - require(_amount > 0, "Compound: amount cannot be 0"); - invokeWallet(address(_wallet), _cToken, 0, abi.encodeWithSignature("redeem(uint256)", _amount)); - } - - /** - * @dev Redeems underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying token to redeem. - */ - function redeemUnderlying(BaseWallet _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "Compound: No market for target token"); - require(_amount > 0, "Compound: amount cannot be 0"); - invokeWallet(address(_wallet), _cToken, 0, abi.encodeWithSignature("redeemUnderlying(uint256)", _amount)); - } - - /** - * @dev Borrows underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying tokens to borrow. - */ - function borrow(BaseWallet _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "Compound: No market for target token"); - require(_amount > 0, "Compound: amount cannot be 0"); - invokeWallet(address(_wallet), _cToken, 0, abi.encodeWithSignature("borrow(uint256)", _amount)); - } - - /** - * @dev Repays some borrowed underlying tokens to a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying to repay. - */ - function repayBorrow(BaseWallet _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "Compound: No market for target token"); - require(_amount > 0, "Compound: amount cannot be 0"); - string memory symbol = ICToken(_cToken).symbol(); - if (keccak256(abi.encodePacked(symbol)) == keccak256(abi.encodePacked("cETH"))) { - invokeWallet(address(_wallet), _cToken, _amount, abi.encodeWithSignature("repayBorrow()")); - } else { - address token = ICToken(_cToken).underlying(); - invokeWallet(address(_wallet), token, 0, abi.encodeWithSignature("approve(address,uint256)", _cToken, _amount)); - invokeWallet(address(_wallet), _cToken, 0, abi.encodeWithSignature("repayBorrow(uint256)", _amount)); - } - } - - /** - * @dev Enters a cToken market if it was not entered before. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _comptroller The comptroller contract. - */ - function enterMarketIfNeeded(BaseWallet _wallet, address _cToken, address _comptroller) internal { - bool isEntered = IComptroller(_comptroller).checkMembership(address(_wallet), ICToken(_cToken)); - if (!isEntered) { - address[] memory market = new address[](1); - market[0] = _cToken; - invokeWallet(address(_wallet), _comptroller, 0, abi.encodeWithSignature("enterMarkets(address[])", market)); - } - } - - /** - * @dev Exits a cToken market if there is no more collateral and debt. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _comptroller The comptroller contract. - */ - function exitMarketIfNeeded(BaseWallet _wallet, address _cToken, address _comptroller) internal { - uint collateral = ICToken(_cToken).balanceOf(address(_wallet)); - uint debt = ICToken(_cToken).borrowBalanceStored(address(_wallet)); - if (collateral == 0 && debt == 0) { - invokeWallet(address(_wallet), _comptroller, 0, abi.encodeWithSignature("exitMarket(address)", _cToken)); - } - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/GuardianManager.sol b/contracts-legacy/v1.6.0/contracts/modules/GuardianManager.sol deleted file mode 100644 index 904d7e1bf..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/GuardianManager.sol +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/GuardianUtils.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; - -/** - * @title GuardianManager - * @dev Module to manage the guardians of wallets. - * Guardians are accounts (EOA or contracts) that are authorized to perform specific - * security operations on wallets such as toggle a safety lock, start a recovery procedure, - * or confirm transactions. Addition or revokation of guardians is initiated by the owner - * of a wallet and must be confirmed after a security period (e.g. 24 hours). - * The list of guardians for a wallet is stored on a saparate - * contract to facilitate its use by other modules. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract GuardianManager is BaseModule, RelayerModule { - - bytes32 constant NAME = "GuardianManager"; - - bytes4 constant internal CONFIRM_ADDITION_PREFIX = bytes4(keccak256("confirmGuardianAddition(address,address)")); - bytes4 constant internal CONFIRM_REVOKATION_PREFIX = bytes4(keccak256("confirmGuardianRevokation(address,address)")); - - struct GuardianManagerConfig { - // the time at which a guardian addition or revokation will be confirmable by the owner - mapping (bytes32 => uint256) pending; - } - - // the wallet specific storage - mapping (address => GuardianManagerConfig) internal configs; - // the security period - uint256 public securityPeriod; - // the security window - uint256 public securityWindow; - - // *************** Events *************************** // - - event GuardianAdditionRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); - event GuardianRevokationRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); - event GuardianAdditionCancelled(address indexed wallet, address indexed guardian); - event GuardianRevokationCancelled(address indexed wallet, address indexed guardian); - event GuardianAdded(address indexed wallet, address indexed guardian); - event GuardianRevoked(address indexed wallet, address indexed guardian); - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - uint256 _securityPeriod, - uint256 _securityWindow - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - } - - // *************** External Functions ********************* // - - /** - * @dev Lets the owner add a guardian to its wallet. - * The first guardian is added immediately. All following additions must be confirmed - * by calling the confirmGuardianAddition() method. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(BaseWallet _wallet, address _guardian) external onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - require(!isOwner(_wallet, _guardian), "GM: target guardian cannot be owner"); - require(!isGuardian(_wallet, _guardian), "GM: target is already a guardian"); - // Guardians must either be an EOA or a contract with an owner() - // method that returns an address with a 5000 gas stipend. - // Note that this test is not meant to be strict and can be bypassed by custom malicious contracts. - // solium-disable-next-line security/no-low-level-calls - (bool success,) = _guardian.call.gas(5000)(abi.encodeWithSignature("owner()")); - require(success, "GM: guardian must be EOA or implement owner()"); - if (guardianStorage.guardianCount(_wallet) == 0) { - guardianStorage.addGuardian(_wallet, _guardian); - emit GuardianAdded(address(_wallet), _guardian); - } else { - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "addition")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require( - config.pending[id] == 0 || now > config.pending[id] + securityWindow, - "GM: addition of target as guardian is already pending"); - config.pending[id] = now + securityPeriod; - emit GuardianAdditionRequested(address(_wallet), _guardian, now + securityPeriod); - } - } - - /** - * @dev Confirms the pending addition of a guardian to a wallet. - * The method must be called during the confirmation window and - * can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function confirmGuardianAddition(BaseWallet _wallet, address _guardian) public onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "addition")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require(config.pending[id] > 0, "GM: no pending addition as guardian for target"); - require(config.pending[id] < now, "GM: Too early to confirm guardian addition"); - require(now < config.pending[id] + securityWindow, "GM: Too late to confirm guardian addition"); - guardianStorage.addGuardian(_wallet, _guardian); - delete config.pending[id]; - emit GuardianAdded(address(_wallet), _guardian); - } - - /** - * @dev Lets the owner cancel a pending guardian addition. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function cancelGuardianAddition(BaseWallet _wallet, address _guardian) public onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "addition")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require(config.pending[id] > 0, "GM: no pending addition as guardian for target"); - delete config.pending[id]; - emit GuardianAdditionCancelled(address(_wallet), _guardian); - } - - /** - * @dev Lets the owner revoke a guardian from its wallet. - * Revokation must be confirmed by calling the confirmGuardianRevokation() method. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(BaseWallet _wallet, address _guardian) external onlyWalletOwner(_wallet) { - require(isGuardian(_wallet, _guardian), "GM: must be an existing guardian"); - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "revokation")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require( - config.pending[id] == 0 || now > config.pending[id] + securityWindow, - "GM: revokation of target as guardian is already pending"); // TODO need to allow if confirmation window passed - config.pending[id] = now + securityPeriod; - emit GuardianRevokationRequested(address(_wallet), _guardian, now + securityPeriod); - } - - /** - * @dev Confirms the pending revokation of a guardian to a wallet. - * The method must be called during the confirmation window and - * can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function confirmGuardianRevokation(BaseWallet _wallet, address _guardian) public { - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "revokation")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require(config.pending[id] > 0, "GM: no pending guardian revokation for target"); - require(config.pending[id] < now, "GM: Too early to confirm guardian revokation"); - require(now < config.pending[id] + securityWindow, "GM: Too late to confirm guardian revokation"); - guardianStorage.revokeGuardian(_wallet, _guardian); - delete config.pending[id]; - emit GuardianRevoked(address(_wallet), _guardian); - } - - /** - * @dev Lets the owner cancel a pending guardian revokation. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function cancelGuardianRevokation(BaseWallet _wallet, address _guardian) public onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(address(_wallet), _guardian, "revokation")); - GuardianManagerConfig storage config = configs[address(_wallet)]; - require(config.pending[id] > 0, "GM: no pending guardian revokation for target"); - delete config.pending[id]; - emit GuardianRevokationCancelled(address(_wallet), _guardian); - } - - /** - * @dev Checks if an address is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The address to check. - * @return true if the address if a guardian for the wallet. - */ - function isGuardian(BaseWallet _wallet, address _guardian) public view returns (bool _isGuardian) { - (_isGuardian, ) = GuardianUtils.isGuardian(guardianStorage.getGuardians(_wallet), _guardian); - } - - /** - * @dev Counts the number of active guardians for a wallet. - * @param _wallet The target wallet. - * @return the number of active guardians for a wallet. - */ - function guardianCount(BaseWallet _wallet) external view returns (uint256 _count) { - return guardianStorage.guardianCount(_wallet); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides to use the incremental nonce and save some gas - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 /* _signHash */) internal returns (bool) { - return checkAndUpdateNonce(_wallet, _nonce); - } - - function validateSignatures( - BaseWallet _wallet, - bytes memory /* _data */, - bytes32 _signHash, - bytes memory _signatures - ) - internal - view - returns (bool) - { - address signer = recoverSigner(_signHash, _signatures, 0); - return isOwner(_wallet, signer); // "GM: signer must be owner" - } - - function getRequiredSignatures(BaseWallet /* _wallet */, bytes memory _data) internal view returns (uint256) { - bytes4 methodId = functionPrefix(_data); - if (methodId == CONFIRM_ADDITION_PREFIX || methodId == CONFIRM_REVOKATION_PREFIX) { - return 0; - } - return 1; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/LockManager.sol b/contracts-legacy/v1.6.0/contracts/modules/LockManager.sol deleted file mode 100644 index 6c96dbd68..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/LockManager.sol +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; -import "./common/GuardianUtils.sol"; - -/** - * @title LockManager - * @dev Module to manage the state of a wallet's lock. - * Other modules can use the state of the lock to determine if their operations - * should be authorised or blocked. Only the guardians of a wallet can lock and unlock it. - * The lock automatically unlocks after a given period. The lock state is stored on a saparate - * contract to facilitate its use by other modules. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract LockManager is BaseModule, RelayerModule { - - bytes32 constant NAME = "LockManager"; - - // The lock period - uint256 public lockPeriod; - - // *************** Events *************************** // - - event Locked(address indexed wallet, uint64 releaseAfter); - event Unlocked(address indexed wallet); - - // *************** Modifiers ************************ // - - /** - * @dev Throws if the wallet is not locked. - */ - modifier onlyWhenLocked(BaseWallet _wallet) { - // solium-disable-next-line security/no-block-members - require(guardianStorage.isLocked(_wallet), "GD: wallet must be locked"); - _; - } - - /** - * @dev Throws if the caller is not a guardian for the wallet. - */ - modifier onlyGuardian(BaseWallet _wallet) { - (bool isGuardian, ) = GuardianUtils.isGuardian(guardianStorage.getGuardians(_wallet), msg.sender); - require(msg.sender == address(this) || isGuardian, "GD: wallet must be unlocked"); - _; - } - - // *************** Constructor ************************ // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - uint256 _lockPeriod - ) - BaseModule(_registry, _guardianStorage, NAME) public { - lockPeriod = _lockPeriod; - } - - // *************** External functions ************************ // - - /** - * @dev Lets a guardian lock a wallet. - * @param _wallet The target wallet. - */ - function lock(BaseWallet _wallet) external onlyGuardian(_wallet) onlyWhenUnlocked(_wallet) { - guardianStorage.setLock(_wallet, now + lockPeriod); - emit Locked(address(_wallet), uint64(now + lockPeriod)); - } - - /** - * @dev Lets a guardian unlock a locked wallet. - * @param _wallet The target wallet. - */ - function unlock(BaseWallet _wallet) external onlyGuardian(_wallet) onlyWhenLocked(_wallet) { - address locker = guardianStorage.getLocker(_wallet); - require(locker == address(this), "LM: cannot unlock a wallet that was locked by another module"); - guardianStorage.setLock(_wallet, 0); - emit Unlocked(address(_wallet)); - } - - /** - * @dev Returns the release time of a wallet lock or 0 if the wallet is unlocked. - * @param _wallet The target wallet. - * @return The epoch time at which the lock will release (in seconds). - */ - function getLock(BaseWallet _wallet) public view returns(uint64 _releaseAfter) { - uint256 lockEnd = guardianStorage.getLock(_wallet); - if (lockEnd > now) { - _releaseAfter = uint64(lockEnd); - } - } - - /** - * @dev Checks if a wallet is locked. - * @param _wallet The target wallet. - * @return true if the wallet is locked. - */ - function isLocked(BaseWallet _wallet) external view returns (bool _isLocked) { - return guardianStorage.isLocked(_wallet); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides to use the incremental nonce and save some gas - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 /* _signHash */) internal returns (bool) { - return checkAndUpdateNonce(_wallet, _nonce); - } - - function validateSignatures( - BaseWallet _wallet, - bytes memory /* _data */, - bytes32 _signHash, - bytes memory _signatures - ) - internal - view - returns (bool) - { - (bool isGuardian, ) = GuardianUtils.isGuardian(guardianStorage.getGuardians(_wallet), recoverSigner(_signHash, _signatures, 0)); - return isGuardian; // "LM: must be a guardian to lock or unlock" - } - - function getRequiredSignatures(BaseWallet /* _wallet */, bytes memory /* _data */) internal view returns (uint256) { - return 1; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/NftTransfer.sol b/contracts-legacy/v1.6.0/contracts/modules/NftTransfer.sol deleted file mode 100644 index 69efa0ad7..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/NftTransfer.sol +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; -import "./common/OnlyOwnerModule.sol"; - -/** - * @title NftTransfer - * @dev Module to transfer NFTs (ERC721), - * @author Olivier VDB - - */ -contract NftTransfer is BaseModule, RelayerModule, OnlyOwnerModule { - - bytes32 constant NAME = "NftTransfer"; - - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - bytes4 private constant ERC721_RECEIVED = 0x150b7a02; - - // The address of the CryptoKitties contract - address public ckAddress; - - // *************** Events *************************** // - - event NonFungibleTransfer(address indexed wallet, address indexed nftContract, uint256 indexed tokenId, address to, bytes data); - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - address _ckAddress - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - ckAddress = _ckAddress; - } - - // *************** External/Public Functions ********************* // - - /** - * @dev Inits the module for a wallet by setting up the onERC721Received - * static call redirection from the wallet to the module. - * @param _wallet The target wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - _wallet.enableStaticCall(address(this), ERC721_RECEIVED); - } - - /** - * @notice Handle the receipt of an NFT - * @dev An ERC721 smart contract calls this function on the recipient contract - * after a `safeTransfer`. If the recipient is a BaseWallet, the call to onERC721Received - * will be forwarded to the method onERC721Received of the present module. - * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - */ - function onERC721Received( - address /* operator */, - address /* from */, - uint256 /* tokenId */, - bytes calldata /* data*/ - ) - external - returns (bytes4) - { - return ERC721_RECEIVED; - } - - /** - * @dev lets the owner transfer NFTs from a wallet. - * @param _wallet The target wallet. - * @param _nftContract The ERC721 address. - * @param _to The recipient. - * @param _tokenId The NFT id - * @param _safe Whether to execute a safe transfer or not - * @param _data The data to pass with the transfer. - */ - function transferNFT( - BaseWallet _wallet, - address _nftContract, - address _to, - uint256 _tokenId, - bool _safe, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - bytes memory methodData; - if (_nftContract == ckAddress) { - methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _tokenId); - } else { - if (_safe) { - methodData = abi.encodeWithSignature( - "safeTransferFrom(address,address,uint256,bytes)", address(_wallet), _to, _tokenId, _data); - } else { - require(isERC721(_nftContract, _tokenId), "NT: Non-compliant NFT contract"); - methodData = abi.encodeWithSignature( - "transferFrom(address,address,uint256)", address(_wallet), _to, _tokenId); - } - } - invokeWallet(address(_wallet), _nftContract, 0, methodData); - emit NonFungibleTransfer(address(_wallet), _nftContract, _tokenId, _to, _data); - } - - // *************** Internal Functions ********************* // - - /** - * @dev Check whether a given contract complies with ERC721. - * @param _nftContract The contract to check. - * @param _tokenId The tokenId to use for the check. - * @return true if the contract is an ERC721, false otherwise. - */ - function isERC721(address _nftContract, uint256 _tokenId) internal returns (bool) { - // solium-disable-next-line security/no-low-level-calls - (bool success, bytes memory result) = _nftContract.call(abi.encodeWithSignature("supportsInterface(bytes4)", 0x80ac58cd)); - if (success && result[0] != 0x0) - return true; - - // solium-disable-next-line security/no-low-level-calls - (success, result) = _nftContract.call(abi.encodeWithSignature("supportsInterface(bytes4)", 0x6466353c)); - if (success && result[0] != 0x0) - return true; - - // solium-disable-next-line security/no-low-level-calls - (success,) = _nftContract.call(abi.encodeWithSignature("ownerOf(uint256)", _tokenId)); - return success; - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/RecoveryManager.sol b/contracts-legacy/v1.6.0/contracts/modules/RecoveryManager.sol deleted file mode 100644 index 9b510c2e3..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/RecoveryManager.sol +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModuleV2.sol"; -import "./storage/GuardianStorage.sol"; - -/** - * @title RecoveryManager - * @dev Module to manage the recovery of a wallet owner. - * Recovery is executed by a consensus of the wallet's guardians and takes - * 24 hours before it can be finalized. Once finalised the ownership of the wallet - * is transfered to a new address. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract RecoveryManager is BaseModule, RelayerModuleV2 { - - bytes32 constant NAME = "RecoveryManager"; - - bytes4 constant internal EXECUTE_RECOVERY_PREFIX = bytes4(keccak256("executeRecovery(address,address)")); - bytes4 constant internal FINALIZE_RECOVERY_PREFIX = bytes4(keccak256("finalizeRecovery(address)")); - bytes4 constant internal CANCEL_RECOVERY_PREFIX = bytes4(keccak256("cancelRecovery(address)")); - bytes4 constant internal TRANSFER_OWNERSHIP_PREFIX = bytes4(keccak256("transferOwnership(address,address)")); - - struct RecoveryConfig { - address recovery; - uint64 executeAfter; - uint32 guardianCount; - } - - // Wallet specific storage - mapping (address => RecoveryConfig) internal recoveryConfigs; - - // Recovery period - uint256 public recoveryPeriod; - // Lock period - uint256 public lockPeriod; - // Security period used for (non-recovery) ownership transfer - uint256 public securityPeriod; - // Security window used for (non-recovery) ownership transfer - uint256 public securityWindow; - // Location of the Guardian storage - GuardianStorage public guardianStorage; - - // *************** Events *************************** // - - event RecoveryExecuted(address indexed wallet, address indexed _recovery, uint64 executeAfter); - event RecoveryFinalized(address indexed wallet, address indexed _recovery); - event RecoveryCanceled(address indexed wallet, address indexed _recovery); - event OwnershipTransfered(address indexed wallet, address indexed _newOwner); - - // *************** Modifiers ************************ // - - /** - * @dev Throws if there is no ongoing recovery procedure. - */ - modifier onlyWhenRecovery(BaseWallet _wallet) { - require(recoveryConfigs[address(_wallet)].executeAfter > 0, "RM: there must be an ongoing recovery"); - _; - } - - /** - * @dev Throws if there is an ongoing recovery procedure. - */ - modifier notWhenRecovery(BaseWallet _wallet) { - require(recoveryConfigs[address(_wallet)].executeAfter == 0, "RM: there cannot be an ongoing recovery"); - _; - } - - // *************** Constructor ************************ // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - uint256 _recoveryPeriod, - uint256 _lockPeriod, - uint256 _securityPeriod, - uint256 _securityWindow - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - require(_lockPeriod >= _recoveryPeriod && _recoveryPeriod >= _securityPeriod + _securityWindow, "RM: insecure security periods"); - guardianStorage = _guardianStorage; - recoveryPeriod = _recoveryPeriod; - lockPeriod = _lockPeriod; - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - } - - // *************** External functions ************************ // - - /** - * @dev Lets the guardians start the execution of the recovery procedure. - * Once triggered the recovery is pending for the security period before it can - * be finalised. - * Must be confirmed by N guardians, where N = ((Nb Guardian + 1) / 2). - * @param _wallet The target wallet. - * @param _recovery The address to which ownership should be transferred. - */ - function executeRecovery(BaseWallet _wallet, address _recovery) external onlyExecute notWhenRecovery(_wallet) { - require(_recovery != address(0), "RM: recovery address cannot be null"); - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - config.recovery = _recovery; - config.executeAfter = uint64(now + recoveryPeriod); - config.guardianCount = uint32(guardianStorage.guardianCount(_wallet)); - guardianStorage.setLock(_wallet, now + lockPeriod); - emit RecoveryExecuted(address(_wallet), _recovery, config.executeAfter); - } - - /** - * @dev Finalizes an ongoing recovery procedure if the security period is over. - * The method is public and callable by anyone to enable orchestration. - * @param _wallet The target wallet. - */ - function finalizeRecovery(BaseWallet _wallet) external onlyWhenRecovery(_wallet) { - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - require(uint64(now) > config.executeAfter, "RM: the recovery period is not over yet"); - _wallet.setOwner(config.recovery); - emit RecoveryFinalized(address(_wallet), config.recovery); - guardianStorage.setLock(_wallet, 0); - delete recoveryConfigs[address(_wallet)]; - } - - /** - * @dev Lets the owner cancel an ongoing recovery procedure. - * Must be confirmed by N guardians, where N = ((Nb Guardian + 1) / 2) - 1. - * @param _wallet The target wallet. - */ - function cancelRecovery(BaseWallet _wallet) external onlyExecute onlyWhenRecovery(_wallet) { - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - emit RecoveryCanceled(address(_wallet), config.recovery); - guardianStorage.setLock(_wallet, 0); - delete recoveryConfigs[address(_wallet)]; - } - - /** - * @dev Lets the owner start the execution of the ownership transfer procedure. - * Once triggered the ownership transfer is pending for the security period before it can - * be finalised. - * @param _wallet The target wallet. - * @param _newOwner The address to which ownership should be transferred. - */ - function transferOwnership(BaseWallet _wallet, address _newOwner) external onlyExecute onlyWhenUnlocked(_wallet) { - require(_newOwner != address(0), "RM: new owner address cannot be null"); - _wallet.setOwner(_newOwner); - - emit OwnershipTransfered(address(_wallet), _newOwner); - } - - /** - * @dev Gets the details of the ongoing recovery procedure if any. - * @param _wallet The target wallet. - */ - function getRecovery(BaseWallet _wallet) public view returns(address _address, uint64 _executeAfter, uint32 _guardianCount) { - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - return (config.recovery, config.executeAfter, config.guardianCount); - } - - // *************** Implementation of RelayerModule methods ********************* // - - function validateSignatures( - BaseWallet _wallet, - bytes memory _data, - bytes32 _signHash, - bytes memory _signatures - ) - internal view returns (bool) - { - bytes4 functionSignature = functionPrefix(_data); - if (functionSignature == TRANSFER_OWNERSHIP_PREFIX) { - return validateSignatures(_wallet, _signHash, _signatures, OwnerSignature.Required); - } else if (functionSignature == EXECUTE_RECOVERY_PREFIX) { - return validateSignatures(_wallet, _signHash, _signatures, OwnerSignature.Disallowed); - } else if (functionSignature == CANCEL_RECOVERY_PREFIX) { - return validateSignatures(_wallet, _signHash, _signatures, OwnerSignature.Optional); - } - } - - function getRequiredSignatures(BaseWallet _wallet, bytes memory _data) public view returns (uint256) { - bytes4 methodId = functionPrefix(_data); - if (methodId == EXECUTE_RECOVERY_PREFIX) { - uint walletGuardians = guardianStorage.guardianCount(_wallet); - require(walletGuardians > 0, "RM: no guardians set on wallet"); - return SafeMath.ceil(walletGuardians, 2); - } - if (methodId == FINALIZE_RECOVERY_PREFIX) { - return 0; - } - if (methodId == CANCEL_RECOVERY_PREFIX) { - return SafeMath.ceil(recoveryConfigs[address(_wallet)].guardianCount + 1, 2); - } - if (methodId == TRANSFER_OWNERSHIP_PREFIX) { - uint majorityGuardians = SafeMath.ceil(guardianStorage.guardianCount(_wallet), 2); - return SafeMath.add(majorityGuardians, 1); - } - revert("RM: unknown method"); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/SimpleUpgrader.sol b/contracts-legacy/v1.6.0/contracts/modules/SimpleUpgrader.sol deleted file mode 100644 index 06dafbcb9..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/SimpleUpgrader.sol +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -import "./common/BaseModule.sol"; - -/** - * @title SimpleUpgrader - * @dev Temporary module used to add/remove other modules. - * @author Olivier VDB - , Julien Niset - - */ -contract SimpleUpgrader is BaseModule { - - bytes32 constant NAME = "SimpleUpgrader"; - address[] public toDisable; - address[] public toEnable; - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - address[] memory _toDisable, - address[] memory _toEnable - ) - BaseModule(_registry, GuardianStorage(0), NAME) - public - { - toDisable = _toDisable; - toEnable = _toEnable; - } - - // *************** External/Public Functions ********************* // - - /** - * @dev Perform the upgrade for a wallet. This method gets called - * when SimpleUpgrader is temporarily added as a module. - * @param _wallet The target wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - uint256 i = 0; - //add new modules - for (; i < toEnable.length; i++) { - BaseWallet(_wallet).authoriseModule(toEnable[i], true); - } - //remove old modules - for (i = 0; i < toDisable.length; i++) { - BaseWallet(_wallet).authoriseModule(toDisable[i], false); - } - // SimpleUpgrader did its job, we no longer need it as a module - BaseWallet(_wallet).authoriseModule(address(this), false); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/TokenExchanger.sol b/contracts-legacy/v1.6.0/contracts/modules/TokenExchanger.sol deleted file mode 100644 index 68c31bd1a..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/TokenExchanger.sol +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; -import "./common/OnlyOwnerModule.sol"; -import "../../lib/other/ERC20.sol"; -import "../../lib/other/KyberNetwork.sol"; - -/** - * @title TokenExchanger - * @dev Module to trade tokens (ETH or ERC20) using KyberNetworks. - * @author Julien Niset - - */ -contract TokenExchanger is BaseModule, RelayerModule, OnlyOwnerModule { - - bytes32 constant NAME = "TokenExchanger"; - - using SafeMath for uint256; - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // The address of the KyberNetwork proxy contract - address public kyber; - // The address of the contract collecting fees for Argent. - address public feeCollector; - // The Argent fee in 1-per-10000. - uint256 public feeRatio; - - event TokenExchanged(address indexed wallet, address srcToken, uint srcAmount, address destToken, uint destAmount); - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - address _kyber, - address _feeCollector, - uint _feeRatio - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - kyber = _kyber; - feeCollector = _feeCollector; - feeRatio = _feeRatio; - } - - /** - * @dev Lets the owner of the wallet execute a trade. - * @param _wallet The target wallet - * @param _srcToken The address of the source token. - * @param _srcAmount The amoutn of source token to trade. - * @param _destToken The address of the destination token. - * @param _maxDestAmount The maximum amount of destination token accepted for the trade. - * @param _minConversionRate The minimum accepted rate for the trade. - * @return The amount of destination tokens that have been received. - */ - function trade( - BaseWallet _wallet, - address _srcToken, - uint256 _srcAmount, - address _destToken, - uint256 _maxDestAmount, - uint256 _minConversionRate - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns(uint256) - { - bytes memory methodData; - require(_srcToken == ETH_TOKEN_ADDRESS || _destToken == ETH_TOKEN_ADDRESS, "TE: source or destination must be ETH"); - (uint256 destAmount, uint256 fee, ) = getExpectedTrade(_srcToken, _destToken, _srcAmount); - if (destAmount > _maxDestAmount) { - fee = fee.mul(_maxDestAmount).div(destAmount); - destAmount = _maxDestAmount; - } - if (_srcToken == ETH_TOKEN_ADDRESS) { - uint256 srcTradable = _srcAmount.sub(fee); - methodData = abi.encodeWithSignature( - "trade(address,uint256,address,address,uint256,uint256,address)", - _srcToken, - srcTradable, - _destToken, - address(_wallet), - _maxDestAmount, - _minConversionRate, - feeCollector - ); - invokeWallet(address(_wallet), kyber, srcTradable, methodData); - } else { - // approve kyber on erc20 - methodData = abi.encodeWithSignature("approve(address,uint256)", kyber, _srcAmount); - invokeWallet(address(_wallet), _srcToken, 0, methodData); - // transfer erc20 - methodData = abi.encodeWithSignature( - "trade(address,uint256,address,address,uint256,uint256,address)", - _srcToken, - _srcAmount, - _destToken, - address(_wallet), - _maxDestAmount, - _minConversionRate, - feeCollector - ); - invokeWallet(address(_wallet), kyber, 0, methodData); - } - - if (fee > 0) { - invokeWallet(address(_wallet), feeCollector, fee, ""); - } - emit TokenExchanged(address(_wallet), _srcToken, _srcAmount, _destToken, destAmount); - return destAmount; - } - - /** - * @dev Gets the expected terms of a trade. - * @param _srcToken The address of the source token. - * @param _destToken The address of the destination token. - * @param _srcAmount The amount of source token to trade. - * @return the amount of destination tokens to be received and the amount of ETH paid to Argent as fee. - */ - function getExpectedTrade( - address _srcToken, - address _destToken, - uint256 _srcAmount - ) - public - view - returns(uint256 _destAmount, uint256 _fee, uint256 _expectedRate) - { - if (_srcToken == ETH_TOKEN_ADDRESS) { - _fee = computeFee(_srcAmount); - (_expectedRate,) = KyberNetwork(kyber).getExpectedRate(ERC20(_srcToken), ERC20(_destToken), _srcAmount.sub(_fee)); - uint256 destDecimals = ERC20(_destToken).decimals(); - // destAmount = expectedRate * (_srcAmount - fee) / ETH_PRECISION * (DEST_PRECISION / SRC_PRECISION) - _destAmount = _expectedRate.mul(_srcAmount.sub(_fee)).div(10 ** (36-destDecimals)); - } else { - (_expectedRate,) = KyberNetwork(kyber).getExpectedRate(ERC20(_srcToken), ERC20(_destToken), _srcAmount); - uint256 srcDecimals = ERC20(_srcToken).decimals(); - // destAmount = expectedRate * _srcAmount / ETH_PRECISION * (DEST_PRECISION / SRC_PRECISION) - fee - _destAmount = _expectedRate.mul(_srcAmount).div(10 ** srcDecimals); - _fee = computeFee(_destAmount); - _destAmount -= _fee; - } - } - - /** - * @dev Computes the Argent fee based on the amount of source tokens in ETH. - * @param _srcAmount The amount of source token to trade in ETH. - * @return the fee paid to Argent. - */ - function computeFee(uint256 _srcAmount) internal view returns (uint256 fee) { - fee = (_srcAmount * feeRatio) / 10000; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/TransferManager.sol b/contracts-legacy/v1.6.0/contracts/modules/TransferManager.sol deleted file mode 100644 index 286bd73cf..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/TransferManager.sol +++ /dev/null @@ -1,494 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../wallet/BaseWallet.sol"; -import "./common/BaseModule.sol"; -import "./common/RelayerModule.sol"; -import "./common/OnlyOwnerModule.sol"; -import "./common/BaseTransfer.sol"; -import "./common/LimitManager.sol"; -import "./storage/TransferStorage.sol"; -import "../infrastructure/TokenPriceProvider.sol"; -import "../../lib/other/ERC20.sol"; - -/** - * @title TransferManager - * @dev Module to transfer and approve tokens (ETH or ERC20) or data (contract call) based on a security context (daily limit, whitelist, etc). - * This module is the V2 of TokenTransfer. - * @author Julien Niset - - */ -contract TransferManager is BaseModule, RelayerModule, OnlyOwnerModule, BaseTransfer, LimitManager { - - bytes32 constant NAME = "TransferManager"; - - bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES = bytes4(keccak256("isValidSignature(bytes,bytes)")); - bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES32 = bytes4(keccak256("isValidSignature(bytes32,bytes)")); - - enum ActionType { Transfer } - - using SafeMath for uint256; - - struct TokenManagerConfig { - // Mapping between pending action hash and their timestamp - mapping (bytes32 => uint256) pendingActions; - } - - // wallet specific storage - mapping (address => TokenManagerConfig) internal configs; - - // The security period - uint256 public securityPeriod; - // The execution window - uint256 public securityWindow; - // The Token storage - TransferStorage public transferStorage; - // The Token price provider - TokenPriceProvider public priceProvider; - // The previous limit manager needed to migrate the limits - LimitManager public oldLimitManager; - - // *************** Events *************************** // - - event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter); - event RemovedFromWhitelist(address indexed wallet, address indexed target); - event PendingTransferCreated(address indexed wallet, bytes32 indexed id, uint256 indexed executeAfter, - address token, address to, uint256 amount, bytes data); - event PendingTransferExecuted(address indexed wallet, bytes32 indexed id); - event PendingTransferCanceled(address indexed wallet, bytes32 indexed id); - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - TransferStorage _transferStorage, - GuardianStorage _guardianStorage, - address _priceProvider, - uint256 _securityPeriod, - uint256 _securityWindow, - uint256 _defaultLimit, - LimitManager _oldLimitManager - ) - BaseModule(_registry, _guardianStorage, NAME) - LimitManager(_defaultLimit) - public - { - transferStorage = _transferStorage; - priceProvider = TokenPriceProvider(_priceProvider); - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - oldLimitManager = _oldLimitManager; - } - - /** - * @dev Inits the module for a wallet by setting up the isValidSignature (EIP 1271) - * static call redirection from the wallet to the module and copying all the parameters - * of the daily limit from the previous implementation of the LimitManager module. - * @param _wallet The target wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - - // setup static calls - _wallet.enableStaticCall(address(this), ERC1271_ISVALIDSIGNATURE_BYTES); - _wallet.enableStaticCall(address(this), ERC1271_ISVALIDSIGNATURE_BYTES32); - - // setup default limit for new deployment - if (address(oldLimitManager) == address(0)) { - super.init(_wallet); - return; - } - // get limit from previous LimitManager - uint256 current = oldLimitManager.getCurrentLimit(_wallet); - (uint256 pending, uint64 changeAfter) = oldLimitManager.getPendingLimit(_wallet); - // setup default limit for new wallets - if (current == 0 && changeAfter == 0) { - super.init(_wallet); - return; - } - // migrate existing limit for existing wallets - if (current == pending) { - limits[address(_wallet)].limit.current = uint128(current); - } else { - limits[address(_wallet)].limit = Limit(uint128(current), uint128(pending), changeAfter); - } - // migrate daily pending if we are within a rolling period - (uint256 unspent, uint64 periodEnd) = oldLimitManager.getDailyUnspent(_wallet); - // solium-disable-next-line security/no-block-members - if (periodEnd > now) { - limits[address(_wallet)].dailySpent = DailySpent(uint128(current.sub(unspent)), periodEnd); - } - } - - // *************** External/Public Functions ********************* // - - /** - * @dev lets the owner transfer tokens (ETH or ERC20) from a wallet. - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _to The destination address - * @param _amount The amoutn of token to transfer - * @param _data The data for the transaction - */ - function transferToken( - BaseWallet _wallet, - address _token, - address _to, - uint256 _amount, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _to)) { - // transfer to whitelist - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - uint256 etherAmount = (_token == ETH_TOKEN) ? _amount : priceProvider.getEtherValue(_amount, _token); - if (checkAndUpdateDailySpent(_wallet, etherAmount)) { - // transfer under the limit - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - // transfer above the limit - (bytes32 id, uint256 executeAfter) = addPendingAction(ActionType.Transfer, _wallet, _token, _to, _amount, _data); - emit PendingTransferCreated(address(_wallet), id, executeAfter, _token, _to, _amount, _data); - } - } - } - - /** - * @dev lets the owner approve an allowance of ERC20 tokens for a spender (dApp). - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _spender The address of the spender - * @param _amount The amount of tokens to approve - */ - function approveToken( - BaseWallet _wallet, - address _token, - address _spender, - uint256 _amount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _spender)) { - // approve to whitelist - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // get current alowance - uint256 currentAllowance = ERC20(_token).allowance(address(_wallet), _spender); - if (_amount <= currentAllowance) { - // approve if we reduce the allowance - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // check if delta is under the limit - uint delta = _amount - currentAllowance; - uint256 deltaInEth = priceProvider.getEtherValue(delta, _token); - require(checkAndUpdateDailySpent(_wallet, deltaInEth), "TM: Approve above daily limit"); - // approve if under the limit - doApproveToken(_wallet, _token, _spender, _amount); - } - } - } - - /** - * @dev lets the owner call a contract. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - * @param _value The amount of ETH to transfer as part of call - * @param _data The encoded method data - */ - function callContract( - BaseWallet _wallet, - address _contract, - uint256 _value, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Make sure we don't call a module, the wallet itself, or a supported ERC20 - authoriseContractCall(_wallet, _contract); - - if (isWhitelisted(_wallet, _contract)) { - // call to whitelist - doCallContract(_wallet, _contract, _value, _data); - } else { - require(checkAndUpdateDailySpent(_wallet, _value), "TM: Call contract above daily limit"); - // call under the limit - doCallContract(_wallet, _contract, _value, _data); - } - } - - /** - * @dev lets the owner do an ERC20 approve followed by a call to a contract. - * We assume that the contract will pull the tokens and does not require ETH. - * @param _wallet The target wallet. - * @param _token The token to approve. - * @param _spender The address to approve. - * @param _amount The amount of ERC20 tokens to approve. - * @param _contract The address of the contract. - * @param _data The encoded method data - */ - function approveTokenAndCallContract( - BaseWallet _wallet, - address _token, - address _spender, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Make sure we don't call a module, the wallet itself, or a supported ERC20 - authoriseContractCall(_wallet, _contract); - - if (!isWhitelisted(_wallet, _spender)) { - // check if the amount is under the daily limit - // check the entire amount because the currently approved amount will be restored and should still count towards the daily limit - uint256 valueInEth = priceProvider.getEtherValue(_amount, _token); - require(checkAndUpdateDailySpent(_wallet, valueInEth), "TM: Approve above daily limit"); - } - - doApproveTokenAndCallContract(_wallet, _token, _spender, _amount, _contract, _data); - } - - /** - * @dev Adds an address to the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to add. - */ - function addToWhitelist( - BaseWallet _wallet, - address _target - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(!isWhitelisted(_wallet, _target), "TT: target already whitelisted"); - // solium-disable-next-line security/no-block-members - uint256 whitelistAfter = now.add(securityPeriod); - transferStorage.setWhitelist(_wallet, _target, whitelistAfter); - emit AddedToWhitelist(address(_wallet), _target, uint64(whitelistAfter)); - } - - /** - * @dev Removes an address from the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to remove. - */ - function removeFromWhitelist( - BaseWallet _wallet, - address _target - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(isWhitelisted(_wallet, _target), "TT: target not whitelisted"); - transferStorage.setWhitelist(_wallet, _target, 0); - emit RemovedFromWhitelist(address(_wallet), _target); - } - - /** - * @dev Executes a pending transfer for a wallet. - * The method can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _token The token of the pending transfer. - * @param _to The destination address of the pending transfer. - * @param _amount The amount of token to transfer of the pending transfer. - * @param _data The data associated to the pending transfer. - * @param _block The block at which the pending transfer was created. - */ - function executePendingTransfer( - BaseWallet _wallet, - address _token, - address _to, - uint _amount, - bytes calldata _data, - uint _block - ) - external - onlyWhenUnlocked(_wallet) - { - bytes32 id = keccak256(abi.encodePacked(ActionType.Transfer, _token, _to, _amount, _data, _block)); - uint executeAfter = configs[address(_wallet)].pendingActions[id]; - require(executeAfter > 0, "TT: unknown pending transfer"); - uint executeBefore = executeAfter.add(securityWindow); - // solium-disable-next-line security/no-block-members - require(executeAfter <= now && now <= executeBefore, "TT: transfer outside of the execution window"); - delete configs[address(_wallet)].pendingActions[id]; - doTransfer(_wallet, _token, _to, _amount, _data); - emit PendingTransferExecuted(address(_wallet), id); - } - - function cancelPendingTransfer( - BaseWallet _wallet, - bytes32 _id - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(configs[address(_wallet)].pendingActions[_id] > 0, "TT: unknown pending action"); - delete configs[address(_wallet)].pendingActions[_id]; - emit PendingTransferCanceled(address(_wallet), _id); - } - - /** - * @dev Lets the owner of a wallet change its daily limit. - * The limit is expressed in ETH. Changes to the limit take 24 hours. - * @param _wallet The target wallet. - * @param _newLimit The new limit. - */ - function changeLimit(BaseWallet _wallet, uint256 _newLimit) external onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - changeLimit(_wallet, _newLimit, securityPeriod); - } - - /** - * @dev Convenience method to disable the limit - * The limit is disabled by setting it to an arbitrary large value. - * @param _wallet The target wallet. - */ - function disableLimit(BaseWallet _wallet) external onlyWalletOwner(_wallet) onlyWhenUnlocked(_wallet) { - disableLimit(_wallet, securityPeriod); - } - - /** - * @dev Checks if an address is whitelisted for a wallet. - * @param _wallet The target wallet. - * @param _target The address. - * @return true if the address is whitelisted. - */ - function isWhitelisted(BaseWallet _wallet, address _target) public view returns (bool _isWhitelisted) { - uint whitelistAfter = transferStorage.getWhitelist(_wallet, _target); - // solium-disable-next-line security/no-block-members - return whitelistAfter > 0 && whitelistAfter < now; - } - - /** - * @dev Gets the info of a pending transfer for a wallet. - * @param _wallet The target wallet. - * @param _id The pending transfer ID. - * @return the epoch time at which the pending transfer can be executed. - */ - function getPendingTransfer(BaseWallet _wallet, bytes32 _id) external view returns (uint64 _executeAfter) { - _executeAfter = uint64(configs[address(_wallet)].pendingActions[_id]); - } - - /** - * @dev Implementation of EIP 1271. - * Should return whether the signature provided is valid for the provided data. - * @param _data Arbitrary length data signed on the behalf of address(this) - * @param _signature Signature byte array associated with _data - */ - function isValidSignature(bytes calldata _data, bytes calldata _signature) external view returns (bytes4) { - bytes32 msgHash = keccak256(abi.encodePacked(_data)); - isValidSignature(msgHash, _signature); - return ERC1271_ISVALIDSIGNATURE_BYTES; - } - - /** - * @dev Implementation of EIP 1271. - * Should return whether the signature provided is valid for the provided data. - * @param _msgHash Hash of a message signed on the behalf of address(this) - * @param _signature Signature byte array associated with _msgHash - */ - function isValidSignature(bytes32 _msgHash, bytes memory _signature) public view returns (bytes4) { - require(_signature.length == 65, "TM: invalid signature length"); - address signer = recoverSigner(_msgHash, _signature, 0); - require(isOwner(BaseWallet(msg.sender), signer), "TM: Invalid signer"); - return ERC1271_ISVALIDSIGNATURE_BYTES32; - } - - // *************** Internal Functions ********************* // - - /** - * @dev Creates a new pending action for a wallet. - * @param _action The target action. - * @param _wallet The target wallet. - * @param _token The target token for the action. - * @param _to The recipient of the action. - * @param _amount The amount of token associated to the action. - * @param _data The data associated to the action. - * @return the identifier for the new pending action and the time when the action can be executed - */ - function addPendingAction( - ActionType _action, - BaseWallet _wallet, - address _token, - address _to, - uint _amount, - bytes memory _data - ) - internal - returns (bytes32 id, uint256 executeAfter) - { - id = keccak256(abi.encodePacked(_action, _token, _to, _amount, _data, block.number)); - require(configs[address(_wallet)].pendingActions[id] == 0, "TM: duplicate pending action"); - // solium-disable-next-line security/no-block-members - executeAfter = now.add(securityPeriod); - configs[address(_wallet)].pendingActions[id] = executeAfter; - } - - /** - * @dev Make sure a contract call is not trying to call a module, the wallet itself, or a supported ERC20. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - */ - function authoriseContractCall(BaseWallet _wallet, address _contract) internal view { - require( - _contract != address(_wallet) && // not the wallet itself - !_wallet.authorised(_contract) && // not an authorised module - (priceProvider.cachedPrices(_contract) == 0 || isLimitDisabled(_wallet)), // not an ERC20 listed in the provider (or limit disabled) - "TM: Forbidden contract"); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides refund to add the refund in the daily limit. - function refund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _gasLimit, uint _signatures, address _relayer) internal { - // 21000 (transaction) + 7620 (execution of refund) + 7324 (execution of updateDailySpent) + 672 to log the event + _gasUsed - uint256 amount = 36616 + _gasUsed; - if (_gasPrice > 0 && _signatures > 0 && amount <= _gasLimit) { - if (_gasPrice > tx.gasprice) { - amount = amount * tx.gasprice; - } else { - amount = amount * _gasPrice; - } - checkAndUpdateDailySpent(_wallet, amount); - invokeWallet(address(_wallet), _relayer, amount, EMPTY_BYTES); - } - } - - // Overrides verifyRefund to add the refund in the daily limit. - function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) { - if (_gasPrice > 0 && _signatures > 0 && ( - address(_wallet).balance < _gasUsed * _gasPrice || - isWithinDailyLimit(_wallet, getCurrentLimit(_wallet), _gasUsed * _gasPrice) == false || - _wallet.authorised(address(this)) == false - )) - { - return false; - } - return true; - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/BaseModule.sol b/contracts-legacy/v1.6.0/contracts/modules/common/BaseModule.sol deleted file mode 100644 index 02012bb43..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/BaseModule.sol +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "../../infrastructure/ModuleRegistry.sol"; -import "../storage/GuardianStorage.sol"; -import "./Module.sol"; -import "../../../lib/other/ERC20.sol"; -import "../../../lib/utils/SafeMath.sol"; - -/** - * @title BaseModule - * @dev Basic module that contains some methods common to all modules. - * @author Julien Niset - - */ -contract BaseModule is Module { - - // Empty calldata - bytes constant internal EMPTY_BYTES = ""; - - // The adddress of the module registry. - ModuleRegistry internal registry; - // The address of the Guardian storage - GuardianStorage internal guardianStorage; - - /** - * @dev Throws if the wallet is locked. - */ - modifier onlyWhenUnlocked(BaseWallet _wallet) { - // solium-disable-next-line security/no-block-members - require(!guardianStorage.isLocked(_wallet), "BM: wallet must be unlocked"); - _; - } - - event ModuleCreated(bytes32 name); - event ModuleInitialised(address wallet); - - constructor(ModuleRegistry _registry, GuardianStorage _guardianStorage, bytes32 _name) public { - registry = _registry; - guardianStorage = _guardianStorage; - emit ModuleCreated(_name); - } - - /** - * @dev Throws if the sender is not the target wallet of the call. - */ - modifier onlyWallet(BaseWallet _wallet) { - require(msg.sender == address(_wallet), "BM: caller must be wallet"); - _; - } - - /** - * @dev Throws if the sender is not the owner of the target wallet or the module itself. - */ - modifier onlyWalletOwner(BaseWallet _wallet) { - require(msg.sender == address(this) || isOwner(_wallet, msg.sender), "BM: must be an owner for the wallet"); - _; - } - - /** - * @dev Throws if the sender is not the owner of the target wallet. - */ - modifier strictOnlyWalletOwner(BaseWallet _wallet) { - require(isOwner(_wallet, msg.sender), "BM: msg.sender must be an owner for the wallet"); - _; - } - - /** - * @dev Inits the module for a wallet by logging an event. - * The method can only be called by the wallet itself. - * @param _wallet The wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - emit ModuleInitialised(address(_wallet)); - } - - /** - * @dev Adds a module to a wallet. First checks that the module is registered. - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external strictOnlyWalletOwner(_wallet) { - require(registry.isRegisteredModule(address(_module)), "BM: module is not registered"); - _wallet.authoriseModule(address(_module), true); - } - - /** - * @dev Utility method enbaling anyone to recover ERC20 token sent to the - * module by mistake and transfer them to the Module Registry. - * @param _token The token to recover. - */ - function recoverToken(address _token) external { - uint total = ERC20(_token).balanceOf(address(this)); - ERC20(_token).transfer(address(registry), total); - } - - /** - * @dev Helper method to check if an address is the owner of a target wallet. - * @param _wallet The target wallet. - * @param _addr The address. - */ - function isOwner(BaseWallet _wallet, address _addr) internal view returns (bool) { - return _wallet.owner() == _addr; - } - - /** - * @dev Helper method to invoke a wallet. - * @param _wallet The target wallet. - * @param _to The target address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { - bool success; - // solium-disable-next-line security/no-call-value - (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data)); - if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values - (_res) = abi.decode(_res, (bytes)); - } else if (_res.length > 0) { - // solium-disable-next-line security/no-inline-assembly - assembly { - returndatacopy(0, 0, returndatasize) - revert(0, returndatasize) - } - } else if (!success) { - revert("BM: wallet invoke reverted"); - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/BaseTransfer.sol b/contracts-legacy/v1.6.0/contracts/modules/common/BaseTransfer.sol deleted file mode 100644 index 3ed726003..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/BaseTransfer.sol +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -import "./BaseModule.sol"; - -/** - * @title BaseTransfer - * @dev Module containing internal methods to execute or approve transfers - * @author Olivier VDB - - */ -contract BaseTransfer is BaseModule { - - // Mock token address for ETH - address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // *************** Events *************************** // - - event Transfer(address indexed wallet, address indexed token, uint256 indexed amount, address to, bytes data); - event Approved(address indexed wallet, address indexed token, uint256 amount, address spender); - event CalledContract(address indexed wallet, address indexed to, uint256 amount, bytes data); - event ApprovedAndCalledContract( - address indexed wallet, - address indexed to, - address spender, - address indexed token, - uint256 amountApproved, - uint256 amountSpent, - bytes data - ); - // *************** Internal Functions ********************* // - - /** - * @dev Helper method to transfer ETH or ERC20 for a wallet. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _to The recipient. - * @param _value The amount of ETH to transfer - * @param _data The data to *log* with the transfer. - */ - function doTransfer(BaseWallet _wallet, address _token, address _to, uint256 _value, bytes memory _data) internal { - if (_token == ETH_TOKEN) { - invokeWallet(address(_wallet), _to, _value, EMPTY_BYTES); - } else { - bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _value); - invokeWallet(address(_wallet), _token, 0, methodData); - } - emit Transfer(address(_wallet), _token, _value, _to, _data); - } - - /** - * @dev Helper method to approve spending the ERC20 of a wallet. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _spender The spender address. - * @param _value The amount of token to transfer. - */ - function doApproveToken(BaseWallet _wallet, address _token, address _spender, uint256 _value) internal { - bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _spender, _value); - invokeWallet(address(_wallet), _token, 0, methodData); - emit Approved(address(_wallet), _token, _value, _spender); - } - - /** - * @dev Helper method to call an external contract. - * @param _wallet The target wallet. - * @param _contract The contract address. - * @param _value The ETH value to transfer. - * @param _data The method data. - */ - function doCallContract(BaseWallet _wallet, address _contract, uint256 _value, bytes memory _data) internal { - invokeWallet(address(_wallet), _contract, _value, _data); - emit CalledContract(address(_wallet), _contract, _value, _data); - } - - /** - * @dev Helper method to approve a certain amount of token and call an external contract. - * The address that spends the _token and the address that is called with _data can be different. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _spender The spender address. - * @param _amount The amount of tokens to transfer. - * @param _contract The contract address. - * @param _data The method data. - */ - function doApproveTokenAndCallContract( - BaseWallet _wallet, - address _token, - address _spender, - uint256 _amount, - address _contract, - bytes memory _data - ) - internal - { - uint256 existingAllowance = ERC20(_token).allowance(address(_wallet), _spender); - uint256 totalAllowance = SafeMath.add(existingAllowance, _amount); - // Approve the desired amount plus existing amount. This logic allows for potential gas saving later - // when restoring the original approved amount, in cases where the _spender uses the exact approved _amount. - bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _spender, totalAllowance); - - invokeWallet(address(_wallet), _token, 0, methodData); - invokeWallet(address(_wallet), _contract, 0, _data); - - // Calculate the approved amount that was spent after the call - uint256 unusedAllowance = ERC20(_token).allowance(address(_wallet), _spender); - uint256 usedAllowance = SafeMath.sub(totalAllowance, unusedAllowance); - // Ensure the amount spent does not exceed the amount approved for this call - require(usedAllowance <= _amount, "BT: insufficient amount for call"); - - if (unusedAllowance != existingAllowance) { - // Restore the original allowance amount if the amount spent was different (can be lower). - methodData = abi.encodeWithSignature("approve(address,uint256)", _spender, existingAllowance); - invokeWallet(address(_wallet), _token, 0, methodData); - } - - emit ApprovedAndCalledContract( - address(_wallet), - _contract, - _spender, - _token, - _amount, - usedAllowance, - _data); - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/GuardianUtils.sol b/contracts-legacy/v1.6.0/contracts/modules/common/GuardianUtils.sol deleted file mode 100644 index 0cfc0e77f..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/GuardianUtils.sol +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -library GuardianUtils { - - /** - * @dev Checks if an address is an account guardian or an account authorised to sign on behalf of a smart-contract guardian - * given a list of guardians. - * @param _guardians the list of guardians - * @param _guardian the address to test - * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found. - */ - function isGuardian(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) { - if (_guardians.length == 0 || _guardian == address(0)) { - return (false, _guardians); - } - bool isFound = false; - address[] memory updatedGuardians = new address[](_guardians.length - 1); - uint256 index = 0; - for (uint256 i = 0; i < _guardians.length; i++) { - if (!isFound) { - // check if _guardian is an account guardian - if (_guardian == _guardians[i]) { - isFound = true; - continue; - } - // check if _guardian is the owner of a smart contract guardian - if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) { - isFound = true; - continue; - } - } - if (index < updatedGuardians.length) { - updatedGuardians[index] = _guardians[i]; - index++; - } - } - return isFound ? (true, updatedGuardians) : (false, _guardians); - } - - /** - * @dev Checks if an address is a contract. - * @param _addr The address. - */ - function isContract(address _addr) internal view returns (bool) { - uint32 size; - // solium-disable-next-line security/no-inline-assembly - assembly { - size := extcodesize(_addr) - } - return (size > 0); - } - - /** - * @dev Checks if an address is the owner of a guardian contract. - * The method does not revert if the call to the owner() method consumes more then 5000 gas. - * @param _guardian The guardian contract - * @param _owner The owner to verify. - */ - function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) { - address owner = address(0); - bytes4 sig = bytes4(keccak256("owner()")); - // solium-disable-next-line security/no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr,sig) - let result := staticcall(5000, _guardian, ptr, 0x20, ptr, 0x20) - if eq(result, 1) { - owner := mload(ptr) - } - } - return owner == _owner; - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/LimitManager.sol b/contracts-legacy/v1.6.0/contracts/modules/common/LimitManager.sol deleted file mode 100644 index 4e494b8d1..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/LimitManager.sol +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "./BaseModule.sol"; - -/** - * @title LimitManager - * @dev Module to manage a daily spending limit - * @author Julien Niset - - */ -contract LimitManager is BaseModule { - - // large limit when the limit can be considered disabled - uint128 constant private LIMIT_DISABLED = uint128(-1); // 3.40282366920938463463374607431768211455e+38 - - using SafeMath for uint256; - - struct LimitManagerConfig { - // The daily limit - Limit limit; - // The current usage - DailySpent dailySpent; - } - - struct Limit { - // the current limit - uint128 current; - // the pending limit if any - uint128 pending; - // when the pending limit becomes the current limit - uint64 changeAfter; - } - - struct DailySpent { - // The amount already spent during the current period - uint128 alreadySpent; - // The end of the current period - uint64 periodEnd; - } - - // wallet specific storage - mapping (address => LimitManagerConfig) internal limits; - // The default limit - uint256 public defaultLimit; - - // *************** Events *************************** // - - event LimitChanged(address indexed wallet, uint indexed newLimit, uint64 indexed startAfter); - - // *************** Constructor ********************** // - - constructor(uint256 _defaultLimit) public { - defaultLimit = _defaultLimit; - } - - // *************** External/Public Functions ********************* // - - /** - * @dev Inits the module for a wallet by setting the limit to the default value. - * @param _wallet The target wallet. - */ - function init(BaseWallet _wallet) public onlyWallet(_wallet) { - Limit storage limit = limits[address(_wallet)].limit; - if (limit.current == 0 && limit.changeAfter == 0) { - limit.current = uint128(defaultLimit); - } - } - - // *************** Internal Functions ********************* // - - /** - * @dev Changes the daily limit. - * The limit is expressed in ETH and the change is pending for the security period. - * @param _wallet The target wallet. - * @param _newLimit The new limit. - * @param _securityPeriod The security period. - */ - function changeLimit(BaseWallet _wallet, uint256 _newLimit, uint256 _securityPeriod) internal { - Limit storage limit = limits[address(_wallet)].limit; - // solium-disable-next-line security/no-block-members - uint128 current = (limit.changeAfter > 0 && limit.changeAfter < now) ? limit.pending : limit.current; - limit.current = current; - limit.pending = uint128(_newLimit); - // solium-disable-next-line security/no-block-members - limit.changeAfter = uint64(now.add(_securityPeriod)); - // solium-disable-next-line security/no-block-members - emit LimitChanged(address(_wallet), _newLimit, uint64(now.add(_securityPeriod))); - } - - /** - * @dev Disable the daily limit. - * The change is pending for the security period. - * @param _wallet The target wallet. - * @param _securityPeriod The security period. - */ - function disableLimit(BaseWallet _wallet, uint256 _securityPeriod) internal { - changeLimit(_wallet, LIMIT_DISABLED, _securityPeriod); - } - - /** - * @dev Gets the current daily limit for a wallet. - * @param _wallet The target wallet. - * @return the current limit expressed in ETH. - */ - function getCurrentLimit(BaseWallet _wallet) public view returns (uint256 _currentLimit) { - Limit storage limit = limits[address(_wallet)].limit; - _currentLimit = uint256(currentLimit(limit.current, limit.pending, limit.changeAfter)); - } - - /** - * @dev Returns whether the daily limit is disabled for a wallet. - * @param _wallet The target wallet. - * @return true if the daily limit is disabled, false otherwise. - */ - function isLimitDisabled(BaseWallet _wallet) public view returns (bool _limitDisabled) { - uint256 currentLimit = getCurrentLimit(_wallet); - _limitDisabled = currentLimit == LIMIT_DISABLED; - } - - /** - * @dev Gets a pending limit for a wallet if any. - * @param _wallet The target wallet. - * @return the pending limit (in ETH) and the time at chich it will become effective. - */ - function getPendingLimit(BaseWallet _wallet) external view returns (uint256 _pendingLimit, uint64 _changeAfter) { - Limit storage limit = limits[address(_wallet)].limit; - // solium-disable-next-line security/no-block-members - return ((now < limit.changeAfter)? (uint256(limit.pending), limit.changeAfter) : (0,0)); - } - - /** - * @dev Gets the amount of tokens that has not yet been spent during the current period. - * @param _wallet The target wallet. - * @return the amount of tokens (in ETH) that has not been spent yet and the end of the period. - */ - function getDailyUnspent(BaseWallet _wallet) external view returns (uint256 _unspent, uint64 _periodEnd) { - uint256 limit = getCurrentLimit(_wallet); - DailySpent storage expense = limits[address(_wallet)].dailySpent; - // solium-disable-next-line security/no-block-members - if (now > expense.periodEnd) { - _unspent = limit; - // solium-disable-next-line security/no-block-members - _periodEnd = uint64(now + 24 hours); - } else { - _periodEnd = expense.periodEnd; - if (expense.alreadySpent < limit) { - _unspent = limit - expense.alreadySpent; - } - } - } - - /** - * @dev Helper method to check if a transfer is within the limit. - * If yes the daily unspent for the current period is updated. - * @param _wallet The target wallet. - * @param _amount The amount for the transfer - */ - function checkAndUpdateDailySpent(BaseWallet _wallet, uint _amount) internal returns (bool) { - if (_amount == 0) - return true; - Limit storage limit = limits[address(_wallet)].limit; - uint128 current = currentLimit(limit.current, limit.pending, limit.changeAfter); - if (isWithinDailyLimit(_wallet, current, _amount)) { - updateDailySpent(_wallet, current, _amount); - return true; - } - return false; - } - - /** - * @dev Helper method to update the daily spent for the current period. - * @param _wallet The target wallet. - * @param _limit The current limit for the wallet. - * @param _amount The amount to add to the daily spent. - */ - function updateDailySpent(BaseWallet _wallet, uint128 _limit, uint _amount) internal { - if (_limit != LIMIT_DISABLED) { - DailySpent storage expense = limits[address(_wallet)].dailySpent; - // solium-disable-next-line security/no-block-members - if (expense.periodEnd < now) { - // solium-disable-next-line security/no-block-members - expense.periodEnd = uint64(now + 24 hours); - expense.alreadySpent = uint128(_amount); - } else { - expense.alreadySpent += uint128(_amount); - } - } - } - - /** - * @dev Checks if a transfer amount is withing the daily limit for a wallet. - * @param _wallet The target wallet. - * @param _limit The current limit for the wallet. - * @param _amount The transfer amount. - * @return true if the transfer amount is withing the daily limit. - */ - function isWithinDailyLimit(BaseWallet _wallet, uint _limit, uint _amount) internal view returns (bool) { - if (_limit == LIMIT_DISABLED) { - return true; - } - DailySpent storage expense = limits[address(_wallet)].dailySpent; - // solium-disable-next-line security/no-block-members - if (expense.periodEnd < now) { - return (_amount <= _limit); - } else { - return (expense.alreadySpent + _amount <= _limit && expense.alreadySpent + _amount >= expense.alreadySpent); - } - } - - /** - * @dev Helper method to get the current limit from a Limit struct. - * @param _current The value of the current parameter - * @param _pending The value of the pending parameter - * @param _changeAfter The value of the changeAfter parameter - */ - function currentLimit(uint128 _current, uint128 _pending, uint64 _changeAfter) internal view returns (uint128) { - // solium-disable-next-line security/no-block-members - if (_changeAfter > 0 && _changeAfter < now) { - return _pending; - } - return _current; - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/Module.sol b/contracts-legacy/v1.6.0/contracts/modules/common/Module.sol deleted file mode 100644 index 5d6383c72..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/Module.sol +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; - -/** - * @title Module - * @dev Interface for a module. - * A module MUST implement the addModule() method to ensure that a wallet with at least one module - * can never end up in a "frozen" state. - * @author Julien Niset - - */ -interface Module { - - /** - * @dev Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. - * @param _wallet The wallet. - */ - function init(BaseWallet _wallet) external; - - /** - * @dev Adds a module to a wallet. - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external; - - /** - * @dev Utility method to recover any ERC20 token that was sent to the - * module by mistake. - * @param _token The token to recover. - */ - function recoverToken(address _token) external; -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/OnlyOwnerModule.sol b/contracts-legacy/v1.6.0/contracts/modules/common/OnlyOwnerModule.sol deleted file mode 100644 index a454c1025..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/OnlyOwnerModule.sol +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./BaseModule.sol"; -import "./RelayerModule.sol"; -import "../../wallet/BaseWallet.sol"; - -/** - * @title OnlyOwnerModule - * @dev Module that extends BaseModule and RelayerModule for modules where the execute() method - * must be called with one signature frm the owner. - * @author Julien Niset - - */ -contract OnlyOwnerModule is BaseModule, RelayerModule { - - // bytes4 private constant IS_ONLY_OWNER_MODULE = bytes4(keccak256("isOnlyOwnerModule()")); - - /** - * @dev Returns a constant that indicates that the module is an OnlyOwnerModule. - * @return The constant bytes4(keccak256("isOnlyOwnerModule()")) - */ - function isOnlyOwnerModule() external pure returns (bytes4) { - // return IS_ONLY_OWNER_MODULE; - return this.isOnlyOwnerModule.selector; - } - - /** - * @dev Adds a module to a wallet. First checks that the module is registered. - * Unlike its overrided parent, this method can be called via the RelayerModule's execute() - * @param _wallet The target wallet. - * @param _module The modules to authorise. - */ - function addModule(BaseWallet _wallet, Module _module) external onlyWalletOwner(_wallet) { - require(registry.isRegisteredModule(address(_module)), "BM: module is not registered"); - _wallet.authoriseModule(address(_module), true); - } - - // *************** Implementation of RelayerModule methods ********************* // - - // Overrides to use the incremental nonce and save some gas - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 /* _signHash */) internal returns (bool) { - return checkAndUpdateNonce(_wallet, _nonce); - } - - function validateSignatures( - BaseWallet _wallet, - bytes memory /* _data */, - bytes32 _signHash, - bytes memory _signatures - ) - internal - view - returns (bool) - { - address signer = recoverSigner(_signHash, _signatures, 0); - return isOwner(_wallet, signer); // "OOM: signer must be owner" - } - - function getRequiredSignatures(BaseWallet /* _wallet */, bytes memory /* _data */) internal view returns (uint256) { - return 1; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModule.sol b/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModule.sol deleted file mode 100644 index 24573a972..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModule.sol +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "./BaseModule.sol"; - -/** - * @title RelayerModule - * @dev Base module containing logic to execute transactions signed by eth-less accounts and sent by a relayer. - * @author Julien Niset - - */ -contract RelayerModule is BaseModule { - - uint256 constant internal BLOCKBOUND = 10000; - - mapping (address => RelayerConfig) public relayer; - - struct RelayerConfig { - uint256 nonce; - mapping (bytes32 => bool) executedTx; - } - - event TransactionExecuted(address indexed wallet, bool indexed success, bytes32 signedHash); - - /** - * @dev Throws if the call did not go through the execute() method. - */ - modifier onlyExecute { - require(msg.sender == address(this), "RM: must be called via execute()"); - _; - } - - /* ***************** Abstract method ************************* */ - - /** - * @dev Gets the number of valid signatures that must be provided to execute a - * specific relayed transaction. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @return The number of required signatures. - */ - function getRequiredSignatures(BaseWallet _wallet, bytes memory _data) internal view returns (uint256); - - /** - * @dev Validates the signatures provided with a relayed transaction. - * The method MUST throw if one or more signatures are not valid. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @param _signHash The signed hash representing the relayed transaction. - * @param _signatures The signatures as a concatenated byte array. - */ - function validateSignatures( - BaseWallet _wallet, - bytes memory _data, - bytes32 _signHash, - bytes memory _signatures) internal view returns (bool); - - /* ************************************************************ */ - - /** - * @dev Executes a relayed transaction. - * @param _wallet The target wallet. - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _signatures The signatures as a concatenated byte array. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function execute( - BaseWallet _wallet, - bytes calldata _data, - uint256 _nonce, - bytes calldata _signatures, - uint256 _gasPrice, - uint256 _gasLimit - ) - external - returns (bool success) - { - uint startGas = gasleft(); - bytes32 signHash = getSignHash(address(this), address(_wallet), 0, _data, _nonce, _gasPrice, _gasLimit); - require(checkAndUpdateUniqueness(_wallet, _nonce, signHash), "RM: Duplicate request"); - require(verifyData(address(_wallet), _data), "RM: the wallet authorized is different then the target of the relayed data"); - uint256 requiredSignatures = getRequiredSignatures(_wallet, _data); - if ((requiredSignatures * 65) == _signatures.length) { - if (verifyRefund(_wallet, _gasLimit, _gasPrice, requiredSignatures)) { - if (requiredSignatures == 0 || validateSignatures(_wallet, _data, signHash, _signatures)) { - // solium-disable-next-line security/no-call-value - (success,) = address(this).call(_data); - refund(_wallet, startGas - gasleft(), _gasPrice, _gasLimit, requiredSignatures, msg.sender); - } - } - } - emit TransactionExecuted(address(_wallet), success, signHash); - } - - /** - * @dev Gets the current nonce for a wallet. - * @param _wallet The target wallet. - */ - function getNonce(BaseWallet _wallet) external view returns (uint256 nonce) { - return relayer[address(_wallet)].nonce; - } - - /** - * @dev Generates the signed hash of a relayed transaction according to ERC 1077. - * @param _from The starting address for the relayed transaction (should be the module) - * @param _to The destination address for the relayed transaction (should be the wallet) - * @param _value The value for the relayed transaction - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function getSignHash( - address _from, - address _to, - uint256 _value, - bytes memory _data, - uint256 _nonce, - uint256 _gasPrice, - uint256 _gasLimit - ) - internal - pure - returns (bytes32) - { - return keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256(abi.encodePacked(byte(0x19), byte(0), _from, _to, _value, _data, _nonce, _gasPrice, _gasLimit)) - )); - } - - /** - * @dev Checks if the relayed transaction is unique. - * @param _wallet The target wallet. - * @param _signHash The signed hash of the transaction - */ - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 /* _nonce */, bytes32 _signHash) internal returns (bool) { - if (relayer[address(_wallet)].executedTx[_signHash] == true) { - return false; - } - relayer[address(_wallet)].executedTx[_signHash] = true; - return true; - } - - /** - * @dev Checks that a nonce has the correct format and is valid. - * It must be constructed as nonce = {block number}{timestamp} where each component is 16 bytes. - * @param _wallet The target wallet. - * @param _nonce The nonce - */ - function checkAndUpdateNonce(BaseWallet _wallet, uint256 _nonce) internal returns (bool) { - if (_nonce <= relayer[address(_wallet)].nonce) { - return false; - } - uint256 nonceBlock = (_nonce & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128; - if (nonceBlock > block.number + BLOCKBOUND) { - return false; - } - relayer[address(_wallet)].nonce = _nonce; - return true; - } - - /** - * @dev Recovers the signer at a given position from a list of concatenated signatures. - * @param _signedHash The signed hash - * @param _signatures The concatenated signatures. - * @param _index The index of the signature to recover. - */ - function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { - uint8 v; - bytes32 r; - bytes32 s; - // we jump 32 (0x20) as the first slot of bytes contains the length - // we jump 65 (0x41) per signature - // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask - // solium-disable-next-line security/no-inline-assembly - assembly { - r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) - s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) - v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) - } - require(v == 27 || v == 28); // solium-disable-line error-reason - return ecrecover(_signedHash, v, r, s); - } - - /** - * @dev Refunds the gas used to the Relayer. - * For security reasons the default behavior is to not refund calls with 0 or 1 signatures. - * @param _wallet The target wallet. - * @param _gasUsed The gas used. - * @param _gasPrice The gas price for the refund. - * @param _gasLimit The gas limit for the refund. - * @param _signatures The number of signatures used in the call. - * @param _relayer The address of the Relayer. - */ - function refund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _gasLimit, uint _signatures, address _relayer) internal { - uint256 amount = 29292 + _gasUsed; // 21000 (transaction) + 7620 (execution of refund) + 672 to log the event + _gasUsed - // only refund if gas price not null, more than 1 signatures, gas less than gasLimit - if (_gasPrice > 0 && _signatures > 1 && amount <= _gasLimit) { - if (_gasPrice > tx.gasprice) { - amount = amount * tx.gasprice; - } else { - amount = amount * _gasPrice; - } - invokeWallet(address(_wallet), _relayer, amount, EMPTY_BYTES); - } - } - - /** - * @dev Returns false if the refund is expected to fail. - * @param _wallet The target wallet. - * @param _gasUsed The expected gas used. - * @param _gasPrice The expected gas price for the refund. - */ - function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) { - if (_gasPrice > 0 && - _signatures > 1 && - (address(_wallet).balance < _gasUsed * _gasPrice || _wallet.authorised(address(this)) == false)) { - return false; - } - return true; - } - - /** - * @dev Checks that the wallet address provided as the first parameter of the relayed data is the same - * as the wallet passed as the input of the execute() method. - @return false if the addresses are different. - */ - function verifyData(address _wallet, bytes memory _data) private pure returns (bool) { - require(_data.length >= 36, "RM: Invalid dataWallet"); - address dataWallet; - // solium-disable-next-line security/no-inline-assembly - assembly { - //_data = {length:32}{sig:4}{_wallet:32}{...} - dataWallet := mload(add(_data, 0x24)) - } - return dataWallet == _wallet; - } - - /** - * @dev Parses the data to extract the method signature. - */ - function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { - require(_data.length >= 4, "RM: Invalid functionPrefix"); - // solium-disable-next-line security/no-inline-assembly - assembly { - prefix := mload(add(_data, 0x20)) - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModuleV2.sol b/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModuleV2.sol deleted file mode 100644 index 23ec1deb3..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/common/RelayerModuleV2.sol +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "./GuardianUtils.sol"; -import "./BaseModule.sol"; - -/** - * @title RelayerModuleV2 - * @dev Base module containing logic to execute transactions signed by eth-less accounts and sent by a relayer. - * RelayerModuleV2 should ultimately replace RelayerModule and be subclassed by all modules. - * It is currently only subclassed by RecoveryManager and ApprovedTransfer. - * @author Julien Niset , Olivier VDB - */ -contract RelayerModuleV2 is BaseModule { - - uint256 constant internal BLOCKBOUND = 10000; - - mapping (address => RelayerConfig) public relayer; - - struct RelayerConfig { - uint256 nonce; - mapping (bytes32 => bool) executedTx; - } - - enum OwnerSignature { - Required, - Optional, - Disallowed - } - - event TransactionExecuted(address indexed wallet, bool indexed success, bytes32 signedHash); - - /** - * @dev Throws if the call did not go through the execute() method. - */ - modifier onlyExecute { - require(msg.sender == address(this), "RM: must be called via execute()"); - _; - } - - /* ***************** Abstract methods ************************* */ - - /** - * @dev Gets the number of valid signatures that must be provided to execute a - * specific relayed transaction. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @return The number of required signatures. - */ - function getRequiredSignatures(BaseWallet _wallet, bytes memory _data) public view returns (uint256); - - /** - * @dev Validates the signatures provided with a relayed transaction. - * The method MUST return false if one or more signatures are not valid. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @param _signHash The signed hash representing the relayed transaction. - * @param _signatures The signatures as a concatenated byte array. - * @return A boolean indicating whether the signatures are valid. - */ - function validateSignatures( - BaseWallet _wallet, - bytes memory _data, - bytes32 _signHash, - bytes memory _signatures - ) - internal view returns (bool); - - /* ***************** External methods ************************* */ - - /** - * @dev Executes a relayed transaction. - * @param _wallet The target wallet. - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _signatures The signatures as a concatenated byte array. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function execute( - BaseWallet _wallet, - bytes calldata _data, - uint256 _nonce, - bytes calldata _signatures, - uint256 _gasPrice, - uint256 _gasLimit - ) - external - returns (bool success) - { - uint startGas = gasleft(); - bytes32 signHash = getSignHash(address(this), address(_wallet), 0, _data, _nonce, _gasPrice, _gasLimit); - require(checkAndUpdateUniqueness(_wallet, _nonce, signHash), "RM: Duplicate request"); - require(verifyData(address(_wallet), _data), "RM: Target of _data != _wallet"); - uint256 requiredSignatures = getRequiredSignatures(_wallet, _data); - require(requiredSignatures * 65 == _signatures.length, "RM: Wrong number of signatures"); - require(requiredSignatures == 0 || validateSignatures(_wallet, _data, signHash, _signatures), "RM: Invalid signatures"); - // The correctness of the refund is checked on the next line using an `if` instead of a `require` - // in order to prevent a failing refund from being replayable in the future. - if (verifyRefund(_wallet, _gasLimit, _gasPrice, requiredSignatures)) { - // solium-disable-next-line security/no-call-value - (success,) = address(this).call(_data); - refund(_wallet, startGas - gasleft(), _gasPrice, _gasLimit, requiredSignatures, msg.sender); - } - emit TransactionExecuted(address(_wallet), success, signHash); - } - - /** - * @dev Gets the current nonce for a wallet. - * @param _wallet The target wallet. - */ - function getNonce(BaseWallet _wallet) external view returns (uint256 nonce) { - return relayer[address(_wallet)].nonce; - } - - /* ***************** Internal & Private methods ************************* */ - - /** - * @dev Generates the signed hash of a relayed transaction according to ERC 1077. - * @param _from The starting address for the relayed transaction (should be the module) - * @param _to The destination address for the relayed transaction (should be the wallet) - * @param _value The value for the relayed transaction - * @param _data The data for the relayed transaction - * @param _nonce The nonce used to prevent replay attacks. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. - */ - function getSignHash( - address _from, - address _to, - uint256 _value, - bytes memory _data, - uint256 _nonce, - uint256 _gasPrice, - uint256 _gasLimit - ) - internal - pure - returns (bytes32) - { - return keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256(abi.encodePacked(byte(0x19), byte(0), _from, _to, _value, _data, _nonce, _gasPrice, _gasLimit)) - )); - } - - /** - * @dev Checks if the relayed transaction is unique. - * @param _wallet The target wallet. - * @param _nonce The nonce - * @param _signHash The signed hash of the transaction - */ - function checkAndUpdateUniqueness(BaseWallet _wallet, uint256 _nonce, bytes32 _signHash) internal returns (bool) { - if (relayer[address(_wallet)].executedTx[_signHash] == true) { - return false; - } - relayer[address(_wallet)].executedTx[_signHash] = true; - return true; - } - - /** - * @dev Checks that a nonce has the correct format and is valid. - * It must be constructed as nonce = {block number}{timestamp} where each component is 16 bytes. - * @param _wallet The target wallet. - * @param _nonce The nonce - */ - function checkAndUpdateNonce(BaseWallet _wallet, uint256 _nonce) internal returns (bool) { - if (_nonce <= relayer[address(_wallet)].nonce) { - return false; - } - uint256 nonceBlock = (_nonce & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128; - if (nonceBlock > block.number + BLOCKBOUND) { - return false; - } - relayer[address(_wallet)].nonce = _nonce; - return true; - } - - /** - * @dev Validates the signatures provided with a relayed transaction. - * The method MUST throw if one or more signatures are not valid. - * @param _wallet The target wallet. - * @param _signHash The signed hash representing the relayed transaction. - * @param _signatures The signatures as a concatenated byte array. - * @param _option An enum indicating whether the owner is required, optional or disallowed. - */ - function validateSignatures( - BaseWallet _wallet, - bytes32 _signHash, - bytes memory _signatures, - OwnerSignature _option - ) - internal view returns (bool) - { - address lastSigner = address(0); - address[] memory guardians; - if (_option != OwnerSignature.Required || _signatures.length > 65) { - guardians = guardianStorage.getGuardians(_wallet); // guardians are only read if they may be needed - } - bool isGuardian; - - for (uint8 i = 0; i < _signatures.length / 65; i++) { - address signer = recoverSigner(_signHash, _signatures, i); - - if (i == 0) { - if (_option == OwnerSignature.Required) { - // First signer must be owner - if (isOwner(_wallet, signer)) { - continue; - } - return false; - } else if (_option == OwnerSignature.Optional) { - // First signer can be owner - if (isOwner(_wallet, signer)) { - continue; - } - } - } - if (signer <= lastSigner) { - return false; // Signers must be different - } - lastSigner = signer; - (isGuardian, guardians) = GuardianUtils.isGuardian(guardians, signer); - if (!isGuardian) { - return false; - } - } - return true; - } - - /** - * @dev Recovers the signer at a given position from a list of concatenated signatures. - * @param _signedHash The signed hash - * @param _signatures The concatenated signatures. - * @param _index The index of the signature to recover. - */ - function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) { - uint8 v; - bytes32 r; - bytes32 s; - // we jump 32 (0x20) as the first slot of bytes contains the length - // we jump 65 (0x41) per signature - // for v we load 32 bytes ending with v (the first 31 come from s) then apply a mask - // solium-disable-next-line security/no-inline-assembly - assembly { - r := mload(add(_signatures, add(0x20,mul(0x41,_index)))) - s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) - v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) - } - require(v == 27 || v == 28); // solium-disable-line error-reason - return ecrecover(_signedHash, v, r, s); - } - - /** - * @dev Refunds the gas used to the Relayer. - * For security reasons the default behavior is to not refund calls with 0 or 1 signatures. - * @param _wallet The target wallet. - * @param _gasUsed The gas used. - * @param _gasPrice The gas price for the refund. - * @param _gasLimit The gas limit for the refund. - * @param _signatures The number of signatures used in the call. - * @param _relayer The address of the Relayer. - */ - function refund( - BaseWallet _wallet, - uint _gasUsed, - uint _gasPrice, - uint _gasLimit, - uint _signatures, - address _relayer - ) - internal - { - uint256 amount = 29292 + _gasUsed; // 21000 (transaction) + 7620 (execution of refund) + 672 to log the event + _gasUsed - // only refund if gas price not null, more than 1 signatures, gas less than gasLimit - if (_gasPrice > 0 && _signatures > 1 && amount <= _gasLimit) { - if (_gasPrice > tx.gasprice) { - amount = amount * tx.gasprice; - } else { - amount = amount * _gasPrice; - } - invokeWallet(address(_wallet), _relayer, amount, EMPTY_BYTES); - } - } - - /** - * @dev Returns false if the refund is expected to fail. - * @param _wallet The target wallet. - * @param _gasUsed The expected gas used. - * @param _gasPrice The expected gas price for the refund. - */ - function verifyRefund(BaseWallet _wallet, uint _gasUsed, uint _gasPrice, uint _signatures) internal view returns (bool) { - if (_gasPrice > 0 && - _signatures > 1 && - (address(_wallet).balance < _gasUsed * _gasPrice || _wallet.authorised(address(this)) == false)) { - return false; - } - return true; - } - - /** - * @dev Parses the data to extract the method signature. - */ - function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { - require(_data.length >= 4, "RM: Invalid functionPrefix"); - // solium-disable-next-line security/no-inline-assembly - assembly { - prefix := mload(add(_data, 0x20)) - } - } - - /** - * @dev Checks that the wallet address provided as the first parameter of the relayed data is the same - * as the wallet passed as the input of the execute() method. - @return false if the addresses are different. - */ - function verifyData(address _wallet, bytes memory _data) private pure returns (bool) { - require(_data.length >= 36, "RM: Invalid dataWallet"); - address dataWallet; - // solium-disable-next-line security/no-inline-assembly - assembly { - //_data = {length:32}{sig:4}{_wallet:32}{...} - dataWallet := mload(add(_data, 0x24)) - } - return dataWallet == _wallet; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerManager.sol b/contracts-legacy/v1.6.0/contracts/modules/maker/MakerManager.sol deleted file mode 100644 index 167af8dfb..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerManager.sol +++ /dev/null @@ -1,643 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -import "../../wallet/BaseWallet.sol"; -import "../common/BaseModule.sol"; -import "../common/RelayerModule.sol"; -import "../common/OnlyOwnerModule.sol"; -import "../../defi/Loan.sol"; - -// Interface to MakerDAO's Tub contract, used to manage CDPs -contract IMakerCdp { - IDSValue public pep; // MKR price feed - IMakerVox public vox; // DAI price feed - - function sai() external view returns (address); // DAI - function skr() external view returns (address); // PETH - function gem() external view returns (address); // WETH - function gov() external view returns (address); // MKR - - function lad(bytes32 cup) external view returns (address); - function ink(bytes32 cup) external view returns (uint); - function tab(bytes32 cup) external returns (uint); - function rap(bytes32 cup) external returns (uint); - - function tag() public view returns (uint wad); - function mat() public view returns (uint ray); - function per() public view returns (uint ray); - function safe(bytes32 cup) external returns (bool); - function ask(uint wad) public view returns (uint); - function bid(uint wad) public view returns (uint); - - function open() external returns (bytes32 cup); - function join(uint wad) external; // Join PETH - function exit(uint wad) external; // Exit PETH - function give(bytes32 cup, address guy) external; - function lock(bytes32 cup, uint wad) external; - function free(bytes32 cup, uint wad) external; - function draw(bytes32 cup, uint wad) external; - function wipe(bytes32 cup, uint wad) external; - function shut(bytes32 cup) external; - function bite(bytes32 cup) external; -} - -interface IMakerVox { - function par() external returns (uint); -} - -interface IDSValue { - function peek() external view returns (bytes32, bool); - function read() external view returns (bytes32); - function poke(bytes32 wut) external; - function void() external; -} - -interface IUniswapFactory { - function getExchange(address _token) external view returns(address); -} - -interface IUniswapExchange { - function getEthToTokenOutputPrice(uint256 _tokensBought) external view returns (uint256); - function getEthToTokenInputPrice(uint256 _ethSold) external view returns (uint256); - function getTokenToEthOutputPrice(uint256 _ethBought) external view returns (uint256); - function getTokenToEthInputPrice(uint256 _tokensSold) external view returns (uint256); -} - - -/** - * @title MakerManager - * @dev Module to borrow tokens with MakerDAO - * @author Olivier VDB - , Julien Niset - - */ -contract MakerManager is Loan, BaseModule, RelayerModule, OnlyOwnerModule { - - bytes32 constant NAME = "MakerManager"; - - // The Maker Tub contract - IMakerCdp public makerCdp; - // The Uniswap Factory contract - IUniswapFactory public uniswapFactory; - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // Method signatures to reduce gas cost at depoyment - bytes4 constant internal CDP_DRAW = bytes4(keccak256("draw(bytes32,uint256)")); - bytes4 constant internal CDP_WIPE = bytes4(keccak256("wipe(bytes32,uint256)")); - bytes4 constant internal CDP_SHUT = bytes4(keccak256("shut(bytes32)")); - bytes4 constant internal CDP_JOIN = bytes4(keccak256("join(uint256)")); - bytes4 constant internal CDP_LOCK = bytes4(keccak256("lock(bytes32,uint256)")); - bytes4 constant internal CDP_FREE = bytes4(keccak256("free(bytes32,uint256)")); - bytes4 constant internal CDP_EXIT = bytes4(keccak256("exit(uint256)")); - bytes4 constant internal WETH_DEPOSIT = bytes4(keccak256("deposit()")); - bytes4 constant internal WETH_WITHDRAW = bytes4(keccak256("withdraw(uint256)")); - bytes4 constant internal ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); - bytes4 constant internal ETH_TOKEN_SWAP_OUTPUT = bytes4(keccak256("ethToTokenSwapOutput(uint256,uint256)")); - bytes4 constant internal ETH_TOKEN_SWAP_INPUT = bytes4(keccak256("ethToTokenSwapInput(uint256,uint256)")); - bytes4 constant internal TOKEN_ETH_SWAP_INPUT = bytes4(keccak256("tokenToEthSwapInput(uint256,uint256,uint256)")); - - using SafeMath for uint256; - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - IMakerCdp _makerCdp, - IUniswapFactory _uniswapFactory - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - makerCdp = _makerCdp; - uniswapFactory = _uniswapFactory; - } - - /* ********************************** Implementation of Loan ************************************* */ - - /** - * @dev Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral (must be 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE). - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of tokens borrowed. - * @return The ID of the created CDP. - */ - function openLoan( - BaseWallet _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - require(_collateral == ETH_TOKEN_ADDRESS, "Maker: collateral must be ETH"); - require(_debtToken == makerCdp.sai(), "Maker: debt token must be DAI"); - _loanId = openCdp(_wallet, _collateralAmount, _debtAmount, makerCdp); - emit LoanOpened(address(_wallet), _loanId, _collateral, _collateralAmount, _debtToken, _debtAmount); - } - - /** - * @dev Closes a collateralized loan by repaying all debts (plus interest) and redeeming all collateral (plus interest). - * @param _wallet The target wallet. - * @param _loanId The ID of the target CDP. - */ - function closeLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - closeCdp(_wallet, _loanId, makerCdp, uniswapFactory); - emit LoanClosed(address(_wallet), _loanId); - } - - /** - * @dev Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target CDP. - * @param _collateral The token used as a collateral (must be 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE). - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_collateral == ETH_TOKEN_ADDRESS, "Maker: collateral must be ETH"); - addCollateral(_wallet, _loanId, _collateralAmount, makerCdp); - emit CollateralAdded(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target CDP. - * @param _collateral The token used as a collateral (must be 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE). - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_collateral == ETH_TOKEN_ADDRESS, "Maker: collateral must be ETH"); - removeCollateral(_wallet, _loanId, _collateralAmount, makerCdp); - emit CollateralRemoved(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target CDP. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_debtToken == makerCdp.sai(), "Maker: debt token must be DAI"); - addDebt(_wallet, _loanId, _debtAmount, makerCdp); - emit DebtAdded(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target CDP. - * @param _debtToken The token to repay (must be the address of the DAI contract). - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_debtToken == makerCdp.sai(), "Maker: debt token must be DAI"); - removeDebt(_wallet, _loanId, _debtAmount, makerCdp, uniswapFactory); - emit DebtRemoved(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Gets information about a loan identified by its ID. - * @param _loanId The ID of the target CDP. - * @return a status [0: no loan, 1: loan is safe, 2: loan is unsafe and can be liquidated, 3: loan exists but we are unable to provide info] - * and a value (in ETH) representing the value that could still be borrowed when status = 1; or the value of the collateral that should be added to - * avoid liquidation when status = 2. - */ - function getLoan( - BaseWallet /* _wallet */, - bytes32 _loanId - ) - external - view - returns (uint8 _status, uint256 _ethValue) - { - if (exists(_loanId, makerCdp)) { - return (3,0); - } - return (0,0); - } - - /* *********************************** Maker wrappers ************************************* */ - - /* CDP actions */ - - /** - * @dev Lets the owner of a wallet open a new CDP. The owner must have enough ether - * in their wallet. The required amount of ether will be automatically converted to - * PETH and used as collateral in the CDP. - * @param _wallet The target wallet - * @param _pethCollateral The amount of PETH to lock as collateral in the CDP. - * @param _daiDebt The amount of DAI to draw from the CDP - * @param _makerCdp The Maker CDP contract - * @return The id of the created CDP. - */ - function openCdp( - BaseWallet _wallet, - uint256 _pethCollateral, - uint256 _daiDebt, - IMakerCdp _makerCdp - ) - internal - returns (bytes32 _cup) - { - // Open CDP (CDP owner will be module) - _cup = _makerCdp.open(); - // Transfer CDP ownership to wallet - _makerCdp.give(_cup, address(_wallet)); - // Convert ETH to PETH & lock PETH into CDP - lockETH(_wallet, _cup, _pethCollateral, _makerCdp); - // Draw DAI from CDP - if (_daiDebt > 0) { - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_DRAW, _cup, _daiDebt)); - } - } - - /** - * @dev Lets the owner of a CDP add more collateral to their CDP. The owner must have enough ether - * in their wallet. The required amount of ether will be automatically converted to - * PETH and locked in the CDP. - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _amount The amount of additional PETH to lock as collateral in the CDP. - * @param _makerCdp The Maker CDP contract - */ - function addCollateral( - BaseWallet _wallet, - bytes32 _cup, - uint256 _amount, - IMakerCdp _makerCdp - ) - internal - { - // _wallet must be owner of CDP - require(address(_wallet) == _makerCdp.lad(_cup), "CM: not CDP owner"); - // convert ETH to PETH & lock PETH into CDP - lockETH(_wallet, _cup, _amount, _makerCdp); - } - - /** - * @dev Lets the owner of a CDP remove some collateral from their CDP - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _amount The amount of PETH to remove from the CDP. - * @param _makerCdp The Maker CDP contract - */ - function removeCollateral( - BaseWallet _wallet, - bytes32 _cup, - uint256 _amount, - IMakerCdp _makerCdp - ) - internal - { - // unlock PETH from CDP & convert PETH to ETH - freeETH(_wallet, _cup, _amount, _makerCdp); - } - - /** - * @dev Lets the owner of a CDP draw more DAI from their CDP. - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _amount The amount of additional DAI to draw from the CDP. - * @param _makerCdp The Maker CDP contract - */ - function addDebt( - BaseWallet _wallet, - bytes32 _cup, - uint256 _amount, - IMakerCdp _makerCdp - ) - internal - { - // draw DAI from CDP - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_DRAW, _cup, _amount)); - } - - /** - * @dev Lets the owner of a CDP partially repay their debt. The repayment is made up of - * the outstanding DAI debt (including the stability fee if non-zero) plus the MKR governance fee. - * The method will use the user's MKR tokens in priority and will, if needed, convert the required - * amount of ETH to cover for any missing MKR tokens. - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _amount The amount of DAI debt to repay. - * @param _makerCdp The Maker CDP contract - * @param _uniswapFactory The Uniswap Factory contract. - */ - function removeDebt( - BaseWallet _wallet, - bytes32 _cup, - uint256 _amount, - IMakerCdp _makerCdp, - IUniswapFactory _uniswapFactory - ) - internal - { - // _wallet must be owner of CDP - require(address(_wallet) == _makerCdp.lad(_cup), "CM: not CDP owner"); - // get governance fee in MKR - uint256 mkrFee = governanceFeeInMKR(_cup, _amount, _makerCdp); - // get MKR balance - address mkrToken = _makerCdp.gov(); - uint256 mkrBalance = ERC20(mkrToken).balanceOf(address(_wallet)); - if (mkrBalance < mkrFee) { - // Not enough MKR => Convert some ETH into MKR with Uniswap - address mkrUniswap = _uniswapFactory.getExchange(mkrToken); - uint256 etherValueOfMKR = IUniswapExchange(mkrUniswap).getEthToTokenOutputPrice(mkrFee - mkrBalance); - invokeWallet(address(_wallet), mkrUniswap, etherValueOfMKR, abi.encodeWithSelector(ETH_TOKEN_SWAP_OUTPUT, mkrFee - mkrBalance, block.timestamp)); - } - - // get DAI balance - address daiToken = _makerCdp.sai(); - uint256 daiBalance = ERC20(daiToken).balanceOf(address(_wallet)); - if (daiBalance < _amount) { - // Not enough DAI => Convert some ETH into DAI with Uniswap - address daiUniswap = _uniswapFactory.getExchange(daiToken); - uint256 etherValueOfDAI = IUniswapExchange(daiUniswap).getEthToTokenOutputPrice(_amount - daiBalance); - invokeWallet(address(_wallet), daiUniswap, etherValueOfDAI, abi.encodeWithSelector(ETH_TOKEN_SWAP_OUTPUT, _amount - daiBalance, block.timestamp)); - } - - // Approve DAI to let wipe() repay the DAI debt - invokeWallet(address(_wallet), daiToken, 0, abi.encodeWithSelector(ERC20_APPROVE, address(_makerCdp), _amount)); - // Approve MKR to let wipe() pay the MKR governance fee - invokeWallet(address(_wallet), mkrToken, 0, abi.encodeWithSelector(ERC20_APPROVE, address(_makerCdp), mkrFee)); - // repay DAI debt and MKR governance fee - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_WIPE, _cup, _amount)); - } - - /** - * @dev Lets the owner of a CDP close their CDP. The method will 1) repay all debt - * and governance fee, 2) free all collateral, and 3) delete the CDP. - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @param _uniswapFactory The Uniswap Factory contract. - */ - function closeCdp( - BaseWallet _wallet, - bytes32 _cup, - IMakerCdp _makerCdp, - IUniswapFactory _uniswapFactory - ) - internal - { - // repay all debt (in DAI) + stability fee (in DAI) + governance fee (in MKR) - uint debt = daiDebt(_cup, _makerCdp); - if (debt > 0) - removeDebt(_wallet, _cup, debt, _makerCdp, _uniswapFactory); - // free all ETH collateral - uint collateral = pethCollateral(_cup, _makerCdp); - if (collateral > 0) - removeCollateral(_wallet, _cup, collateral, _makerCdp); - // shut the CDP - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_SHUT, _cup)); - } - - /* Convenience methods */ - - /** - * @dev Returns the amount of PETH collateral locked in a CDP. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return the amount of PETH locked in the CDP. - */ - function pethCollateral(bytes32 _cup, IMakerCdp _makerCdp) public view returns (uint256) { - return _makerCdp.ink(_cup); - } - - /** - * @dev Returns the amount of DAI debt (including the stability fee if non-zero) drawn from a CDP. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return the amount of DAI drawn from the CDP. - */ - function daiDebt(bytes32 _cup, IMakerCdp _makerCdp) public returns (uint256) { - return _makerCdp.tab(_cup); - } - - /** - * @dev Indicates whether a CDP is above the liquidation ratio. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return false if the CDP is in danger of being liquidated. - */ - function isSafe(bytes32 _cup, IMakerCdp _makerCdp) public returns (bool) { - return _makerCdp.safe(_cup); - } - - /** - * @dev Checks if a CDP exists. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return true if the CDP exists, false otherwise. - */ - function exists(bytes32 _cup, IMakerCdp _makerCdp) public view returns (bool) { - return _makerCdp.lad(_cup) != address(0); - } - - /** - * @dev Max amount of DAI that can still be drawn from a CDP while keeping it above the liquidation ratio. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return the amount of DAI that can still be drawn from a CDP while keeping it above the liquidation ratio. - */ - function maxDaiDrawable(bytes32 _cup, IMakerCdp _makerCdp) public returns (uint256) { - uint256 maxTab = _makerCdp.ink(_cup).rmul(_makerCdp.tag()).rdiv(_makerCdp.vox().par()).rdiv(_makerCdp.mat()); - return maxTab.sub(_makerCdp.tab(_cup)); - } - - /** - * @dev Min amount of collateral that needs to be added to a CDP to bring it above the liquidation ratio. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return the amount of collateral that needs to be added to a CDP to bring it above the liquidation ratio. - */ - function minCollateralRequired(bytes32 _cup, IMakerCdp _makerCdp) public returns (uint256) { - uint256 minInk = _makerCdp.tab(_cup).rmul(_makerCdp.mat()).rmul(_makerCdp.vox().par()).rdiv(_makerCdp.tag()); - return minInk.sub(_makerCdp.ink(_cup)); - } - - /** - * @dev Returns the governance fee in MKR. - * @param _cup The id of the CDP. - * @param _daiRefund The amount of DAI debt being repaid. - * @param _makerCdp The Maker CDP contract - * @return the governance fee in MKR - */ - function governanceFeeInMKR(bytes32 _cup, uint256 _daiRefund, IMakerCdp _makerCdp) public returns (uint256 _fee) { - uint debt = daiDebt(_cup, _makerCdp); - if (debt == 0) - return 0; - uint256 feeInDAI = _daiRefund.rmul(_makerCdp.rap(_cup).rdiv(debt)); - (bytes32 daiPerMKR, bool ok) = _makerCdp.pep().peek(); - if (ok && daiPerMKR != 0) - _fee = feeInDAI.wdiv(uint(daiPerMKR)); - } - - /** - * @dev Returns the total MKR governance fee to be paid before this CDP can be closed. - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return the total governance fee in MKR - */ - function totalGovernanceFeeInMKR(bytes32 _cup, IMakerCdp _makerCdp) external returns (uint256 _fee) { - return governanceFeeInMKR(_cup, daiDebt(_cup, _makerCdp), _makerCdp); - } - - /** - * @dev Minimum amount of PETH that must be locked in a CDP for it to be deemed "safe" - * @param _cup The id of the CDP. - * @param _makerCdp The Maker CDP contract - * @return The minimum amount of PETH to lock in the CDP - */ - function minRequiredCollateral(bytes32 _cup, IMakerCdp _makerCdp) public returns (uint256 _minCollateral) { - _minCollateral = daiDebt(_cup, _makerCdp) // DAI debt - .rmul(_makerCdp.vox().par()) // x ~1 USD/DAI - .rmul(_makerCdp.mat()) // x 1.5 - .rmul(1010000000000000000000000000) // x (1+1%) cushion - .rdiv(_makerCdp.tag()); // ÷ ~170 USD/PETH - } - - /* *********************************** Utilities ************************************* */ - - /** - * @dev Converts a user's ETH into PETH and locks the PETH in a CDP - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _pethAmount The amount of PETH to buy and lock - * @param _makerCdp The Maker CDP contract - */ - function lockETH( - BaseWallet _wallet, - bytes32 _cup, - uint256 _pethAmount, - IMakerCdp _makerCdp - ) - internal - { - // 1. Convert ETH to PETH - address wethToken = _makerCdp.gem(); - // Get WETH/PETH rate - uint ethAmount = _makerCdp.ask(_pethAmount); - // ETH to WETH - invokeWallet(address(_wallet), wethToken, ethAmount, abi.encodeWithSelector(WETH_DEPOSIT)); - // Approve WETH - invokeWallet(address(_wallet), wethToken, 0, abi.encodeWithSelector(ERC20_APPROVE, address(_makerCdp), ethAmount)); - // WETH to PETH - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_JOIN, _pethAmount)); - - // 2. Lock PETH into CDP - address pethToken = _makerCdp.skr(); - // Approve PETH - invokeWallet(address(_wallet), pethToken, 0, abi.encodeWithSelector(ERC20_APPROVE, address(_makerCdp), _pethAmount)); - // lock PETH into CDP - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_LOCK, _cup, _pethAmount)); - } - - /** - * @dev Unlocks PETH from a user's CDP and converts it back to ETH - * @param _wallet The target wallet - * @param _cup The id of the CDP. - * @param _pethAmount The amount of PETH to unlock and sell - * @param _makerCdp The Maker CDP contract - */ - function freeETH( - BaseWallet _wallet, - bytes32 _cup, - uint256 _pethAmount, - IMakerCdp _makerCdp - ) - internal - { - // 1. Unlock PETH - - // Unlock PETH from CDP - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_FREE, _cup, _pethAmount)); - - // 2. Convert PETH to ETH - address wethToken = _makerCdp.gem(); - address pethToken = _makerCdp.skr(); - // Approve PETH - invokeWallet(address(_wallet), pethToken, 0, abi.encodeWithSelector(ERC20_APPROVE, address(_makerCdp), _pethAmount)); - // PETH to WETH - invokeWallet(address(_wallet), address(_makerCdp), 0, abi.encodeWithSelector(CDP_EXIT, _pethAmount)); - // Get WETH/PETH rate - uint ethAmount = _makerCdp.bid(_pethAmount); - // WETH to ETH - invokeWallet(address(_wallet), wethToken, 0, abi.encodeWithSelector(WETH_WITHDRAW, ethAmount)); - } - - /** - * @dev Conversion rate between DAI and MKR - * @param _makerCdp The Maker CDP contract - * @return The amount of DAI per MKR - */ - function daiPerMkr(IMakerCdp _makerCdp) internal view returns (uint256 _daiPerMKR) { - (bytes32 daiPerMKR_, bool ok) = _makerCdp.pep().peek(); - require(ok && daiPerMKR_ != 0, "LM: invalid DAI/MKR rate"); - _daiPerMKR = uint256(daiPerMKR_); - } -} diff --git a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Base.sol b/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Base.sol deleted file mode 100644 index 389d63fe8..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Base.sol +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../common/BaseModule.sol"; -import "../common/RelayerModule.sol"; -import "../common/OnlyOwnerModule.sol"; -import "../../../lib/utils/SafeMath.sol"; -import "../../../lib/maker/MakerInterfaces.sol"; -import "../../infrastructure/MakerRegistry.sol"; - -/** - * @title MakerV2Base - * @dev Common base to MakerV2Invest and MakerV2Loan. - * @author Olivier VDB - - */ -contract MakerV2Base is BaseModule, RelayerModule, OnlyOwnerModule { - - bytes32 constant private NAME = "MakerV2Manager"; - - // The address of the (MCD) DAI token - GemLike internal daiToken; - // The address of the SAI <-> DAI migration contract - address internal scdMcdMigration; - // The address of the Dai Adapter - JoinLike internal daiJoin; - // The address of the Vat - VatLike internal vat; - - uint256 constant internal RAY = 10 ** 27; - - using SafeMath for uint256; - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - ScdMcdMigrationLike _scdMcdMigration - ) - BaseModule(_registry, _guardianStorage, NAME) - public - { - scdMcdMigration = address(_scdMcdMigration); - daiJoin = _scdMcdMigration.daiJoin(); - daiToken = daiJoin.dai(); - vat = daiJoin.vat(); - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Invest.sol b/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Invest.sol deleted file mode 100644 index dff1e5c43..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Invest.sol +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./MakerV2Base.sol"; - -/** - * @title MakerV2Invest - * @dev Module to lock/unlock MCD DAI into/from Maker's Pot - * @author Olivier VDB - - */ -contract MakerV2Invest is MakerV2Base { - - // The address of the Pot - PotLike internal pot; - - // *************** Events ********************** // - - // WARNING: in a previous version of this module, the third parameter of `InvestmentRemoved` - // represented the *fraction* (out of 10000) of the investment withdrawn, not the absolute amount withdrawn - event InvestmentRemoved(address indexed _wallet, address _token, uint256 _amount); - event InvestmentAdded(address indexed _wallet, address _token, uint256 _amount, uint256 _period); - - // *************** Constructor ********************** // - - constructor(PotLike _pot) public { - pot = _pot; - } - - // *************** External/Public Functions ********************* // - - /** - * @dev Lets the wallet owner deposit MCD DAI into the DSR Pot. - * @param _wallet The target wallet. - * @param _amount The amount of DAI to deposit - */ - function joinDsr( - BaseWallet _wallet, - uint256 _amount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to get the chi rate updated to rho == now, otherwise join will fail - pot.drip(); - // Approve DAI adapter to take the DAI amount - invokeWallet(address(_wallet), address(daiToken), 0, abi.encodeWithSignature("approve(address,uint256)", address(daiJoin), _amount)); - // Join DAI into the vat (_amount of external DAI is burned and the vat transfers _amount of internal DAI from the adapter to the _wallet) - invokeWallet(address(_wallet), address(daiJoin), 0, abi.encodeWithSignature("join(address,uint256)", address(_wallet), _amount)); - // Approve the pot to take out (internal) DAI from the wallet's balance in the vat - grantVatAccess(_wallet, address(pot)); - // Compute the pie value in the pot - uint256 pie = _amount.mul(RAY) / pot.chi(); - // Join the pie value to the pot - invokeWallet(address(_wallet), address(pot), 0, abi.encodeWithSignature("join(uint256)", pie)); - // Emitting event - emit InvestmentAdded(address(_wallet), address(daiToken), _amount, 0); - } - - /** - * @dev Lets the wallet owner withdraw MCD DAI from the DSR pot. - * @param _wallet The target wallet. - * @param _amount The amount of DAI to withdraw. - */ - function exitDsr( - BaseWallet _wallet, - uint256 _amount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to count the savings accumulated until this moment - pot.drip(); - // Calculates the pie value in the pot equivalent to the DAI wad amount - uint256 pie = _amount.mul(RAY) / pot.chi(); - // Exit DAI from the pot - invokeWallet(address(_wallet), address(pot), 0, abi.encodeWithSignature("exit(uint256)", pie)); - // Allow adapter to access the _wallet's DAI balance in the vat - grantVatAccess(_wallet, address(daiJoin)); - // Check the actual balance of DAI in the vat after the pot exit - uint bal = vat.dai(address(_wallet)); - // It is necessary to check if due to rounding the exact _amount can be exited by the adapter. - // Otherwise it will do the maximum DAI balance in the vat - uint256 withdrawn = bal >= _amount.mul(RAY) ? _amount : bal / RAY; - invokeWallet(address(_wallet), address(daiJoin), 0, abi.encodeWithSignature("exit(address,uint256)", address(_wallet), withdrawn)); - // Emitting event - emit InvestmentRemoved(address(_wallet), address(daiToken), withdrawn); - } - - /** - * @dev Lets the wallet owner withdraw their entire MCD DAI balance from the DSR pot. - * @param _wallet The target wallet. - */ - function exitAllDsr( - BaseWallet _wallet - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to count the savings accumulated until this moment - pot.drip(); - // Gets the total pie belonging to the _wallet - uint256 pie = pot.pie(address(_wallet)); - // Exit DAI from the pot - invokeWallet(address(_wallet), address(pot), 0, abi.encodeWithSignature("exit(uint256)", pie)); - // Allow adapter to access the _wallet's DAI balance in the vat - grantVatAccess(_wallet, address(daiJoin)); - // Exits the DAI amount corresponding to the value of pie - uint256 withdrawn = pot.chi().mul(pie) / RAY; - invokeWallet(address(_wallet), address(daiJoin), 0, abi.encodeWithSignature("exit(address,uint256)", address(_wallet), withdrawn)); - // Emitting event - emit InvestmentRemoved(address(_wallet), address(daiToken), withdrawn); - } - - /** - * @dev Returns the amount of DAI currently held in the DSR pot. - * @param _wallet The target wallet. - * @return The DSR balance. - */ - function dsrBalance(BaseWallet _wallet) external view returns (uint256 _balance) { - return pot.chi().mul(pot.pie(address(_wallet))) / RAY; - } - - /* ****************************************** Internal method ******************************************* */ - - /** - * @dev Grant access to the wallet's internal DAI balance in the VAT to an operator. - * @param _wallet The target wallet. - * @param _operator The grantee of the access - */ - function grantVatAccess(BaseWallet _wallet, address _operator) internal { - if (vat.can(address(_wallet), _operator) == 0) { - invokeWallet(address(_wallet), address(vat), 0, abi.encodeWithSignature("hope(address)", _operator)); - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Loan.sol b/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Loan.sol deleted file mode 100644 index 1dede0f70..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Loan.sol +++ /dev/null @@ -1,675 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./MakerV2Base.sol"; -import "../../infrastructure/MakerRegistry.sol"; - -interface IUniswapFactory { - function getExchange(address _token) external view returns(IUniswapExchange); -} - -interface IUniswapExchange { - function getEthToTokenOutputPrice(uint256 _tokensBought) external view returns (uint256); - function getEthToTokenInputPrice(uint256 _ethSold) external view returns (uint256); - function getTokenToEthOutputPrice(uint256 _ethBought) external view returns (uint256); - function getTokenToEthInputPrice(uint256 _tokensSold) external view returns (uint256); -} - -/** - * @title MakerV2Loan - * @dev Module to migrate old CDPs and open and manage new vaults. The vaults managed by - * this module are directly owned by the module. This is to prevent a compromised wallet owner - * from being able to use `TransferManager.callContract()` to transfer ownership of a vault - * (a type of asset NOT protected by a wallet's daily limit) to another account. - * @author Olivier VDB - - */ -contract MakerV2Loan is MakerV2Base { - - // The address of the MKR token - GemLike internal mkrToken; - // The address of the WETH token - GemLike internal wethToken; - // The address of the WETH Adapter - JoinLike internal wethJoin; - // The address of the Jug - JugLike internal jug; - // The address of the Vault Manager (referred to as 'CdpManager' to match Maker's naming) - ManagerLike internal cdpManager; - // The address of the SCD Tub - SaiTubLike internal tub; - // The Maker Registry in which all supported collateral tokens and their adapters are stored - MakerRegistry internal makerRegistry; - // The Uniswap Exchange contract for DAI - IUniswapExchange internal daiUniswap; - // The Uniswap Exchange contract for MKR - IUniswapExchange internal mkrUniswap; - // Mapping [wallet][ilk] -> loanId, that keeps track of cdp owners - // while also enforcing a maximum of one loan per token (ilk) and per wallet - // (which will make future upgrades of the module easier) - mapping(address => mapping(bytes32 => bytes32)) public loanIds; - // Lock used by nonReentrant() - bool private _notEntered = true; - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - - // ****************** Events *************************** // - - // Emitted when an SCD CDP is converted into an MCD vault - event CdpMigrated(address indexed _wallet, bytes32 _oldCdpId, bytes32 _newVaultId); - // Vault management events - event LoanOpened( - address indexed _wallet, - bytes32 indexed _loanId, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ); - event LoanClosed(address indexed _wallet, bytes32 indexed _loanId); - event CollateralAdded(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event CollateralRemoved(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event DebtAdded(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - event DebtRemoved(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - - - // *************** Modifiers *************************** // - - /** - * @dev Throws if the sender is not an authorised module. - */ - modifier onlyModule(BaseWallet _wallet) { - require(_wallet.authorised(msg.sender), "MV2: sender unauthorized"); - _; - } - - /** - * @dev Prevents call reentrancy - */ - modifier nonReentrant() { - require(_notEntered, "MV2: reentrant call"); - _notEntered = false; - _; - _notEntered = true; - } - - // *************** Constructor ********************** // - - constructor( - JugLike _jug, - MakerRegistry _makerRegistry, - IUniswapFactory _uniswapFactory - ) - public - { - cdpManager = ScdMcdMigrationLike(scdMcdMigration).cdpManager(); - tub = ScdMcdMigrationLike(scdMcdMigration).tub(); - wethJoin = ScdMcdMigrationLike(scdMcdMigration).wethJoin(); - wethToken = wethJoin.gem(); - mkrToken = tub.gov(); - jug = _jug; - makerRegistry = _makerRegistry; - daiUniswap = _uniswapFactory.getExchange(address(daiToken)); - mkrUniswap = _uniswapFactory.getExchange(address(mkrToken)); - // Authorize daiJoin to exit DAI from the module's internal balance in the vat - vat.hope(address(daiJoin)); - } - - // *************** External/Public Functions ********************* // - - /* ********************************** Implementation of Loan ************************************* */ - - /** - * @dev Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of tokens borrowed. - * @return The ID of the created vault. - */ - function openLoan( - BaseWallet _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - verifySupportedCollateral(_collateral); - require(_debtToken == address(daiToken), "MV2: debt token not DAI"); - _loanId = bytes32(openVault(_wallet, _collateral, _collateralAmount, _debtAmount)); - emit LoanOpened(address(_wallet), _loanId, _collateral, _collateralAmount, _debtToken, _debtAmount); - } - - /** - * @dev Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - addCollateral(_wallet, uint256(_loanId), _collateralAmount); - emit CollateralAdded(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - BaseWallet _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - removeCollateral(_wallet, uint256(_loanId), _collateralAmount); - emit CollateralRemoved(address(_wallet), _loanId, _collateral, _collateralAmount); - } - - /** - * @dev Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - addDebt(_wallet, uint256(_loanId), _debtAmount); - emit DebtAdded(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _debtToken The token to repay (must be the address of the DAI contract). - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - BaseWallet _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - updateStabilityFee(uint256(_loanId)); - removeDebt(_wallet, uint256(_loanId), _debtAmount); - emit DebtRemoved(address(_wallet), _loanId, _debtToken, _debtAmount); - } - - /** - * @dev Closes a collateralized loan by repaying all debts (plus interest) and redeeming all collateral. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function closeLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - updateStabilityFee(uint256(_loanId)); - closeVault(_wallet, uint256(_loanId)); - emit LoanClosed(address(_wallet), _loanId); - } - - /* *************************************** Other vault methods ***************************************** */ - - /** - * @dev Lets a vault owner transfer their vault from their wallet to the present module so the vault - * can be managed by the module. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function acquireLoan( - BaseWallet _wallet, - bytes32 _loanId - ) - external - nonReentrant - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - { - require(cdpManager.owns(uint256(_loanId)) == address(_wallet), "MV2: wrong vault owner"); - // Transfer the vault from the wallet to the module - invokeWallet( - address(_wallet), - address(cdpManager), - 0, - abi.encodeWithSignature("give(uint256,address)", uint256(_loanId), address(this)) - ); - require(cdpManager.owns(uint256(_loanId)) == address(this), "MV2: failed give"); - // Mark the incoming vault as belonging to the wallet (or merge it into the existing vault if there is one) - assignLoanToWallet(_wallet, _loanId); - } - - /** - * @dev Lets a SCD CDP owner migrate their CDP to use the new MCD engine. - * Requires MKR or ETH to pay the SCD governance fee - * @param _wallet The target wallet. - * @param _cup id of the old SCD CDP to migrate - */ - function migrateCdp( - BaseWallet _wallet, - bytes32 _cup - ) - external - onlyWalletOwner(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - (uint daiPerMkr, bool ok) = tub.pep().peek(); - if (ok && daiPerMkr != 0) { - // get governance fee in MKR - uint mkrFee = tub.rap(_cup).wdiv(daiPerMkr); - // Convert some ETH into MKR with Uniswap if necessary - buyTokens(_wallet, mkrToken, mkrFee, mkrUniswap); - // Transfer the MKR to the Migration contract - invokeWallet(address(_wallet), address(mkrToken), 0, abi.encodeWithSignature("transfer(address,uint256)", address(scdMcdMigration), mkrFee)); - } - // Transfer ownership of the SCD CDP to the migration contract - invokeWallet(address(_wallet), address(tub), 0, abi.encodeWithSignature("give(bytes32,address)", _cup, address(scdMcdMigration))); - // Update stability fee rate - jug.drip(wethJoin.ilk()); - // Execute the CDP migration - _loanId = bytes32(ScdMcdMigrationLike(scdMcdMigration).migrate(_cup)); - // Mark the new vault as belonging to the wallet (or merge it into the existing vault if there is one) - _loanId = assignLoanToWallet(_wallet, _loanId); - - emit CdpMigrated(address(_wallet), _cup, _loanId); - } - - /** - * @dev Lets a future upgrade of this module transfer a vault to itself - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function giveVault( - BaseWallet _wallet, - bytes32 _loanId - ) - external - onlyModule(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - cdpManager.give(uint256(_loanId), msg.sender); - clearLoanOwner(_wallet, _loanId); - } - - /* ************************************** Internal Functions ************************************** */ - - function toInt(uint256 _x) internal pure returns (int _y) { - _y = int(_x); - require(_y >= 0, "MV2: int overflow"); - } - - function assignLoanToWallet(BaseWallet _wallet, bytes32 _loanId) internal returns (bytes32 _assignedLoanId) { - bytes32 ilk = cdpManager.ilks(uint256(_loanId)); - // Check if the user already holds a vault in the MakerV2Manager - bytes32 existingLoanId = loanIds[address(_wallet)][ilk]; - if (existingLoanId > 0) { - // Merge the new loan into the existing loan - cdpManager.shift(uint256(_loanId), uint256(existingLoanId)); - return existingLoanId; - } - // Record the new vault as belonging to the wallet - loanIds[address(_wallet)][ilk] = _loanId; - return _loanId; - } - - function clearLoanOwner(BaseWallet _wallet, bytes32 _loanId) internal { - delete loanIds[address(_wallet)][cdpManager.ilks(uint256(_loanId))]; - } - - function verifyLoanOwner(BaseWallet _wallet, bytes32 _loanId) internal view { - require(loanIds[address(_wallet)][cdpManager.ilks(uint256(_loanId))] == _loanId, "MV2: unauthorized loanId"); - } - - function verifySupportedCollateral(address _collateral) internal view { - if (_collateral != ETH_TOKEN_ADDRESS) { - (bool collateralSupported,,,) = makerRegistry.collaterals(_collateral); - require(collateralSupported, "MV2: unsupported collateral"); - } - } - - function buyTokens( - BaseWallet _wallet, - GemLike _token, - uint256 _tokenAmountRequired, - IUniswapExchange _uniswapExchange - ) - internal - { - // get token balance - uint256 tokenBalance = _token.balanceOf(address(_wallet)); - if (tokenBalance < _tokenAmountRequired) { - // Not enough tokens => Convert some ETH into tokens with Uniswap - uint256 etherValueOfTokens = _uniswapExchange.getEthToTokenOutputPrice(_tokenAmountRequired - tokenBalance); - // solium-disable-next-line security/no-block-members - invokeWallet(address(_wallet), address(_uniswapExchange), etherValueOfTokens, abi.encodeWithSignature("ethToTokenSwapOutput(uint256,uint256)", _tokenAmountRequired - tokenBalance, now)); - } - } - - function joinCollateral( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _collateralAmount, - bytes32 _ilk - ) - internal - { - // Get the adapter and collateral token for the vault - (JoinLike gemJoin, GemLike collateral) = makerRegistry.getCollateral(_ilk); - // Convert ETH to WETH if needed - if (gemJoin == wethJoin) { - invokeWallet(address(_wallet), address(wethToken), _collateralAmount, abi.encodeWithSignature("deposit()")); - } - // Send the collateral to the module - invokeWallet( - address(_wallet), - address(collateral), - 0, - abi.encodeWithSignature("transfer(address,uint256)", address(this), _collateralAmount) - ); - // Approve the adapter to pull the collateral from the module - collateral.approve(address(gemJoin), _collateralAmount); - // Join collateral to the adapter. The first argument to `join` is the address that *technically* owns the vault - gemJoin.join(cdpManager.urns(_cdpId), _collateralAmount); - } - - function joinDebt( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _debtAmount // art.mul(rate).div(RAY) === [wad]*[ray]/[ray]=[wad] - ) - internal - { - // Send the DAI to the module - invokeWallet(address(_wallet), address(daiToken), 0, abi.encodeWithSignature("transfer(address,uint256)", address(this), _debtAmount)); - // Approve the DAI adapter to burn DAI from the module - daiToken.approve(address(daiJoin), _debtAmount); - // Join DAI to the adapter. The first argument to `join` is the address that *technically* owns the vault - // To avoid rounding issues, we substract one wei to the amount joined - daiJoin.join(cdpManager.urns(_cdpId), _debtAmount.sub(1)); - } - - function drawAndExitDebt( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _debtAmount, - uint256 _collateralAmount, - bytes32 _ilk - ) - internal - { - // Get the accumulated rate for the collateral type - (, uint rate,,,) = vat.ilks(_ilk); - // Express the debt in the RAD units used internally by the vat - uint daiDebtInRad = _debtAmount.mul(RAY); - // Lock the collateral and draw the debt. To avoid rounding issues we add an extra wei of debt - cdpManager.frob(_cdpId, toInt(_collateralAmount), toInt(daiDebtInRad.div(rate) + 1)); - // Transfer the (internal) DAI debt from the cdp's urn to the module. - cdpManager.move(_cdpId, address(this), daiDebtInRad); - // Mint the DAI token and exit it to the user's wallet - daiJoin.exit(address(_wallet), _debtAmount); - } - - function updateStabilityFee( - uint256 _cdpId - ) - internal - { - jug.drip(cdpManager.ilks(_cdpId)); - } - - function debt( - uint256 _cdpId - ) - internal - view - returns (uint256 _fullRepayment, uint256 _maxNonFullRepayment) - { - bytes32 ilk = cdpManager.ilks(_cdpId); - (, uint256 art) = vat.urns(ilk, cdpManager.urns(_cdpId)); - if (art > 0) { - (, uint rate,,, uint dust) = vat.ilks(ilk); - _maxNonFullRepayment = art.mul(rate).sub(dust).div(RAY); - _fullRepayment = art.mul(rate).div(RAY) - .add(1) // the amount approved is 1 wei more than the amount repaid, to avoid rounding issues - .add(art-art.mul(rate).div(RAY).mul(RAY).div(rate)); // adding 1 extra wei if further rounding issues are expected - } - } - - function collateral( - uint256 _cdpId - ) - internal - view - returns (uint256 _collateralAmount) - { - (_collateralAmount,) = vat.urns(cdpManager.ilks(_cdpId), cdpManager.urns(_cdpId)); - } - - function verifyValidRepayment( - uint256 _cdpId, - uint256 _debtAmount - ) - internal - view - { - (uint256 fullRepayment, uint256 maxRepayment) = debt(_cdpId); - require(_debtAmount <= maxRepayment || _debtAmount == fullRepayment, "MV2: repay less or full"); - } - - /** - * @dev Lets the owner of a wallet open a new vault. The owner must have enough collateral - * in their wallet. - * @param _wallet The target wallet - * @param _collateral The token to use as collateral in the vault. - * @param _collateralAmount The amount of collateral to lock in the vault. - * @param _debtAmount The amount of DAI to draw from the vault - * @return The id of the created vault. - */ - // solium-disable-next-line security/no-assign-params - function openVault( - BaseWallet _wallet, - address _collateral, - uint256 _collateralAmount, - uint256 _debtAmount - ) - internal - returns (uint256 _cdpId) - { - // Continue with WETH as collateral instead of ETH if needed - if (_collateral == ETH_TOKEN_ADDRESS) { - _collateral = address(wethToken); - } - // Get the ilk for the collateral - bytes32 ilk = makerRegistry.getIlk(_collateral); - // Open a vault if there isn't already one for the collateral type (the vault owner will effectively be the module) - _cdpId = uint256(loanIds[address(_wallet)][ilk]); - if (_cdpId == 0) { - _cdpId = cdpManager.open(ilk, address(this)); - // Mark the vault as belonging to the wallet - loanIds[address(_wallet)][ilk] = bytes32(_cdpId); - } - // Move the collateral from the wallet to the vat - joinCollateral(_wallet, _cdpId, _collateralAmount, ilk); - // Draw the debt and exit it to the wallet - if (_debtAmount > 0) { - drawAndExitDebt(_wallet, _cdpId, _debtAmount, _collateralAmount, ilk); - } - } - - /** - * @dev Lets the owner of a vault add more collateral to their vault. The owner must have enough of the - * collateral token in their wallet. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _collateralAmount The amount of collateral to add to the vault. - */ - function addCollateral( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _collateralAmount - ) - internal - { - // Move the collateral from the wallet to the vat - joinCollateral(_wallet, _cdpId, _collateralAmount, cdpManager.ilks(_cdpId)); - // Lock the collateral - cdpManager.frob(_cdpId, toInt(_collateralAmount), 0); - } - - /** - * @dev Lets the owner of a vault remove some collateral from their vault - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _collateralAmount The amount of collateral to remove from the vault. - */ - function removeCollateral( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _collateralAmount - ) - internal - { - // Unlock the collateral - cdpManager.frob(_cdpId, -toInt(_collateralAmount), 0); - // Transfer the (internal) collateral from the cdp's urn to the module. - cdpManager.flux(_cdpId, address(this), _collateralAmount); - // Get the adapter for the collateral - (JoinLike gemJoin,) = makerRegistry.getCollateral(cdpManager.ilks(_cdpId)); - // Exit the collateral from the adapter. - gemJoin.exit(address(_wallet), _collateralAmount); - // Convert WETH to ETH if needed - if (gemJoin == wethJoin) { - invokeWallet(address(_wallet), address(wethToken), 0, abi.encodeWithSignature("withdraw(uint256)", _collateralAmount)); - } - } - - /** - * @dev Lets the owner of a vault draw more DAI from their vault. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _amount The amount of additional DAI to draw from the vault. - */ - function addDebt( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _amount - ) - internal - { - // Draw and exit the debt to the wallet - drawAndExitDebt(_wallet, _cdpId, _amount, 0, cdpManager.ilks(_cdpId)); - } - - /** - * @dev Lets the owner of a vault partially repay their debt. The repayment is made up of - * the outstanding DAI debt plus the DAI stability fee. - * The method will use the user's DAI tokens in priority and will, if needed, convert the required - * amount of ETH to cover for any missing DAI tokens. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _amount The amount of DAI debt to repay. - */ - function removeDebt( - BaseWallet _wallet, - uint256 _cdpId, - uint256 _amount - ) - internal - { - verifyValidRepayment(_cdpId, _amount); - // Convert some ETH into DAI with Uniswap if necessary - buyTokens(_wallet, daiToken, _amount, daiUniswap); - // Move the DAI from the wallet to the vat. - joinDebt(_wallet, _cdpId, _amount); - // Get the accumulated rate for the collateral type - (, uint rate,,,) = vat.ilks(cdpManager.ilks(_cdpId)); - // Repay the debt. To avoid rounding issues we reduce the repayment by one wei - cdpManager.frob(_cdpId, 0, -toInt(_amount.sub(1).mul(RAY).div(rate))); - } - - /** - * @dev Lets the owner of a vault close their vault. The method will: - * 1) repay all debt and fee - * 2) free all collateral - * @param _wallet The target wallet - * @param _cdpId The id of the CDP. - */ - function closeVault( - BaseWallet _wallet, - uint256 _cdpId - ) - internal - { - (uint256 fullRepayment,) = debt(_cdpId); - // Repay the debt - if (fullRepayment > 0) { - removeDebt(_wallet, _cdpId, fullRepayment); - } - // Remove the collateral - uint256 ink = collateral(_cdpId); - if (ink > 0) { - removeCollateral(_wallet, _cdpId, ink); - } - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Manager.sol b/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Manager.sol deleted file mode 100644 index c4c473a56..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/maker/MakerV2Manager.sol +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "./MakerV2Base.sol"; -import "./MakerV2Invest.sol"; -import "./MakerV2Loan.sol"; - -/** - * @title MakerV2Manager - * @dev Module to lock/unlock MCD DAI into/from Maker's Pot, - * migrate old CDPs and open and manage new CDPs. - * @author Olivier VDB - - */ -contract MakerV2Manager is MakerV2Base, MakerV2Invest, MakerV2Loan { - - // *************** Constructor ********************** // - - constructor( - ModuleRegistry _registry, - GuardianStorage _guardianStorage, - ScdMcdMigrationLike _scdMcdMigration, - PotLike _pot, - JugLike _jug, - MakerRegistry _makerRegistry, - IUniswapFactory _uniswapFactory - ) - MakerV2Base(_registry, _guardianStorage, _scdMcdMigration) - MakerV2Invest(_pot) - MakerV2Loan(_jug, _makerRegistry, _uniswapFactory) - public - { - } - -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/storage/GuardianStorage.sol b/contracts-legacy/v1.6.0/contracts/modules/storage/GuardianStorage.sol deleted file mode 100644 index d709a86c8..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/storage/GuardianStorage.sol +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "./Storage.sol"; -import "./IGuardianStorage.sol"; - -/** - * @title GuardianStorage - * @dev Contract storing the state of wallets related to guardians and lock. - * The contract only defines basic setters and getters with no logic. Only modules authorised - * for a wallet can modify its state. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract GuardianStorage is IGuardianStorage, Storage { - - struct GuardianStorageConfig { - // the list of guardians - address[] guardians; - // the info about guardians - mapping (address => GuardianInfo) info; - // the lock's release timestamp - uint256 lock; - // the module that set the last lock - address locker; - } - - struct GuardianInfo { - bool exists; - uint128 index; - } - - // wallet specific storage - mapping (address => GuardianStorageConfig) internal configs; - - // *************** External Functions ********************* // - - /** - * @dev Lets an authorised module add a guardian to a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(BaseWallet _wallet, address _guardian) external onlyModule(_wallet) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - config.info[_guardian].exists = true; - config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1); - } - - /** - * @dev Lets an authorised module revoke a guardian from a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(BaseWallet _wallet, address _guardian) external onlyModule(_wallet) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - address lastGuardian = config.guardians[config.guardians.length - 1]; - if (_guardian != lastGuardian) { - uint128 targetIndex = config.info[_guardian].index; - config.guardians[targetIndex] = lastGuardian; - config.info[lastGuardian].index = targetIndex; - } - config.guardians.length--; - delete config.info[_guardian]; - } - - /** - * @dev Returns the number of guardians for a wallet. - * @param _wallet The target wallet. - * @return the number of guardians. - */ - function guardianCount(BaseWallet _wallet) external view returns (uint256) { - return configs[address(_wallet)].guardians.length; - } - - /** - * @dev Gets the list of guaridans for a wallet. - * @param _wallet The target wallet. - * @return the list of guardians. - */ - function getGuardians(BaseWallet _wallet) external view returns (address[] memory) { - GuardianStorageConfig storage config = configs[address(_wallet)]; - address[] memory guardians = new address[](config.guardians.length); - for (uint256 i = 0; i < config.guardians.length; i++) { - guardians[i] = config.guardians[i]; - } - return guardians; - } - - /** - * @dev Checks if an account is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The account. - * @return true if the account is a guardian for a wallet. - */ - function isGuardian(BaseWallet _wallet, address _guardian) external view returns (bool) { - return configs[address(_wallet)].info[_guardian].exists; - } - - /** - * @dev Lets an authorised module set the lock for a wallet. - * @param _wallet The target wallet. - * @param _releaseAfter The epoch time at which the lock should automatically release. - */ - function setLock(BaseWallet _wallet, uint256 _releaseAfter) external onlyModule(_wallet) { - configs[address(_wallet)].lock = _releaseAfter; - if (_releaseAfter != 0 && msg.sender != configs[address(_wallet)].locker) { - configs[address(_wallet)].locker = msg.sender; - } - } - - /** - * @dev Checks if the lock is set for a wallet. - * @param _wallet The target wallet. - * @return true if the lock is set for the wallet. - */ - function isLocked(BaseWallet _wallet) external view returns (bool) { - return configs[address(_wallet)].lock > now; - } - - /** - * @dev Gets the time at which the lock of a wallet will release. - * @param _wallet The target wallet. - * @return the time at which the lock of a wallet will release, or zero if there is no lock set. - */ - function getLock(BaseWallet _wallet) external view returns (uint256) { - return configs[address(_wallet)].lock; - } - - /** - * @dev Gets the address of the last module that modified the lock for a wallet. - * @param _wallet The target wallet. - * @return the address of the last module that modified the lock for a wallet. - */ - function getLocker(BaseWallet _wallet) external view returns (address) { - return configs[address(_wallet)].locker; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/storage/IGuardianStorage.sol b/contracts-legacy/v1.6.0/contracts/modules/storage/IGuardianStorage.sol deleted file mode 100644 index 3b6f44ae8..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/storage/IGuardianStorage.sol +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; - -interface IGuardianStorage{ - - /** - * @dev Lets an authorised module add a guardian to a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(BaseWallet _wallet, address _guardian) external; - - /** - * @dev Lets an authorised module revoke a guardian from a wallet. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(BaseWallet _wallet, address _guardian) external; - - /** - * @dev Checks if an account is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The account. - * @return true if the account is a guardian for a wallet. - */ - function isGuardian(BaseWallet _wallet, address _guardian) external view returns (bool); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/storage/Storage.sol b/contracts-legacy/v1.6.0/contracts/modules/storage/Storage.sol deleted file mode 100644 index 5a449704c..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/storage/Storage.sol +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; - -/** - * @title Storage - * @dev Base contract for the storage of a wallet. - * @author Julien Niset - - */ -contract Storage { - - /** - * @dev Throws if the caller is not an authorised module. - */ - modifier onlyModule(BaseWallet _wallet) { - require(_wallet.authorised(msg.sender), "TS: must be an authorized module to call this method"); - _; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/modules/storage/TransferStorage.sol b/contracts-legacy/v1.6.0/contracts/modules/storage/TransferStorage.sol deleted file mode 100644 index eac40c710..000000000 --- a/contracts-legacy/v1.6.0/contracts/modules/storage/TransferStorage.sol +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../../wallet/BaseWallet.sol"; -import "./Storage.sol"; - -/** - * @title TransferStorage - * @dev Contract storing the state of wallets related to transfers (limit and whitelist). - * The contract only defines basic setters and getters with no logic. Only modules authorised - * for a wallet can modify its state. - * @author Julien Niset - - */ -contract TransferStorage is Storage { - - // wallet specific storage - mapping (address => mapping (address => uint256)) internal whitelist; - - // *************** External Functions ********************* // - - /** - * @dev Lets an authorised module add or remove an account from the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The account to add/remove. - * @param _value True for addition, false for revokation. - */ - function setWhitelist(BaseWallet _wallet, address _target, uint256 _value) external onlyModule(_wallet) { - whitelist[address(_wallet)][_target] = _value; - } - - /** - * @dev Gets the whitelist state of an account for a wallet. - * @param _wallet The target wallet. - * @param _target The account. - * @return the epoch time at which an account strats to be whitelisted, or zero if the account is not whitelisted. - */ - function getWhitelist(BaseWallet _wallet, address _target) external view returns (uint256) { - return whitelist[address(_wallet)][_target]; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/wallet/BaseWallet.sol b/contracts-legacy/v1.6.0/contracts/wallet/BaseWallet.sol deleted file mode 100644 index 42797fdc1..000000000 --- a/contracts-legacy/v1.6.0/contracts/wallet/BaseWallet.sol +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../modules/common/Module.sol"; - -/** - * @title BaseWallet - * @dev Simple modular wallet that authorises modules to call its invoke() method. - * @author Julien Niset - - */ -contract BaseWallet { - - // The implementation of the proxy - address public implementation; - // The owner - address public owner; - // The authorised modules - mapping (address => bool) public authorised; - // The enabled static calls - mapping (bytes4 => address) public enabled; - // The number of modules - uint public modules; - - event AuthorisedModule(address indexed module, bool value); - event EnabledStaticCall(address indexed module, bytes4 indexed method); - event Invoked(address indexed module, address indexed target, uint indexed value, bytes data); - event Received(uint indexed value, address indexed sender, bytes data); - event OwnerChanged(address owner); - - /** - * @dev Throws if the sender is not an authorised module. - */ - modifier moduleOnly { - require(authorised[msg.sender], "BW: msg.sender not an authorized module"); - _; - } - - /** - * @dev Inits the wallet by setting the owner and authorising a list of modules. - * @param _owner The owner. - * @param _modules The modules to authorise. - */ - function init(address _owner, address[] calldata _modules) external { - require(owner == address(0) && modules == 0, "BW: wallet already initialised"); - require(_modules.length > 0, "BW: construction requires at least 1 module"); - owner = _owner; - modules = _modules.length; - for (uint256 i = 0; i < _modules.length; i++) { - require(authorised[_modules[i]] == false, "BW: module is already added"); - authorised[_modules[i]] = true; - Module(_modules[i]).init(this); - emit AuthorisedModule(_modules[i], true); - } - if (address(this).balance > 0) { - emit Received(address(this).balance, address(0), ""); - } - } - - /** - * @dev Enables/Disables a module. - * @param _module The target module. - * @param _value Set to true to authorise the module. - */ - function authoriseModule(address _module, bool _value) external moduleOnly { - if (authorised[_module] != _value) { - emit AuthorisedModule(_module, _value); - if (_value == true) { - modules += 1; - authorised[_module] = true; - Module(_module).init(this); - } else { - modules -= 1; - require(modules > 0, "BW: wallet must have at least one module"); - delete authorised[_module]; - } - } - } - - /** - * @dev Enables a static method by specifying the target module to which the call - * must be delegated. - * @param _module The target module. - * @param _method The static method signature. - */ - function enableStaticCall(address _module, bytes4 _method) external moduleOnly { - require(authorised[_module], "BW: must be an authorised module for static call"); - enabled[_method] = _module; - emit EnabledStaticCall(_module, _method); - } - - /** - * @dev Sets a new owner for the wallet. - * @param _newOwner The new owner. - */ - function setOwner(address _newOwner) external moduleOnly { - require(_newOwner != address(0), "BW: address cannot be null"); - owner = _newOwner; - emit OwnerChanged(_newOwner); - } - - /** - * @dev Performs a generic transaction. - * @param _target The address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invoke(address _target, uint _value, bytes calldata _data) external moduleOnly returns (bytes memory _result) { - bool success; - // solium-disable-next-line security/no-call-value - (success, _result) = _target.call.value(_value)(_data); - if (!success) { - // solium-disable-next-line security/no-inline-assembly - assembly { - returndatacopy(0, 0, returndatasize) - revert(0, returndatasize) - } - } - emit Invoked(msg.sender, _target, _value, _data); - } - - /** - * @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to - * implement specific static methods. It delegates the static call to a target contract if the data corresponds - * to an enabled method, or logs the call otherwise. - */ - function() external payable { - if (msg.data.length > 0) { - address module = enabled[msg.sig]; - if (module == address(0)) { - emit Received(msg.value, msg.sender, msg.data); - } else { - require(authorised[module], "BW: must be an authorised module for static call"); - // solium-disable-next-line security/no-inline-assembly - assembly { - calldatacopy(0, 0, calldatasize()) - let result := staticcall(gas, module, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } - } - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/contracts/wallet/Proxy.sol b/contracts-legacy/v1.6.0/contracts/wallet/Proxy.sol deleted file mode 100644 index 9bbc079cb..000000000 --- a/contracts-legacy/v1.6.0/contracts/wallet/Proxy.sol +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; - -/** - * @title Proxy - * @dev Basic proxy that delegates all calls to a fixed implementing contract. - * The implementing contract cannot be upgraded. - * @author Julien Niset - - */ -contract Proxy { - - address implementation; - - event Received(uint indexed value, address indexed sender, bytes data); - - constructor(address _implementation) public { - implementation = _implementation; - } - - function() external payable { - - if (msg.data.length == 0 && msg.value > 0) { - emit Received(msg.value, msg.sender, msg.data); - } else { - // solium-disable-next-line security/no-inline-assembly - assembly { - let target := sload(0) - calldatacopy(0, 0, calldatasize()) - let result := delegatecall(gas, target, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/compound/CErc20.sol b/contracts-legacy/v1.6.0/lib/compound/CErc20.sol deleted file mode 100644 index 8c6adaaea..000000000 --- a/contracts-legacy/v1.6.0/lib/compound/CErc20.sol +++ /dev/null @@ -1,215 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CToken.sol"; - -/** - * @title Compound's CErc20 Contract - * @notice CTokens which wrap an EIP-20 underlying - * @author Compound - */ -contract CErc20 is CToken { - - /** - * @notice Underlying asset for this CToken - */ - address public underlying; - - /** - * @notice Construct a new money market - * @param underlying_ The address of the underlying asset - * @param comptroller_ The address of the Comptroller - * @param interestRateModel_ The address of the interest rate model - * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 - * @param name_ ERC-20 name of this token - * @param symbol_ ERC-20 symbol of this token - * @param decimals_ ERC-20 decimal precision of this token - */ - constructor(address underlying_, - ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint decimals_) public - CToken(comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_) { - // Set underlying - underlying = underlying_; - EIP20Interface(underlying).totalSupply(); // Sanity check the underlying - } - - /*** User Interface ***/ - - /** - * @notice Sender supplies assets into the market and receives cTokens in exchange - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param mintAmount The amount of the underlying asset to supply - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function mint(uint mintAmount) external returns (uint) { - return mintInternal(mintAmount); - } - - /** - * @notice Sender redeems cTokens in exchange for the underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemTokens The number of cTokens to redeem into underlying - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeem(uint redeemTokens) external returns (uint) { - return redeemInternal(redeemTokens); - } - - /** - * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemAmount The amount of underlying to redeem - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeemUnderlying(uint redeemAmount) external returns (uint) { - return redeemUnderlyingInternal(redeemAmount); - } - - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function borrow(uint borrowAmount) external returns (uint) { - return borrowInternal(borrowAmount); - } - - /** - * @notice Sender repays their own borrow - * @param repayAmount The amount to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function repayBorrow(uint repayAmount) external returns (uint) { - return repayBorrowInternal(repayAmount); - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @param borrower the account with the debt being payed off - * @param repayAmount The amount to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { - return repayBorrowBehalfInternal(borrower, repayAmount); - } - - /** - * @notice The sender liquidates the borrowers collateral. - * The collateral seized is transferred to the liquidator. - * @param borrower The borrower of this cToken to be liquidated - * @param cTokenCollateral The market in which to seize collateral from the borrower - * @param repayAmount The amount of the underlying borrowed asset to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function liquidateBorrow(address borrower, uint repayAmount, CToken cTokenCollateral) external returns (uint) { - return liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); - } - - /*** Safe Token ***/ - - /** - * @notice Gets balance of this contract in terms of the underlying - * @dev This excludes the value of the current message, if any - * @return The quantity of underlying tokens owned by this contract - */ - function getCashPrior() internal view returns (uint) { - EIP20Interface token = EIP20Interface(underlying); - return token.balanceOf(address(this)); - } - - /** - * @dev Checks whether or not there is sufficient allowance for this contract to move amount from `from` and - * whether or not `from` has a balance of at least `amount`. Does NOT do a transfer. - */ - function checkTransferIn(address from, uint amount) internal view returns (Error) { - EIP20Interface token = EIP20Interface(underlying); - - if (token.allowance(from, address(this)) < amount) { - return Error.TOKEN_INSUFFICIENT_ALLOWANCE; - } - - if (token.balanceOf(from) < amount) { - return Error.TOKEN_INSUFFICIENT_BALANCE; - } - - return Error.NO_ERROR; - } - - /** - * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and returns an explanatory - * error code rather than reverting. If caller has not called `checkTransferIn`, this may revert due to - * insufficient balance or insufficient allowance. If caller has called `checkTransferIn` prior to this call, - * and it returned Error.NO_ERROR, this should not revert in normal conditions. - * - * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. - * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca - */ - function doTransferIn(address from, uint amount) internal returns (Error) { - EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); - bool result; - - token.transferFrom(from, address(this), amount); - - // solium-disable-next-line security/no-inline-assembly - assembly { - switch returndatasize() - case 0 { // This is a non-standard ERC-20 - result := not(0) // set result to true - } - case 32 { // This is a complaint ERC-20 - returndatacopy(0, 0, 32) - result := mload(0) // Set `result = returndata` of external call - } - default { // This is an excessively non-compliant ERC-20, revert. - revert(0, 0) - } - } - - if (!result) { - return Error.TOKEN_TRANSFER_IN_FAILED; - } - - return Error.NO_ERROR; - } - - /** - * @dev Similar to EIP20 transfer, except it handles a False result from `transfer` and returns an explanatory - * error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to - * insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified - * it is >= amount, this should not revert in normal conditions. - * - * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. - * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca - */ - function doTransferOut(address payable to, uint amount) internal returns (Error) { - EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); - bool result; - - token.transfer(to, amount); - - // solium-disable-next-line security/no-inline-assembly - assembly { - switch returndatasize() - case 0 { // This is a non-standard ERC-20 - result := not(0) // set result to true - } - case 32 { // This is a complaint ERC-20 - returndatacopy(0, 0, 32) - result := mload(0) // Set `result = returndata` of external call - } - default { // This is an excessively non-compliant ERC-20, revert. - revert(0, 0) - } - } - - if (!result) { - return Error.TOKEN_TRANSFER_OUT_FAILED; - } - - return Error.NO_ERROR; - } -} diff --git a/contracts-legacy/v1.6.0/lib/compound/CEther.sol b/contracts-legacy/v1.6.0/lib/compound/CEther.sol deleted file mode 100644 index a5bad57c2..000000000 --- a/contracts-legacy/v1.6.0/lib/compound/CEther.sol +++ /dev/null @@ -1,169 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CToken.sol"; - -/** - * @title Compound's CEther Contract - * @notice CToken which wraps Ether - * @author Compound - */ -contract CEther is CToken { - - /** - * @notice Construct a new CEther money market - * @param comptroller_ The address of the Comptroller - * @param interestRateModel_ The address of the interest rate model - * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 - * @param name_ ERC-20 name of this token - * @param symbol_ ERC-20 symbol of this token - * @param decimals_ ERC-20 decimal precision of this token - */ - constructor(ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint decimals_) public - CToken(comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_) {} - - /*** User Interface ***/ - - /** - * @notice Sender supplies assets into the market and receives cTokens in exchange - * @dev Reverts upon any failure - */ - function mint() external payable { - requireNoError(mintInternal(msg.value), "mint failed"); - } - - /** - * @notice Sender redeems cTokens in exchange for the underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemTokens The number of cTokens to redeem into underlying - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeem(uint redeemTokens) external returns (uint) { - return redeemInternal(redeemTokens); - } - - /** - * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemAmount The amount of underlying to redeem - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeemUnderlying(uint redeemAmount) external returns (uint) { - return redeemUnderlyingInternal(redeemAmount); - } - - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function borrow(uint borrowAmount) external returns (uint) { - return borrowInternal(borrowAmount); - } - - /** - * @notice Sender repays their own borrow - * @dev Reverts upon any failure - */ - function repayBorrow() external payable { - requireNoError(repayBorrowInternal(msg.value), "repayBorrow failed"); - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @dev Reverts upon any failure - * @param borrower the account with the debt being payed off - */ - function repayBorrowBehalf(address borrower) external payable { - requireNoError(repayBorrowBehalfInternal(borrower, msg.value), "repayBorrowBehalf failed"); - } - - /** - * @notice The sender liquidates the borrowers collateral. - * The collateral seized is transferred to the liquidator. - * @dev Reverts upon any failure - * @param borrower The borrower of this cToken to be liquidated - * @param cTokenCollateral The market in which to seize collateral from the borrower - */ - function liquidateBorrow(address borrower, CToken cTokenCollateral) external payable { - requireNoError(liquidateBorrowInternal(borrower, msg.value, cTokenCollateral), "liquidateBorrow failed"); - } - - /** - * @notice Send Ether to CEther to mint - */ - function () external payable { - requireNoError(mintInternal(msg.value), "mint failed"); - } - - /*** Safe Token ***/ - - /** - * @notice Gets balance of this contract in terms of Ether, before this message - * @dev This excludes the value of the current message, if any - * @return The quantity of Ether owned by this contract - */ - function getCashPrior() internal view returns (uint) { - (MathError err, uint startingBalance) = subUInt(address(this).balance, msg.value); - require(err == MathError.NO_ERROR); - return startingBalance; - } - - /** - * @notice Checks whether the requested transfer matches the `msg` - * @dev Does NOT do a transfer - * @param from Address sending the Ether - * @param amount Amount of Ether being sent - * @return Whether or not the transfer checks out - */ - function checkTransferIn(address from, uint amount) internal view returns (Error) { - // Sanity checks - require(msg.sender == from, "sender mismatch"); - require(msg.value == amount, "value mismatch"); - return Error.NO_ERROR; - } - - /** - * @notice Perform the actual transfer in, which is a no-op - * @param from Address sending the Ether - * @param amount Amount of Ether being sent - * @return Success - */ - function doTransferIn(address from, uint amount) internal returns (Error) { - // Sanity checks - require(msg.sender == from, "sender mismatch"); - require(msg.value == amount, "value mismatch"); - return Error.NO_ERROR; - } - - function doTransferOut(address payable to, uint amount) internal returns (Error) { - /* Send the Ether, with minimal gas and revert on failure */ - to.transfer(amount); - return Error.NO_ERROR; - } - - function requireNoError(uint errCode, string memory message) internal pure { - if (errCode == uint(Error.NO_ERROR)) { - return; - } - - bytes memory fullMessage = new bytes(bytes(message).length + 5); - uint i; - - for (i = 0; i < bytes(message).length; i++) { - fullMessage[i] = bytes(message)[i]; - } - - fullMessage[i+0] = byte(uint8(32)); - fullMessage[i+1] = byte(uint8(40)); - fullMessage[i+2] = byte(uint8(48 + ( errCode / 10 ))); - fullMessage[i+3] = byte(uint8(48 + ( errCode % 10 ))); - fullMessage[i+4] = byte(uint8(41)); - - require(errCode == uint(Error.NO_ERROR), string(fullMessage)); - } -} diff --git a/contracts-legacy/v1.6.0/lib/compound/Comptroller.sol b/contracts-legacy/v1.6.0/lib/compound/Comptroller.sol deleted file mode 100644 index 5f4edb44d..000000000 --- a/contracts-legacy/v1.6.0/lib/compound/Comptroller.sol +++ /dev/null @@ -1,996 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CToken.sol"; -import "./ErrorReporter.sol"; -import "./Exponential.sol"; -import "./PriceOracle.sol"; -import "./ComptrollerInterface.sol"; -import "./ComptrollerStorage.sol"; -import "./Unitroller.sol"; - -/** - * @title Compound's Comptroller Contract - * @author Compound - */ -contract Comptroller is ComptrollerV1Storage, ComptrollerInterface, ComptrollerErrorReporter, Exponential { - struct Market { - /** - * @notice Whether or not this market is listed - */ - bool isListed; - - /** - * @notice Multiplier representing the most one can borrow against their collateral in this market. - * For instance, 0.9 to allow borrowing 90% of collateral value. - * Must be between 0 and 1, and stored as a mantissa. - */ - uint collateralFactorMantissa; - - /** - * @notice Per-market mapping of "accounts in this asset" - */ - mapping(address => bool) accountMembership; - } - - /** - * @notice Official mapping of cTokens -> Market metadata - * @dev Used e.g. to determine if a market is supported - */ - mapping(address => Market) public markets; - - /** - * @notice Emitted when an admin supports a market - */ - event MarketListed(CToken cToken); - - /** - * @notice Emitted when an account enters a market - */ - event MarketEntered(CToken cToken, address account); - - /** - * @notice Emitted when an account exits a market - */ - event MarketExited(CToken cToken, address account); - - /** - * @notice Emitted when close factor is changed by admin - */ - event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); - - /** - * @notice Emitted when a collateral factor is changed by admin - */ - event NewCollateralFactor(CToken cToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); - - /** - * @notice Emitted when liquidation incentive is changed by admin - */ - event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); - - /** - * @notice Emitted when maxAssets is changed by admin - */ - event NewMaxAssets(uint oldMaxAssets, uint newMaxAssets); - - /** - * @notice Emitted when price oracle is changed - */ - event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); - - /** - * @notice Indicator that this is a Comptroller contract (for inspection) - */ - bool public constant isComptroller = true; - - // closeFactorMantissa must be strictly greater than this value - uint constant closeFactorMinMantissa = 5e16; // 0.05 - - // closeFactorMantissa must not exceed this value - uint constant closeFactorMaxMantissa = 9e17; // 0.9 - - // No collateralFactorMantissa may exceed this value - uint constant collateralFactorMaxMantissa = 9e17; // 0.9 - - // liquidationIncentiveMantissa must be no less than this value - uint constant liquidationIncentiveMinMantissa = mantissaOne; - - // liquidationIncentiveMantissa must be no greater than this value - uint constant liquidationIncentiveMaxMantissa = 15e17; // 1.5 - - constructor() public { - admin = msg.sender; - } - - /*** Assets You Are In ***/ - - /** - * @notice Returns the assets an account has entered - * @param account The address of the account to pull assets for - * @return A dynamic list with the assets the account has entered - */ - function getAssetsIn(address account) external view returns (CToken[] memory) { - CToken[] memory assetsIn = accountAssets[account]; - - return assetsIn; - } - - /** - * @notice Returns whether the given account is entered in the given asset - * @param account The address of the account to check - * @param cToken The cToken to check - * @return True if the account is in the asset, otherwise false. - */ - function checkMembership(address account, CToken cToken) external view returns (bool) { - return markets[address(cToken)].accountMembership[account]; - } - - /** - * @notice Add assets to be included in account liquidity calculation - * @param cTokens The list of addresses of the cToken markets to be enabled - * @return Success indicator for whether each corresponding market was entered - */ - function enterMarkets(address[] memory cTokens) public returns (uint[] memory) { - uint len = cTokens.length; - - uint[] memory results = new uint[](len); - for (uint i = 0; i < len; i++) { - CToken cToken = CToken(cTokens[i]); - Market storage marketToJoin = markets[address(cToken)]; - - if (!marketToJoin.isListed) { - // if market is not listed, cannot join move along - results[i] = uint(Error.MARKET_NOT_LISTED); - continue; - } - - if (marketToJoin.accountMembership[msg.sender] == true) { - // if already joined, move along - results[i] = uint(Error.NO_ERROR); - continue; - } - - if (accountAssets[msg.sender].length >= maxAssets) { - // if no space, cannot join, move along - results[i] = uint(Error.TOO_MANY_ASSETS); - continue; - } - - // survived the gauntlet, add to list - // NOTE: we store these somewhat redundantly as a significant optimization - // this avoids having to iterate through the list for the most common use cases - // that is, only when we need to perform liquidity checks - // and not whenever we want to check if an account is in a particular market - marketToJoin.accountMembership[msg.sender] = true; - accountAssets[msg.sender].push(cToken); - - emit MarketEntered(cToken, msg.sender); - - results[i] = uint(Error.NO_ERROR); - } - - return results; - } - - /** - * @notice Removes asset from sender's account liquidity calculation - * @dev Sender must not have an outstanding borrow balance in the asset, - * or be providing neccessary collateral for an outstanding borrow. - * @param cTokenAddress The address of the asset to be removed - * @return Whether or not the account successfully exited the market - */ - function exitMarket(address cTokenAddress) external returns (uint) { - CToken cToken = CToken(cTokenAddress); - /* Get sender tokensHeld and amountOwed underlying from the cToken */ - (uint oErr, uint tokensHeld, uint amountOwed, ) = cToken.getAccountSnapshot(msg.sender); - require(oErr == 0, "exitMarket: getAccountSnapshot failed"); // semi-opaque error code - - /* Fail if the sender has a borrow balance */ - if (amountOwed != 0) { - return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED); - } - - /* Fail if the sender is not permitted to redeem all of their tokens */ - uint allowed = redeemAllowedInternal(cTokenAddress, msg.sender, tokensHeld); - if (allowed != 0) { - return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed); - } - - Market storage marketToExit = markets[address(cToken)]; - - /* Return true if the sender is not already ‘in’ the market */ - if (!marketToExit.accountMembership[msg.sender]) { - return uint(Error.NO_ERROR); - } - - /* Set cToken account membership to false */ - delete marketToExit.accountMembership[msg.sender]; - - /* Delete cToken from the account’s list of assets */ - // load into memory for faster iteration - CToken[] memory userAssetList = accountAssets[msg.sender]; - uint len = userAssetList.length; - uint assetIndex = len; - for (uint i = 0; i < len; i++) { - if (userAssetList[i] == cToken) { - assetIndex = i; - break; - } - } - - // We *must* have found the asset in the list or our redundant data structure is broken - assert(assetIndex < len); - - // copy last item in list to location of item to be removed, reduce length by 1 - CToken[] storage storedList = accountAssets[msg.sender]; - storedList[assetIndex] = storedList[storedList.length - 1]; - storedList.length--; - - emit MarketExited(cToken, msg.sender); - - return uint(Error.NO_ERROR); - } - - /*** Policy Hooks ***/ - - /** - * @notice Checks if the account should be allowed to mint tokens in the given market - * @param cToken The market to verify the mint against - * @param minter The account which would get the minted tokens - * @param mintAmount The amount of underlying being supplied to the market in exchange for tokens - * @return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint) { - minter; // currently unused - mintAmount; // currently unused - - if (!markets[cToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // *may include Policy Hook-type checks - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates mint and reverts on rejection. May emit logs. - * @param cToken Asset being minted - * @param minter The address minting the tokens - * @param mintAmount The amount of the underlying asset being minted - * @param mintTokens The number of tokens being minted - */ - function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external { - cToken; // currently unused - minter; // currently unused - mintAmount; // currently unused - mintTokens; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the account should be allowed to redeem tokens in the given market - * @param cToken The market to verify the redeem against - * @param redeemer The account which would redeem the tokens - * @param redeemTokens The number of cTokens to exchange for the underlying asset in the market - * @return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint) { - return redeemAllowedInternal(cToken, redeemer, redeemTokens); - } - - function redeemAllowedInternal(address cToken, address redeemer, uint redeemTokens) internal view returns (uint) { - if (!markets[cToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // *may include Policy Hook-type checks - - /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */ - if (!markets[cToken].accountMembership[redeemer]) { - return uint(Error.NO_ERROR); - } - - /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */ - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(redeemer, CToken(cToken), redeemTokens, 0); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall > 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates redeem and reverts on rejection. May emit logs. - * @param cToken Asset being redeemed - * @param redeemer The address redeeming the tokens - * @param redeemAmount The amount of the underlying asset being redeemed - * @param redeemTokens The number of tokens being redeemed - */ - function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external { - cToken; // currently unused - redeemer; // currently unused - redeemAmount; // currently unused - redeemTokens; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the account should be allowed to borrow the underlying asset of the given market - * @param cToken The market to verify the borrow against - * @param borrower The account which would borrow the asset - * @param borrowAmount The amount of underlying the account would borrow - * @return 0 if the borrow is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint) { - if (!markets[cToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // *may include Policy Hook-type checks - - if (!markets[cToken].accountMembership[borrower]) { - return uint(Error.MARKET_NOT_ENTERED); - } - - if (oracle.getUnderlyingPrice(CToken(cToken)) == 0) { - return uint(Error.PRICE_ERROR); - } - - (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, CToken(cToken), 0, borrowAmount); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall > 0) { - return uint(Error.INSUFFICIENT_LIQUIDITY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates borrow and reverts on rejection. May emit logs. - * @param cToken Asset whose underlying is being borrowed - * @param borrower The address borrowing the underlying - * @param borrowAmount The amount of the underlying asset requested to borrow - */ - function borrowVerify(address cToken, address borrower, uint borrowAmount) external { - cToken; // currently unused - borrower; // currently unused - borrowAmount; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the account should be allowed to repay a borrow in the given market - * @param cToken The market to verify the repay against - * @param payer The account which would repay the asset - * @param borrower The account which would borrowed the asset - * @param repayAmount The amount of the underlying asset the account would repay - * @return 0 if the repay is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function repayBorrowAllowed( - address cToken, - address payer, - address borrower, - uint repayAmount) external returns (uint) { - payer; // currently unused - borrower; // currently unused - repayAmount; // currently unused - - if (!markets[cToken].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // *may include Policy Hook-type checks - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates repayBorrow and reverts on rejection. May emit logs. - * @param cToken Asset being repaid - * @param payer The address repaying the borrow - * @param borrower The address of the borrower - * @param repayAmount The amount of underlying being repaid - */ - function repayBorrowVerify( - address cToken, - address payer, - address borrower, - uint repayAmount, - uint borrowerIndex) external { - cToken; // currently unused - payer; // currently unused - borrower; // currently unused - repayAmount; // currently unused - borrowerIndex; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the liquidation should be allowed to occur - * @param cTokenBorrowed Asset which was borrowed by the borrower - * @param cTokenCollateral Asset which was used as collateral and will be seized - * @param liquidator The address repaying the borrow and seizing the collateral - * @param borrower The address of the borrower - * @param repayAmount The amount of underlying being repaid - */ - function liquidateBorrowAllowed( - address cTokenBorrowed, - address cTokenCollateral, - address liquidator, - address borrower, - uint repayAmount) external returns (uint) { - liquidator; // currently unused - borrower; // currently unused - repayAmount; // currently unused - - if (!markets[cTokenBorrowed].isListed || !markets[cTokenCollateral].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - // *may include Policy Hook-type checks - - /* The borrower must have shortfall in order to be liquidatable */ - (Error err, , uint shortfall) = getAccountLiquidityInternal(borrower); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); - } - - /* The liquidator may not repay more than what is allowed by the closeFactor */ - uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored(borrower); - (MathError mathErr, uint maxClose) = mulScalarTruncate(Exp({mantissa: closeFactorMantissa}), borrowBalance); - if (mathErr != MathError.NO_ERROR) { - return uint(Error.MATH_ERROR); - } - if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); - } - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates liquidateBorrow and reverts on rejection. May emit logs. - * @param cTokenBorrowed Asset which was borrowed by the borrower - * @param cTokenCollateral Asset which was used as collateral and will be seized - * @param liquidator The address repaying the borrow and seizing the collateral - * @param borrower The address of the borrower - * @param repayAmount The amount of underlying being repaid - */ - function liquidateBorrowVerify( - address cTokenBorrowed, - address cTokenCollateral, - address liquidator, - address borrower, - uint repayAmount, - uint seizeTokens) external { - cTokenBorrowed; // currently unused - cTokenCollateral; // currently unused - liquidator; // currently unused - borrower; // currently unused - repayAmount; // currently unused - seizeTokens; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the seizing of assets should be allowed to occur - * @param cTokenCollateral Asset which was used as collateral and will be seized - * @param cTokenBorrowed Asset which was borrowed by the borrower - * @param liquidator The address repaying the borrow and seizing the collateral - * @param borrower The address of the borrower - * @param seizeTokens The number of collateral tokens to seize - */ - function seizeAllowed( - address cTokenCollateral, - address cTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens) external returns (uint) { - liquidator; // currently unused - borrower; // currently unused - seizeTokens; // currently unused - - if (!markets[cTokenCollateral].isListed || !markets[cTokenBorrowed].isListed) { - return uint(Error.MARKET_NOT_LISTED); - } - - if (CToken(cTokenCollateral).comptroller() != CToken(cTokenBorrowed).comptroller()) { - return uint(Error.COMPTROLLER_MISMATCH); - } - - // *may include Policy Hook-type checks - - return uint(Error.NO_ERROR); - } - - /** - * @notice Validates seize and reverts on rejection. May emit logs. - * @param cTokenCollateral Asset which was used as collateral and will be seized - * @param cTokenBorrowed Asset which was borrowed by the borrower - * @param liquidator The address repaying the borrow and seizing the collateral - * @param borrower The address of the borrower - * @param seizeTokens The number of collateral tokens to seize - */ - function seizeVerify( - address cTokenCollateral, - address cTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens) external { - cTokenCollateral; // currently unused - cTokenBorrowed; // currently unused - liquidator; // currently unused - borrower; // currently unused - seizeTokens; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /** - * @notice Checks if the account should be allowed to transfer tokens in the given market - * @param cToken The market to verify the transfer against - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of cTokens to transfer - * @return 0 if the transfer is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol) - */ - function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint) { - cToken; // currently unused - src; // currently unused - dst; // currently unused - transferTokens; // currently unused - - // *may include Policy Hook-type checks - - // Currently the only consideration is whether or not - // the src is allowed to redeem this many tokens - return redeemAllowedInternal(cToken, src, transferTokens); - } - - /** - * @notice Validates transfer and reverts on rejection. May emit logs. - * @param cToken Asset being transferred - * @param src The account which sources the tokens - * @param dst The account which receives the tokens - * @param transferTokens The number of cTokens to transfer - */ - function transferVerify(address cToken, address src, address dst, uint transferTokens) external { - cToken; // currently unused - src; // currently unused - dst; // currently unused - transferTokens; // currently unused - - if (false) { - maxAssets = maxAssets; // not pure - } - } - - /*** Liquidity/Liquidation Calculations ***/ - - /** - * @dev Local vars for avoiding stack-depth limits in calculating account liquidity. - * Note that `cTokenBalance` is the number of cTokens the account owns in the market, - * whereas `borrowBalance` is the amount of underlying that the account has borrowed. - */ - struct AccountLiquidityLocalVars { - uint sumCollateral; - uint sumBorrowPlusEffects; - uint cTokenBalance; - uint borrowBalance; - uint exchangeRateMantissa; - uint oraclePriceMantissa; - Exp collateralFactor; - Exp exchangeRate; - Exp oraclePrice; - Exp tokensToEther; - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code (semi-opaque), - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidity(address account) public view returns (uint, uint, uint) { - (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, CToken(0), 0, 0); - - return (uint(err), liquidity, shortfall); - } - - /** - * @notice Determine the current account liquidity wrt collateral requirements - * @return (possible error code, - account liquidity in excess of collateral requirements, - * account shortfall below collateral requirements) - */ - function getAccountLiquidityInternal(address account) internal view returns (Error, uint, uint) { - return getHypotheticalAccountLiquidityInternal(account, CToken(0), 0, 0); - } - - /** - * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed - * @param cTokenModify The market to hypothetically redeem/borrow in - * @param account The account to determine liquidity for - * @param redeemTokens The number of tokens to hypothetically redeem - * @param borrowAmount The amount of underlying to hypothetically borrow - * @dev Note that we calculate the exchangeRateStored for each collateral cToken using stored data, - * without calculating accumulated interest. - * @return (possible error code, - hypothetical account liquidity in excess of collateral requirements, - * hypothetical account shortfall below collateral requirements) - */ - function getHypotheticalAccountLiquidityInternal( - address account, - CToken cTokenModify, - uint redeemTokens, - uint borrowAmount) internal view returns (Error, uint, uint) { - - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - MathError mErr; - - // For each asset the account is in - CToken[] memory assets = accountAssets[account]; - for (uint i = 0; i < assets.length; i++) { - CToken asset = assets[i]; - - // Read the balances and exchange rate from the cToken - (oErr, vars.cTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot(account); - if (oErr != 0) { // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0); - } - vars.collateralFactor = Exp({mantissa: markets[address(asset)].collateralFactorMantissa}); - vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa}); - - // Get the normalized price of the asset - vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset); - if (vars.oraclePriceMantissa == 0) { - return (Error.PRICE_ERROR, 0, 0); - } - vars.oraclePrice = Exp({mantissa: vars.oraclePriceMantissa}); - - // Pre-compute a conversion factor from tokens -> ether (normalized price value) - (mErr, vars.tokensToEther) = mulExp3(vars.collateralFactor, vars.exchangeRate, vars.oraclePrice); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumCollateral += tokensToEther * cTokenBalance - (mErr, vars.sumCollateral) = mulScalarTruncateAddUInt(vars.tokensToEther, vars.cTokenBalance, vars.sumCollateral); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // sumBorrowPlusEffects += oraclePrice * borrowBalance - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt(vars.oraclePrice, vars.borrowBalance, vars.sumBorrowPlusEffects); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // Calculate effects of interacting with cTokenModify - if (asset == cTokenModify) { - // redeem effect - // sumBorrowPlusEffects += tokensToEther * redeemTokens - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt(vars.tokensToEther, redeemTokens, vars.sumBorrowPlusEffects); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - - // borrow effect - // sumBorrowPlusEffects += oraclePrice * borrowAmount - (mErr, vars.sumBorrowPlusEffects) = mulScalarTruncateAddUInt(vars.oraclePrice, borrowAmount, vars.sumBorrowPlusEffects); - if (mErr != MathError.NO_ERROR) { - return (Error.MATH_ERROR, 0, 0); - } - } - } - - // These are safe, as the underflow condition is checked first - if (vars.sumCollateral > vars.sumBorrowPlusEffects) { - return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0); - } else { - return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral); - } - } - - /** - * @notice Calculate number of tokens of collateral asset to seize given an underlying amount - * @dev Used in liquidation (called in cToken.liquidateBorrowFresh) - * @param cTokenBorrowed The address of the borrowed cToken - * @param cTokenCollateral The address of the collateral cToken - * @param repayAmount The amount of cTokenBorrowed underlying to convert into cTokenCollateral tokens - * @return (errorCode, number of cTokenCollateral tokens to be seized in a liquidation) - */ - function liquidateCalculateSeizeTokens(address cTokenBorrowed, address cTokenCollateral, uint repayAmount) external view returns (uint, uint) { - /* Read oracle prices for borrowed and collateral markets */ - uint priceBorrowedMantissa = oracle.getUnderlyingPrice(CToken(cTokenBorrowed)); - uint priceCollateralMantissa = oracle.getUnderlyingPrice(CToken(cTokenCollateral)); - if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) { - return (uint(Error.PRICE_ERROR), 0); - } - - /* - * Get the exchange rate and calculate the number of collateral tokens to seize: - * seizeAmount = repayAmount * liquidationIncentive * priceBorrowed / priceCollateral - * seizeTokens = seizeAmount / exchangeRate - * = repayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate) - */ - uint exchangeRateMantissa = CToken(cTokenCollateral).exchangeRateStored(); // Note: reverts on error - uint seizeTokens; - Exp memory numerator; - Exp memory denominator; - Exp memory ratio; - MathError mathErr; - - (mathErr, numerator) = mulExp(liquidationIncentiveMantissa, priceBorrowedMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, denominator) = mulExp(priceCollateralMantissa, exchangeRateMantissa); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, ratio) = divExp(numerator, denominator); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - (mathErr, seizeTokens) = mulScalarTruncate(ratio, repayAmount); - if (mathErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0); - } - - return (uint(Error.NO_ERROR), seizeTokens); - } - - /*** Admin Functions ***/ - - /** - * @notice Sets a new price oracle for the comptroller - * @dev Admin function to set a new price oracle - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setPriceOracle(PriceOracle newOracle) public returns (uint) { - // Check caller is admin OR currently initialzing as new unitroller implementation - if (!adminOrInitializing()) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK); - } - - // Track the old oracle for the comptroller - PriceOracle oldOracle = oracle; - - // Ensure invoke newOracle.isPriceOracle() returns true - // require(newOracle.isPriceOracle(), "oracle method isPriceOracle returned false"); - - // Set comptroller's oracle to newOracle - oracle = newOracle; - - // Emit NewPriceOracle(oldOracle, newOracle) - emit NewPriceOracle(oldOracle, newOracle); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the closeFactor used when liquidating borrows - * @dev Admin function to set closeFactor - * @param newCloseFactorMantissa New close factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint256) { - // Check caller is admin OR currently initialzing as new unitroller implementation - if (!adminOrInitializing()) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_CLOSE_FACTOR_OWNER_CHECK); - } - - Exp memory newCloseFactorExp = Exp({mantissa: newCloseFactorMantissa}); - Exp memory lowLimit = Exp({mantissa: closeFactorMinMantissa}); - if (lessThanOrEqualExp(newCloseFactorExp, lowLimit)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - Exp memory highLimit = Exp({mantissa: closeFactorMaxMantissa}); - if (lessThanExp(highLimit, newCloseFactorExp)) { - return fail(Error.INVALID_CLOSE_FACTOR, FailureInfo.SET_CLOSE_FACTOR_VALIDATION); - } - - uint oldCloseFactorMantissa = closeFactorMantissa; - closeFactorMantissa = newCloseFactorMantissa; - emit NewCloseFactor(oldCloseFactorMantissa, closeFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets the collateralFactor for a market - * @dev Admin function to set per-market collateralFactor - * @param cToken The market to set the factor on - * @param newCollateralFactorMantissa The new collateral factor, scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setCollateralFactor(CToken cToken, uint newCollateralFactorMantissa) external returns (uint256) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK); - } - - // Verify market is listed - Market storage market = markets[address(cToken)]; - if (!market.isListed) { - return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS); - } - - Exp memory newCollateralFactorExp = Exp({mantissa: newCollateralFactorMantissa}); - - // Check collateral factor <= 0.9 - Exp memory highLimit = Exp({mantissa: collateralFactorMaxMantissa}); - if (lessThanExp(highLimit, newCollateralFactorExp)) { - return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION); - } - - // If collateral factor != 0, fail if price == 0 - /* NOTE: For some reason we need to comment this to deploy on Ganache */ - // if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(cToken) == 0) { - // return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE); - // } - - // Set market's collateral factor to new collateral factor, remember old value - uint oldCollateralFactorMantissa = market.collateralFactorMantissa; - market.collateralFactorMantissa = newCollateralFactorMantissa; - - // Emit event with asset, old collateral factor, and new collateral factor - emit NewCollateralFactor(cToken, oldCollateralFactorMantissa, newCollateralFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets maxAssets which controls how many markets can be entered - * @dev Admin function to set maxAssets - * @param newMaxAssets New max assets - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setMaxAssets(uint newMaxAssets) external returns (uint) { - // Check caller is admin OR currently initialzing as new unitroller implementation - if (!adminOrInitializing()) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_MAX_ASSETS_OWNER_CHECK); - } - - uint oldMaxAssets = maxAssets; - maxAssets = newMaxAssets; - emit NewMaxAssets(oldMaxAssets, newMaxAssets); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets liquidationIncentive - * @dev Admin function to set liquidationIncentive - * @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18 - * @return uint 0=success, otherwise a failure. (See ErrorReporter for details) - */ - function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) { - // Check caller is admin OR currently initialzing as new unitroller implementation - if (!adminOrInitializing()) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK); - } - - // Check de-scaled 1 <= newLiquidationDiscount <= 1.5 - Exp memory newLiquidationIncentive = Exp({mantissa: newLiquidationIncentiveMantissa}); - Exp memory minLiquidationIncentive = Exp({mantissa: liquidationIncentiveMinMantissa}); - if (lessThanExp(newLiquidationIncentive, minLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - Exp memory maxLiquidationIncentive = Exp({mantissa: liquidationIncentiveMaxMantissa}); - if (lessThanExp(maxLiquidationIncentive, newLiquidationIncentive)) { - return fail(Error.INVALID_LIQUIDATION_INCENTIVE, FailureInfo.SET_LIQUIDATION_INCENTIVE_VALIDATION); - } - - // Save current value for use in log - uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa; - - // Set liquidation incentive to new incentive - liquidationIncentiveMantissa = newLiquidationIncentiveMantissa; - - // Emit event with old incentive, new incentive - emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Add the market to the markets mapping and set it as listed - * @dev Admin function to set isListed and add support for the market - * @param cToken The address of the market (token) to list - * @return uint 0=success, otherwise a failure. (See enum Error for details) - */ - function _supportMarket(CToken cToken) external returns (uint) { - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SUPPORT_MARKET_OWNER_CHECK); - } - - if (markets[address(cToken)].isListed) { - return fail(Error.MARKET_ALREADY_LISTED, FailureInfo.SUPPORT_MARKET_EXISTS); - } - - cToken.isCToken(); // Sanity check to make sure its really a CToken - - markets[address(cToken)] = Market({isListed: true, collateralFactorMantissa: 0}); - emit MarketListed(cToken); - - return uint(Error.NO_ERROR); - } - - function _become(Unitroller unitroller, PriceOracle _oracle, uint _closeFactorMantissa, uint _maxAssets, bool reinitializing) public { - require(msg.sender == unitroller.admin(), "only unitroller admin can change brains"); - uint changeStatus = unitroller._acceptImplementation(); - - require(changeStatus == 0, "change not authorized"); - - if (!reinitializing) { - Comptroller freshBrainedComptroller = Comptroller(address(unitroller)); - - // Ensure invoke _setPriceOracle() = 0 - uint err = freshBrainedComptroller._setPriceOracle(_oracle); - require (err == uint(Error.NO_ERROR), "set price oracle error"); - - // Ensure invoke _setCloseFactor() = 0 - err = freshBrainedComptroller._setCloseFactor(_closeFactorMantissa); - require (err == uint(Error.NO_ERROR), "set close factor error"); - - // Ensure invoke _setMaxAssets() = 0 - err = freshBrainedComptroller._setMaxAssets(_maxAssets); - require (err == uint(Error.NO_ERROR), "set max asssets error"); - - // Ensure invoke _setLiquidationIncentive(liquidationIncentiveMinMantissa) = 0 - err = freshBrainedComptroller._setLiquidationIncentive(liquidationIncentiveMinMantissa); - require (err == uint(Error.NO_ERROR), "set liquidation incentive error"); - } - } - - /** - * @dev Check that caller is admin or this contract is initializing itself as - * the new implementation. - * There should be no way to satisfy msg.sender == comptrollerImplementaiton - * without tx.origin also being admin, but both are included for extra safety - */ - function adminOrInitializing() internal view returns (bool) { - bool initializing = ( - msg.sender == comptrollerImplementation - && - //solium-disable-next-line security/no-tx-origin - tx.origin == admin - ); - bool isAdmin = msg.sender == admin; - return isAdmin || initializing; - } -} diff --git a/contracts-legacy/v1.6.0/lib/compound/Unitroller.sol b/contracts-legacy/v1.6.0/lib/compound/Unitroller.sol deleted file mode 100644 index 58004c40f..000000000 --- a/contracts-legacy/v1.6.0/lib/compound/Unitroller.sol +++ /dev/null @@ -1,152 +0,0 @@ -pragma solidity ^0.5.4; - -import "./ErrorReporter.sol"; -import "./ComptrollerStorage.sol"; -/** - * @title ComptrollerCore - * @dev storage for the comptroller will be at this address, and - * cTokens should reference this contract rather than a deployed implementation if - * - */ -contract Unitroller is UnitrollerAdminStorage, ComptrollerErrorReporter { - - /** - * @notice Emitted when pendingComptrollerImplementation is changed - */ - event NewPendingImplementation(address oldPendingImplementation, address newPendingImplementation); - - /** - * @notice Emitted when pendingComptrollerImplementation is accepted, which means comptroller implementation is updated - */ - event NewImplementation(address oldImplementation, address newImplementation); - - /** - * @notice Emitted when pendingAdmin is changed - */ - event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); - - /** - * @notice Emitted when pendingAdmin is accepted, which means admin is updated - */ - event NewAdmin(address oldAdmin, address newAdmin); - - constructor() public { - // Set admin to caller - admin = msg.sender; - } - - /*** Admin Functions ***/ - function _setPendingImplementation(address newPendingImplementation) public returns (uint) { - - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_IMPLEMENTATION_OWNER_CHECK); - } - - address oldPendingImplementation = pendingComptrollerImplementation; - - pendingComptrollerImplementation = newPendingImplementation; - - emit NewPendingImplementation(oldPendingImplementation, pendingComptrollerImplementation); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accepts new implementation of comptroller. msg.sender must be pendingImplementation - * @dev Admin function for new implementation to accept it's role as implementation - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _acceptImplementation() public returns (uint) { - // Check caller is pendingImplementation and pendingImplementation ≠ address(0) - if (msg.sender != pendingComptrollerImplementation || pendingComptrollerImplementation == address(0)) { - return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK); - } - - // Save current values for inclusion in log - address oldImplementation = comptrollerImplementation; - address oldPendingImplementation = pendingComptrollerImplementation; - - comptrollerImplementation = pendingComptrollerImplementation; - - pendingComptrollerImplementation = address(0); - - emit NewImplementation(oldImplementation, comptrollerImplementation); - emit NewPendingImplementation(oldPendingImplementation, pendingComptrollerImplementation); - - return uint(Error.NO_ERROR); - } - - - /** - * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. - * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. - * @param newPendingAdmin New pending admin. - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - * - * TODO: Should we add a second arg to verify, like a checksum of `newAdmin` address? - */ - function _setPendingAdmin(address newPendingAdmin) public returns (uint) { - // Check caller = admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK); - } - - // Save current value, if any, for inclusion in log - address oldPendingAdmin = pendingAdmin; - - // Store pendingAdmin with value newPendingAdmin - pendingAdmin = newPendingAdmin; - - // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) - emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin - * @dev Admin function for pending admin to accept role and update admin - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _acceptAdmin() public returns (uint) { - // Check caller is pendingAdmin and pendingAdmin ≠ address(0) - if (msg.sender != pendingAdmin || msg.sender == address(0)) { - return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK); - } - - // Save current values for inclusion in log - address oldAdmin = admin; - address oldPendingAdmin = pendingAdmin; - - // Store admin with value pendingAdmin - admin = pendingAdmin; - - // Clear the pending value - pendingAdmin = address(0); - - emit NewAdmin(oldAdmin, admin); - emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); - - return uint(Error.NO_ERROR); - } - - /** - * @dev Delegates execution to an implementation contract. - * It returns to the external caller whatever the implementation returns - * or forwards reverts. - */ - function () payable external { - // delegate all other functions to current implementation - (bool success, ) = comptrollerImplementation.delegatecall(msg.data); - - // solium-disable-next-line security/no-inline-assembly - assembly { - let free_mem_ptr := mload(0x40) - returndatacopy(free_mem_ptr, 0, returndatasize) - - switch success - case 0 { revert(free_mem_ptr, returndatasize) } - default { return(free_mem_ptr, returndatasize) } - } - } -} diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSAuth.sol b/contracts-legacy/v1.6.0/lib/maker/DS/DSAuth.sol deleted file mode 100644 index 4cc112532..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/DS/DSAuth.sol +++ /dev/null @@ -1,71 +0,0 @@ -// 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 . - -pragma solidity ^0.5.4; - -contract DSAuthority { - function canCall( - address src, address dst, bytes4 sig - ) - public - view - returns (bool); -} - -contract DSAuthEvents { - event LogSetAuthority (address indexed authority); - event LogSetOwner (address indexed owner); -} - -contract DSAuth is DSAuthEvents { - DSAuthority public authority; - address public owner; - - constructor() public { - owner = msg.sender; - emit LogSetOwner(msg.sender); - } - - function setOwner(address owner_) - public - auth - { - owner = owner_; - emit LogSetOwner(owner); - } - - function setAuthority(DSAuthority authority_) - public - auth - { - authority = authority_; - emit LogSetAuthority(address(authority)); - } - - modifier auth { - require(isAuthorized(msg.sender, msg.sig), "auth: not authorized"); - _; - } - - function isAuthorized(address src, bytes4 sig) internal view returns (bool) { - if (src == address(this)) { - return true; - } else if (src == owner) { - return true; - } else if (authority == DSAuthority(0)) { - return false; - } else { - return authority.canCall(src, address(this), sig); - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSMath.sol b/contracts-legacy/v1.6.0/lib/maker/DS/DSMath.sol deleted file mode 100644 index 9fb97b49a..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/DS/DSMath.sol +++ /dev/null @@ -1,84 +0,0 @@ -/// math.sol -- mixin for inline numerical wizardry - -// 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 . - -pragma solidity ^0.5.4; - -contract DSMath { - function add(uint x, uint y) internal pure returns (uint z) { - ((z = x + y) >= x); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x); - } - - function min(uint x, uint y) internal pure returns (uint z) { - return x <= y ? x : y; - } - function max(uint x, uint y) internal pure returns (uint z) { - return x >= y ? x : y; - } - function imin(int x, int y) internal pure returns (int z) { - return x <= y ? x : y; - } - function imax(int x, int y) internal pure returns (int z) { - return x >= y ? x : y; - } - - uint constant WAD = 10 ** 18; - uint constant RAY = 10 ** 27; - - function wmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), WAD / 2) / WAD; - } - function rmul(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, y), RAY / 2) / RAY; - } - function wdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, WAD), y / 2) / y; - } - function rdiv(uint x, uint y) internal pure returns (uint z) { - z = add(mul(x, RAY), y / 2) / y; - } - - // This famous algorithm is called "exponentiation by squaring" - // and calculates x^n with x as fixed-point and n as regular unsigned. - // - // It's O(log n), instead of O(n) for naive repeated multiplication. - // - // These facts are why it works: - // - // If n is even, then x^n = (x^2)^(n/2). - // If n is odd, then x^n = x * x^(n-1), - // and applying the equation for even x gives - // x^n = x * (x^2)^((n-1) / 2). - // - // Also, EVM division is flooring and - // floor[(n-1) / 2] = floor[n / 2]. - // - function rpow(uint x, uint n) internal pure returns (uint z) { - z = n % 2 != 0 ? x : RAY; - - for (n /= 2; n != 0; n /= 2) { - x = rmul(x, x); - - if (n % 2 != 0) { - z = rmul(z, x); - } - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSToken.sol b/contracts-legacy/v1.6.0/lib/maker/DS/DSToken.sol deleted file mode 100644 index 740967e55..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/DS/DSToken.sol +++ /dev/null @@ -1,161 +0,0 @@ -/// token.sol -- ERC20 implementation with minting and burning - -// Copyright (C) 2015, 2016, 2017 DappHub, LLC - -// 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 . - -pragma solidity ^0.5.4; - -import "./DSMath.sol"; -import "./DSStop.sol"; - -contract ERC20Events { - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); -} - -contract IERC20 is ERC20Events { - function totalSupply() public view returns (uint); - function balanceOf(address guy) public view returns (uint); - function allowance(address src, address guy) public view returns (uint); - - function approve(address guy, uint wad) public returns (bool); - function transfer(address dst, uint wad) public returns (bool); - function transferFrom(address src, address dst, uint wad) public returns (bool); -} - -contract DSTokenBase is IERC20, DSMath { - uint256 _supply; - mapping (address => uint256) _balances; - mapping (address => mapping (address => uint256)) _approvals; - - constructor(uint supply) public { - _balances[msg.sender] = supply; - _supply = supply; - } - - function totalSupply() public view returns (uint) { - return _supply; - } - function balanceOf(address src) public view returns (uint) { - return _balances[src]; - } - function allowance(address src, address guy) public view returns (uint) { - return _approvals[src][guy]; - } - - function transfer(address dst, uint wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { - if (src != msg.sender) { - _approvals[src][msg.sender] = sub(_approvals[src][msg.sender], wad); - } - - _balances[src] = sub(_balances[src], wad); - _balances[dst] = add(_balances[dst], wad); - - emit Transfer(src, dst, wad); - - return true; - } - - function approve(address guy, uint wad) public returns (bool) { - _approvals[msg.sender][guy] = wad; - - emit Approval(msg.sender, guy, wad); - - return true; - } -} - - -contract DSToken is DSTokenBase(0), DSStop { - - bytes32 public symbol; - uint256 public decimals = 18; // standard token precision. override to customize - - constructor(bytes32 symbol_) public { - symbol = symbol_; - } - - event Mint(address indexed guy, uint wad); - event Burn(address indexed guy, uint wad); - - function approve(address guy) public returns (bool) { - return super.approve(guy, uint(-1)); - } - - function approve(address guy, uint wad) public returns (bool) { - return super.approve(guy, wad); - } - - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { - if (src != msg.sender && _approvals[src][msg.sender] != uint(-1)) { - _approvals[src][msg.sender] = sub(_approvals[src][msg.sender], wad); - } - - _balances[src] = sub(_balances[src], wad); - _balances[dst] = add(_balances[dst], wad); - - emit Transfer(src, dst, wad); - - return true; - } - - function push(address dst, uint wad) public { - transferFrom(msg.sender, dst, wad); - } - function pull(address src, uint wad) public { - transferFrom(src, msg.sender, wad); - } - function move(address src, address dst, uint wad) public { - transferFrom(src, dst, wad); - } - - function mint(uint wad) public { - mint(msg.sender, wad); - } - function burn(uint wad) public { - burn(msg.sender, wad); - } - function mint(address guy, uint wad) public { - _balances[guy] = add(_balances[guy], wad); - _supply = add(_supply, wad); - emit Mint(guy, wad); - } - function burn(address guy, uint wad) public auth { - if (guy != msg.sender && _approvals[guy][msg.sender] != uint(-1)) { - _approvals[guy][msg.sender] = sub(_approvals[guy][msg.sender], wad); - } - - _balances[guy] = sub(_balances[guy], wad); - _supply = sub(_supply, wad); - emit Burn(guy, wad); - } - - // Optional token name - bytes32 public name = ""; - - function setName(bytes32 name_) public auth { - name = name_; - } -} diff --git a/contracts-legacy/v1.6.0/lib/maker/DssCdpManager.sol b/contracts-legacy/v1.6.0/lib/maker/DssCdpManager.sol deleted file mode 100644 index 32cb28a9c..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/DssCdpManager.sol +++ /dev/null @@ -1,257 +0,0 @@ -pragma solidity ^0.5.4; - -import { LibNote } from "./lib.sol"; - -contract VatLike { - function urns(bytes32, address) public view returns (uint, uint); - function hope(address) public; - function flux(bytes32, address, address, uint) public; - function move(address, address, uint) public; - function frob(bytes32, address, address, address, int, int) public; - function fork(bytes32, address, address, int, int) public; -} - -contract UrnHandler { - constructor(address vat) public { - VatLike(vat).hope(msg.sender); - } -} - -contract DssCdpManager is LibNote { - address public vat; - uint public cdpi; // Auto incremental - mapping (uint => address) public urns; // CDPId => UrnHandler - mapping (uint => List) public list; // CDPId => Prev & Next CDPIds (double linked list) - mapping (uint => address) public owns; // CDPId => Owner - mapping (uint => bytes32) public ilks; // CDPId => Ilk - - mapping (address => uint) public first; // Owner => First CDPId - mapping (address => uint) public last; // Owner => Last CDPId - mapping (address => uint) public count; // Owner => Amount of CDPs - - mapping ( - address => mapping ( - uint => mapping ( - address => uint - ) - ) - ) public cdpCan; // Owner => CDPId => Allowed Addr => True/False - - mapping ( - address => mapping ( - address => uint - ) - ) public urnCan; // Urn => Allowed Addr => True/False - - struct List { - uint prev; - uint next; - } - - event NewCdp(address indexed usr, address indexed own, uint indexed cdp); - - modifier cdpAllowed( - uint cdp - ) { - require(msg.sender == owns[cdp] || cdpCan[owns[cdp]][cdp][msg.sender] == 1, "cdp-not-allowed"); - _; - } - - modifier urnAllowed( - address urn - ) { - require(msg.sender == urn || urnCan[urn][msg.sender] == 1, "urn-not-allowed"); - _; - } - - constructor(address vat_) public { - vat = vat_; - } - - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x); - } - - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - - function toInt(uint x) internal pure returns (int y) { - y = int(x); - require(y >= 0); - } - - // Allow/disallow a usr address to manage the cdp. - function cdpAllow( - uint cdp, - address usr, - uint ok - ) public cdpAllowed(cdp) { - cdpCan[owns[cdp]][cdp][usr] = ok; - } - - // Allow/disallow a usr address to quit to the the sender urn. - function urnAllow( - address usr, - uint ok - ) public { - urnCan[msg.sender][usr] = ok; - } - - // Open a new cdp for a given usr address. - function open( - bytes32 ilk, - address usr - ) public note returns (uint) { - require(usr != address(0), "usr-address-0"); - - cdpi = add(cdpi, 1); - urns[cdpi] = address(new UrnHandler(vat)); - owns[cdpi] = usr; - ilks[cdpi] = ilk; - - // Add new CDP to double linked list and pointers - if (first[usr] == 0) { - first[usr] = cdpi; - } - if (last[usr] != 0) { - list[cdpi].prev = last[usr]; - list[last[usr]].next = cdpi; - } - last[usr] = cdpi; - count[usr] = add(count[usr], 1); - - emit NewCdp(msg.sender, usr, cdpi); - return cdpi; - } - - // Give the cdp ownership to a dst address. - function give( - uint cdp, - address dst - ) public note cdpAllowed(cdp) { - require(dst != address(0), "dst-address-0"); - require(dst != owns[cdp], "dst-already-owner"); - - // Remove transferred CDP from double linked list of origin user and pointers - if (list[cdp].prev != 0) { - list[list[cdp].prev].next = list[cdp].next; // Set the next pointer of the prev cdp (if exists) to the next of the transferred one - } - if (list[cdp].next != 0) { // If wasn't the last one - list[list[cdp].next].prev = list[cdp].prev; // Set the prev pointer of the next cdp to the prev of the transferred one - } else { // If was the last one - last[owns[cdp]] = list[cdp].prev; // Update last pointer of the owner - } - if (first[owns[cdp]] == cdp) { // If was the first one - first[owns[cdp]] = list[cdp].next; // Update first pointer of the owner - } - count[owns[cdp]] = sub(count[owns[cdp]], 1); - - // Transfer ownership - owns[cdp] = dst; - - // Add transferred CDP to double linked list of destiny user and pointers - list[cdp].prev = last[dst]; - list[cdp].next = 0; - if (last[dst] != 0) { - list[last[dst]].next = cdp; - } - if (first[dst] == 0) { - first[dst] = cdp; - } - last[dst] = cdp; - count[dst] = add(count[dst], 1); - } - - // Frob the cdp keeping the generated DAI or collateral freed in the cdp urn address. - function frob( - uint cdp, - int dink, - int dart - ) public note cdpAllowed(cdp) { - address urn = urns[cdp]; - VatLike(vat).frob( - ilks[cdp], - urn, - urn, - urn, - dink, - dart - ); - } - - // Transfer wad amount of cdp collateral from the cdp address to a dst address. - function flux( - uint cdp, - address dst, - uint wad - ) public note cdpAllowed(cdp) { - VatLike(vat).flux(ilks[cdp], urns[cdp], dst, wad); - } - - // Transfer wad amount of any type of collateral (ilk) from the cdp address to a dst address. - // This function has the purpose to take away collateral from the system that doesn't correspond to the cdp but was sent there wrongly. - function flux( - bytes32 ilk, - uint cdp, - address dst, - uint wad - ) public note cdpAllowed(cdp) { - VatLike(vat).flux(ilk, urns[cdp], dst, wad); - } - - // Transfer wad amount of DAI from the cdp address to a dst address. - function move( - uint cdp, - address dst, - uint rad - ) public note cdpAllowed(cdp) { - VatLike(vat).move(urns[cdp], dst, rad); - } - - // Quit the system, migrating the cdp (ink, art) to a different dst urn - function quit( - uint cdp, - address dst - ) public note cdpAllowed(cdp) urnAllowed(dst) { - (uint ink, uint art) = VatLike(vat).urns(ilks[cdp], urns[cdp]); - VatLike(vat).fork( - ilks[cdp], - urns[cdp], - dst, - toInt(ink), - toInt(art) - ); - } - - // Import a position from src urn to the urn owned by cdp - function enter( - address src, - uint cdp - ) public note urnAllowed(src) cdpAllowed(cdp) { - (uint ink, uint art) = VatLike(vat).urns(ilks[cdp], src); - VatLike(vat).fork( - ilks[cdp], - src, - urns[cdp], - toInt(ink), - toInt(art) - ); - } - - // Move a position from cdpSrc urn to the cdpDst urn - function shift( - uint cdpSrc, - uint cdpDst - ) public note cdpAllowed(cdpSrc) cdpAllowed(cdpDst) { - require(ilks[cdpSrc] == ilks[cdpDst], "non-matching-cdps"); - (uint ink, uint art) = VatLike(vat).urns(ilks[cdpSrc], urns[cdpSrc]); - VatLike(vat).fork( - ilks[cdpSrc], - urns[cdpSrc], - urns[cdpDst], - toInt(ink), - toInt(art) - ); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/MakerInterfaces.sol b/contracts-legacy/v1.6.0/lib/maker/MakerInterfaces.sol deleted file mode 100644 index 085dfb896..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/MakerInterfaces.sol +++ /dev/null @@ -1,92 +0,0 @@ -pragma solidity ^0.5.4; - -contract GemLike { - function balanceOf(address) public view returns (uint); - function transferFrom(address, address, uint) public returns (bool); - function approve(address, uint) public returns (bool success); - function decimals() public view returns (uint); - function transfer(address,uint) external returns (bool); -} - -contract DSTokenLike { - function mint(address,uint) external; - function burn(address,uint) external; -} - -contract VatLike { - function can(address, address) public view returns (uint); - function dai(address) public view returns (uint); - function hope(address) public; - function wards(address) public view returns (uint); - function ilks(bytes32) public view returns (uint Art, uint rate, uint spot, uint line, uint dust); - function urns(bytes32, address) public view returns (uint ink, uint art); - function frob(bytes32, address, address, address, int, int) public; - function slip(bytes32,address,int) external; - function move(address,address,uint) external; -} - -contract JoinLike { - function ilk() public view returns (bytes32); - function gem() public view returns (GemLike); - function dai() public view returns (GemLike); - function join(address, uint) public; - function exit(address, uint) public; - VatLike public vat; - uint public live; -} - -contract ManagerLike { - function vat() public view returns (address); - function urns(uint) public view returns (address); - function open(bytes32, address) public returns (uint); - function frob(uint, int, int) public; - function give(uint, address) public; - function move(uint, address, uint) public; - function flux(uint, address, uint) public; - function shift(uint, uint) public; - mapping (uint => bytes32) public ilks; - mapping (uint => address) public owns; -} - -contract ScdMcdMigrationLike { - function swapSaiToDai(uint) public; - function swapDaiToSai(uint) public; - function migrate(bytes32) public returns (uint); - JoinLike public saiJoin; - JoinLike public wethJoin; - JoinLike public daiJoin; - ManagerLike public cdpManager; - SaiTubLike public tub; -} - -contract ValueLike { - function peek() public returns (uint, bool); -} - -contract SaiTubLike { - function skr() public view returns (GemLike); - function gem() public view returns (GemLike); - function gov() public view returns (GemLike); - function sai() public view returns (GemLike); - function pep() public view returns (ValueLike); - function bid(uint) public view returns (uint); - function ink(bytes32) public view returns (uint); - function tab(bytes32) public returns (uint); - function rap(bytes32) public returns (uint); - function shut(bytes32) public; - function exit(uint) public; -} - -contract VoxLike { - function par() public returns (uint); -} - -contract JugLike { - function drip(bytes32) external; -} - -contract PotLike { - function chi() public view returns (uint); - function pie(address) public view returns (uint); - function drip() public; -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/MockScdMcdMigration.sol b/contracts-legacy/v1.6.0/lib/maker/MockScdMcdMigration.sol deleted file mode 100644 index 1bc7b0956..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/MockScdMcdMigration.sol +++ /dev/null @@ -1,63 +0,0 @@ -pragma solidity ^0.5.4; - -import "./MakerInterfaces.sol"; - -contract MockVat is VatLike { - function can(address, address) public view returns (uint) { return 1; } - function dai(address) public view returns (uint) { return 0; } - function hope(address) public {} - function wards(address) public view returns (uint) { return 1; } - function ilks(bytes32) public view returns (uint, uint, uint, uint, uint) { return (0, 0, 0, 0, 0); } - function urns(bytes32, address) public view returns (uint, uint) { return (0, 0); } - function frob(bytes32, address, address, address, int, int) public {} - function slip(bytes32,address,int) external {} - function move(address,address,uint) external {} -} - -contract MockTub is SaiTubLike { - function gov() public view returns (GemLike) { return GemLike(address(0)); } - function skr() public view returns (GemLike) { return GemLike(address(0)); } - function gem() public view returns (GemLike) { return GemLike(address(0)); } - function sai() public view returns (GemLike) { return GemLike(address(0)); } - function pep() public view returns (ValueLike) { return ValueLike(address(0)); } - function rap(bytes32) public returns (uint) { return 0; } - function give(bytes32, address) public {} - function tab(bytes32) public returns (uint) { return 0; } - function bid(uint) public view returns (uint) { return 0; } - function ink(bytes32) public view returns (uint) { return 0; } - function shut(bytes32) public {} - function exit(uint) public {} -} - -contract MockJoin is JoinLike { - MockVat public vat; - constructor (MockVat _vat) public { vat = _vat; } - function ilk() public view returns (bytes32) { return bytes32(0); } - function gem() public view returns (GemLike) { return GemLike(address(0)); } - function dai() public view returns (GemLike) { return GemLike(address(0)); } - function join(address, uint) public {} - function exit(address, uint) public {} -} - -/** - * @title MockScdMcdMigration - * @dev Mock contract needed to deploy the MakerV2Manager contract - */ -contract MockScdMcdMigration { - - MockJoin public daiJoin; - MockJoin public wethJoin; - MockTub public tub; - ManagerLike public cdpManager; - MockVat public vat; - - constructor (address _vat, address _daiJoin, address _wethJoin, address _tub, address _cdpManager) public { - vat = (_vat != address(0)) ? MockVat(_vat) : new MockVat(); - daiJoin = (_daiJoin != address(0)) ? MockJoin(_daiJoin) : new MockJoin(vat); - wethJoin = (_wethJoin != address(0)) ? MockJoin(_wethJoin) : new MockJoin(vat); - tub = (_tub != address(0)) ? MockTub(_tub) : new MockTub(); - if (_cdpManager != address(0)) { - cdpManager = ManagerLike(_cdpManager); - } - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/WETH9.sol b/contracts-legacy/v1.6.0/lib/maker/WETH9.sol deleted file mode 100644 index d4235c5b3..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/WETH9.sol +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2015, 2016, 2017 Dapphub - -// 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 . - -pragma solidity ^0.5.4; - -contract WETH9 { - string public name = "Wrapped Ether"; - string public symbol = "WETH"; - uint8 public decimals = 18; - - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); - - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; - - function() external payable { - deposit(); - } - function deposit() public payable { - balanceOf[msg.sender] += msg.value; - emit Deposit(msg.sender, msg.value); - } - function withdraw(uint wad) public { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; - msg.sender.transfer(wad); - emit Withdrawal(msg.sender, wad); - } - - function totalSupply() public view returns (uint) { - return address(this).balance; - } - - function approve(address guy, uint wad) public returns (bool) { - allowance[msg.sender][guy] = wad; - emit Approval(msg.sender, guy, wad); - return true; - } - - function transfer(address dst, uint wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { - require(balanceOf[src] >= wad); - - if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; - } - - balanceOf[src] -= wad; - balanceOf[dst] += wad; - - emit Transfer(src, dst, wad); - - return true; - } -} - diff --git a/contracts-legacy/v1.6.0/lib/maker/jug.sol b/contracts-legacy/v1.6.0/lib/maker/jug.sol deleted file mode 100644 index 33a7788e7..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/jug.sol +++ /dev/null @@ -1,108 +0,0 @@ -pragma solidity ^0.5.4; - -import "./lib.sol"; - -contract VatLike { - function ilks(bytes32) external returns ( - uint256 Art, // wad - uint256 rate // ray - ); - function fold(bytes32,address,int) external; -} - -contract Jug is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external note auth { wards[usr] = 1; } - function deny(address usr) external note auth { wards[usr] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "Jug/not-authorized"); - _; - } - - // --- Data --- - struct Ilk { - uint256 duty; - uint256 rho; - } - - mapping (bytes32 => Ilk) public ilks; - VatLike public vat; - address public vow; - uint256 public base; - - // --- Init --- - constructor(address vat_) public { - wards[msg.sender] = 1; - vat = VatLike(vat_); - } - - // --- Math --- - function rpow(uint x, uint n, uint b) internal pure returns (uint z) { - assembly { - switch x case 0 {switch n case 0 {z := b} default {z := 0}} - default { - switch mod(n, 2) case 0 { z := b } default { z := x } - let half := div(b, 2) // for rounding. - for { n := div(n, 2) } n { n := div(n,2) } { - let xx := mul(x, x) - if iszero(eq(div(xx, x), x)) { revert(0,0) } - let xxRound := add(xx, half) - if lt(xxRound, xx) { revert(0,0) } - x := div(xxRound, b) - if mod(n,2) { - let zx := mul(z, x) - if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } - let zxRound := add(zx, half) - if lt(zxRound, zx) { revert(0,0) } - z := div(zxRound, b) - } - } - } - } - } - uint256 constant ONE = 10 ** 27; - function add(uint x, uint y) internal pure returns (uint z) { - z = x + y; - require(z >= x); - } - function diff(uint x, uint y) internal pure returns (int z) { - z = int(x) - int(y); - require(int(x) >= 0 && int(y) >= 0); - } - function rmul(uint x, uint y) internal pure returns (uint z) { - z = x * y; - require(y == 0 || z / y == x); - z = z / ONE; - } - - // --- Administration --- - function init(bytes32 ilk) external note auth { - Ilk storage i = ilks[ilk]; - require(i.duty == 0, "Jug/ilk-already-init"); - i.duty = ONE; - i.rho = now; - } - function file(bytes32 ilk, bytes32 what, uint data) external note auth { - require(now == ilks[ilk].rho, "Jug/rho-not-updated"); - if (what == "duty") ilks[ilk].duty = data; - else revert("Jug/file-unrecognized-param"); - } - function file(bytes32 what, uint data) external note auth { - if (what == "base") base = data; - else revert("Jug/file-unrecognized-param"); - } - function file(bytes32 what, address data) external note auth { - if (what == "vow") vow = data; - else revert("Jug/file-unrecognized-param"); - } - - // --- Stability Fee Collection --- - function drip(bytes32 ilk) external note returns (uint rate) { - require(now >= ilks[ilk].rho, "Jug/invalid-now"); - (, uint prev) = vat.ilks(ilk); - rate = rmul(rpow(add(base, ilks[ilk].duty), now - ilks[ilk].rho, ONE), prev); - vat.fold(ilk, vow, diff(rate, prev)); - ilks[ilk].rho = now; - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/pot.sol b/contracts-legacy/v1.6.0/lib/maker/pot.sol deleted file mode 100644 index 8b618f0a3..000000000 --- a/contracts-legacy/v1.6.0/lib/maker/pot.sol +++ /dev/null @@ -1,162 +0,0 @@ -/// pot.sol -- Dai Savings Rate - -// Copyright (C) 2018 Rain -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero 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 Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.4; - -import "./lib.sol"; - -/* - "Savings Dai" is obtained when Dai is deposited into - this contract. Each "Savings Dai" accrues Dai interest - at the "Dai Savings Rate". - - This contract does not implement a user tradeable token - and is intended to be used with adapters. - - --- `save` your `dai` in the `pot` --- - - - `dsr`: the Dai Savings Rate - - `pie`: user balance of Savings Dai - - - `join`: start saving some dai - - `exit`: remove some dai - - `drip`: perform rate collection - -*/ - -contract VatLike { - function move(address,address,uint256) external; - function suck(address,address,uint256) external; -} - -contract Pot is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address guy) external note auth { wards[guy] = 1; } - function deny(address guy) external note auth { wards[guy] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "Pot/not-authorized"); - _; - } - - // --- Data --- - mapping (address => uint256) public pie; // user Savings Dai - - uint256 public Pie; // total Savings Dai - uint256 public dsr; // the Dai Savings Rate - uint256 public chi; // the Rate Accumulator - - VatLike public vat; // CDP engine - address public vow; // debt engine - uint256 public rho; // time of last drip - - uint256 public live; // Access Flag - - // --- Init --- - constructor(address vat_) public { - wards[msg.sender] = 1; - vat = VatLike(vat_); - dsr = ONE; - chi = ONE; - rho = now; - live = 1; - } - - // --- Math --- - uint256 constant ONE = 10 ** 27; - function rpow(uint x, uint n, uint base) internal pure returns (uint z) { - assembly { - switch x case 0 {switch n case 0 {z := base} default {z := 0}} - default { - switch mod(n, 2) case 0 { z := base } default { z := x } - let half := div(base, 2) // for rounding. - for { n := div(n, 2) } n { n := div(n,2) } { - let xx := mul(x, x) - if iszero(eq(div(xx, x), x)) { revert(0,0) } - let xxRound := add(xx, half) - if lt(xxRound, xx) { revert(0,0) } - x := div(xxRound, base) - if mod(n,2) { - let zx := mul(z, x) - if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } - let zxRound := add(zx, half) - if lt(zxRound, zx) { revert(0,0) } - z := div(zxRound, base) - } - } - } - } - } - - function rmul(uint x, uint y) internal pure returns (uint z) { - z = mul(x, y) / ONE; - } - - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x); - } - - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x); - } - - // --- Administration --- - function file(bytes32 what, uint256 data) external note auth { - require(live == 1, "Pot/not-live"); - require(now == rho, "Pot/rho-not-updated"); - if (what == "dsr") dsr = data; - else revert("Pot/file-unrecognized-param"); - } - - function file(bytes32 what, address addr) external note auth { - if (what == "vow") vow = addr; - else revert("Pot/file-unrecognized-param"); - } - - function cage() external note auth { - live = 0; - dsr = ONE; - } - - // --- Savings Rate Accumulation --- - function drip() external note returns (uint tmp) { - require(now >= rho, "Pot/invalid-now"); - tmp = rmul(rpow(dsr, now - rho, ONE), chi); - uint chi_ = sub(tmp, chi); - chi = tmp; - rho = now; - vat.suck(address(vow), address(this), mul(Pie, chi_)); - } - - // --- Savings Dai Management --- - function join(uint wad) external note { - require(now == rho, "Pot/rho-not-updated"); - pie[msg.sender] = add(pie[msg.sender], wad); - Pie = add(Pie, wad); - vat.move(msg.sender, address(this), mul(chi, wad)); - } - - function exit(uint wad) external note { - pie[msg.sender] = sub(pie[msg.sender], wad); - Pie = sub(Pie, wad); - vat.move(address(this), msg.sender, mul(chi, wad)); - } -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/other/ERC20.sol b/contracts-legacy/v1.6.0/lib/other/ERC20.sol deleted file mode 100644 index 516240990..000000000 --- a/contracts-legacy/v1.6.0/lib/other/ERC20.sol +++ /dev/null @@ -1,14 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * ERC20 contract interface. - */ -contract ERC20 { - function totalSupply() public view returns (uint); - function decimals() public view returns (uint); - function balanceOf(address tokenOwner) public view returns (uint balance); - function allowance(address tokenOwner, address spender) public view returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); -} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/other/KyberNetwork.sol b/contracts-legacy/v1.6.0/lib/other/KyberNetwork.sol deleted file mode 100644 index 1c3272f38..000000000 --- a/contracts-legacy/v1.6.0/lib/other/KyberNetwork.sol +++ /dev/null @@ -1,27 +0,0 @@ -pragma solidity ^0.5.4; -import "./ERC20.sol"; - -contract KyberNetwork { - - function getExpectedRate( - ERC20 src, - ERC20 dest, - uint srcQty - ) - public - view - returns (uint expectedRate, uint slippageRate); - - function trade( - ERC20 src, - uint srcAmount, - ERC20 dest, - address payable destAddress, - uint maxDestAmount, - uint minConversionRate, - address walletId - ) - public - payable - returns(uint); -} diff --git a/contracts-legacy/v1.6.0/lib/utils/SafeMath.sol b/contracts-legacy/v1.6.0/lib/utils/SafeMath.sol deleted file mode 100644 index 73a49b9e5..000000000 --- a/contracts-legacy/v1.6.0/lib/utils/SafeMath.sol +++ /dev/null @@ -1,119 +0,0 @@ -/* The MIT License (MIT) - -Copyright (c) 2016 Smart Contract Solutions, Inc. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -pragma solidity ^0.5.4; - -/** - * @title SafeMath - * @dev Math operations with safety checks that throw on error - */ -library SafeMath { - - /** - * @dev Multiplies two numbers, reverts on overflow. - */ - function mul(uint256 a, uint256 b) internal pure returns (uint256) { - // Gas optimization: this is cheaper than requiring 'a' not being zero, but the - // benefit is lost if 'b' is also tested. - // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 - if (a == 0) { - return 0; - } - - uint256 c = a * b; - require(c / a == b); - - return c; - } - - /** - * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. - */ - function div(uint256 a, uint256 b) internal pure returns (uint256) { - require(b > 0); // Solidity only automatically asserts when dividing by 0 - uint256 c = a / b; - // assert(a == b * c + a % b); // There is no case in which this doesn't hold - - return c; - } - - /** - * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). - */ - function sub(uint256 a, uint256 b) internal pure returns (uint256) { - require(b <= a); - uint256 c = a - b; - - return c; - } - - /** - * @dev Adds two numbers, reverts on overflow. - */ - function add(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a + b; - require(c >= a); - - return c; - } - - /** - * @dev Divides two numbers and returns the remainder (unsigned integer modulo), - * reverts when dividing by zero. - */ - function mod(uint256 a, uint256 b) internal pure returns (uint256) { - require(b != 0); - return a % b; - } - - /** - * @dev Returns ceil(a / b). - */ - function ceil(uint256 a, uint256 b) internal pure returns (uint256) { - uint256 c = a / b; - if(a % b == 0) { - return c; - } - else { - return c + 1; - } - } - - // from DSMath - operations on fixed precision floats - - uint256 constant WAD = 10 ** 18; - uint256 constant RAY = 10 ** 27; - - function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = add(mul(x, y), WAD / 2) / WAD; - } - function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = add(mul(x, y), RAY / 2) / RAY; - } - function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = add(mul(x, WAD), y / 2) / y; - } - function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) { - z = add(mul(x, RAY), y / 2) / y; - } -} diff --git a/contracts-legacy/v1.6.0/lib/utils/strings.sol b/contracts-legacy/v1.6.0/lib/utils/strings.sol deleted file mode 100644 index 6a46b8d8e..000000000 --- a/contracts-legacy/v1.6.0/lib/utils/strings.sol +++ /dev/null @@ -1,718 +0,0 @@ -/* - * @title String & slice utility library for Solidity contracts. - * @author Nick Johnson - * - * @dev Functionality in this library is largely implemented using an - * abstraction called a 'slice'. A slice represents a part of a string - - * anything from the entire string to a single character, or even no - * characters at all (a 0-length slice). Since a slice only has to specify - * an offset and a length, copying and manipulating slices is a lot less - * expensive than copying and manipulating the strings they reference. - * - * To further reduce gas costs, most functions on slice that need to return - * a slice modify the original one instead of allocating a new one; for - * instance, `s.split(".")` will return the text up to the first '.', - * modifying s to only contain the remainder of the string after the '.'. - * In situations where you do not want to modify the original slice, you - * can make a copy first with `.copy()`, for example: - * `s.copy().split(".")`. Try and avoid using this idiom in loops; since - * Solidity has no memory management, it will result in allocating many - * short-lived slices that are later discarded. - * - * Functions that return two slices come in two versions: a non-allocating - * version that takes the second slice as an argument, modifying it in - * place, and an allocating version that allocates and returns the second - * slice; see `nextRune` for example. - * - * Functions that have to copy string data will return strings rather than - * slices; these can be cast back to slices for further processing if - * required. - * - * For convenience, some functions are provided with non-modifying - * variants that create a new slice and return both; for instance, - * `s.splitNew('.')` leaves s unmodified, and returns two values - * corresponding to the left and right parts of the string. - */ - -// pragma solidity ^0.4.14; -pragma solidity ^0.5.4; - -/* solium-disable */ -library strings { - struct slice { - uint _len; - uint _ptr; - } - - function memcpy(uint dest, uint src, uint len) private pure { - // Copy word-length chunks while possible - for(; len >= 32; len -= 32) { - assembly { - mstore(dest, mload(src)) - } - dest += 32; - src += 32; - } - - // Copy remaining bytes - uint mask = 256 ** (32 - len) - 1; - assembly { - let srcpart := and(mload(src), not(mask)) - let destpart := and(mload(dest), mask) - mstore(dest, or(destpart, srcpart)) - } - } - - /* - * @dev Returns a slice containing the entire string. - * @param self The string to make a slice from. - * @return A newly allocated slice containing the entire string. - */ - function toSlice(string memory self) internal pure returns (slice memory) { - uint ptr; - assembly { - ptr := add(self, 0x20) - } - return slice(bytes(self).length, ptr); - } - - /* - * @dev Returns the length of a null-terminated bytes32 string. - * @param self The value to find the length of. - * @return The length of the string, from 0 to 32. - */ - function len(bytes32 self) internal pure returns (uint) { - uint ret; - if (self == 0) - return 0; - if (uint256(self) & 0xffffffffffffffffffffffffffffffff == 0) { - ret += 16; - self = bytes32(uint(self) / 0x100000000000000000000000000000000); - } - if (uint256(self) & 0xffffffffffffffff == 0) { - ret += 8; - self = bytes32(uint(self) / 0x10000000000000000); - } - if (uint256(self) & 0xffffffff == 0) { - ret += 4; - self = bytes32(uint(self) / 0x100000000); - } - if (uint256(self) & 0xffff == 0) { - ret += 2; - self = bytes32(uint(self) / 0x10000); - } - if (uint256(self) & 0xff == 0) { - ret += 1; - } - return 32 - ret; - } - - /* - * @dev Returns a slice containing the entire bytes32, interpreted as a - * null-terminated utf-8 string. - * @param self The bytes32 value to convert to a slice. - * @return A new slice containing the value of the input argument up to the - * first null. - */ - function toSliceB32(bytes32 self) internal pure returns (slice memory ret) { - // Allocate space for `self` in memory, copy it there, and point ret at it - assembly { - let ptr := mload(0x40) - mstore(0x40, add(ptr, 0x20)) - mstore(ptr, self) - mstore(add(ret, 0x20), ptr) - } - ret._len = len(self); - } - - /* - * @dev Returns a new slice containing the same data as the current slice. - * @param self The slice to copy. - * @return A new slice containing the same data as `self`. - */ - function copy(slice memory self) internal pure returns (slice memory) { - return slice(self._len, self._ptr); - } - - /* - * @dev Copies a slice to a new string. - * @param self The slice to copy. - * @return A newly allocated string containing the slice's text. - */ - function toString(slice memory self) internal pure returns (string memory) { - string memory ret = new string(self._len); - uint retptr; - assembly { retptr := add(ret, 32) } - - memcpy(retptr, self._ptr, self._len); - return ret; - } - - /* - * @dev Returns the length in runes of the slice. Note that this operation - * takes time proportional to the length of the slice; avoid using it - * in loops, and call `slice.empty()` if you only need to know whether - * the slice is empty or not. - * @param self The slice to operate on. - * @return The length of the slice in runes. - */ - function len(slice memory self) internal pure returns (uint l) { - // Starting at ptr-31 means the LSB will be the byte we care about - uint ptr = self._ptr - 31; - uint end = ptr + self._len; - for (l = 0; ptr < end; l++) { - uint8 b; - assembly { b := and(mload(ptr), 0xFF) } - if (b < 0x80) { - ptr += 1; - } else if(b < 0xE0) { - ptr += 2; - } else if(b < 0xF0) { - ptr += 3; - } else if(b < 0xF8) { - ptr += 4; - } else if(b < 0xFC) { - ptr += 5; - } else { - ptr += 6; - } - } - } - - /* - * @dev Returns true if the slice is empty (has a length of 0). - * @param self The slice to operate on. - * @return True if the slice is empty, False otherwise. - */ - function empty(slice memory self) internal pure returns (bool) { - return self._len == 0; - } - - /* - * @dev Returns a positive number if `other` comes lexicographically after - * `self`, a negative number if it comes before, or zero if the - * contents of the two slices are equal. Comparison is done per-rune, - * on unicode codepoints. - * @param self The first slice to compare. - * @param other The second slice to compare. - * @return The result of the comparison. - */ - function compare(slice memory self, slice memory other) internal pure returns (int) { - uint shortest = self._len; - if (other._len < self._len) - shortest = other._len; - - uint selfptr = self._ptr; - uint otherptr = other._ptr; - for (uint idx = 0; idx < shortest; idx += 32) { - uint a; - uint b; - assembly { - a := mload(selfptr) - b := mload(otherptr) - } - if (a != b) { - // Mask out irrelevant bytes and check again - uint256 mask = uint256(-1); // 0xffff... - if(shortest < 32) { - mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); - } - uint256 diff = (a & mask) - (b & mask); - if (diff != 0) - return int(diff); - } - selfptr += 32; - otherptr += 32; - } - return int(self._len) - int(other._len); - } - - /* - * @dev Returns true if the two slices contain the same text. - * @param self The first slice to compare. - * @param self The second slice to compare. - * @return True if the slices are equal, false otherwise. - */ - function equals(slice memory self, slice memory other) internal pure returns (bool) { - return compare(self, other) == 0; - } - - /* - * @dev Extracts the first rune in the slice into `rune`, advancing the - * slice to point to the next rune and returning `self`. - * @param self The slice to operate on. - * @param rune The slice that will contain the first rune. - * @return `rune`. - */ - function nextRune(slice memory self, slice memory rune) internal pure returns (slice memory) { - rune._ptr = self._ptr; - - if (self._len == 0) { - rune._len = 0; - return rune; - } - - uint l; - uint b; - // Load the first byte of the rune into the LSBs of b - assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) } - if (b < 0x80) { - l = 1; - } else if(b < 0xE0) { - l = 2; - } else if(b < 0xF0) { - l = 3; - } else { - l = 4; - } - - // Check for truncated codepoints - if (l > self._len) { - rune._len = self._len; - self._ptr += self._len; - self._len = 0; - return rune; - } - - self._ptr += l; - self._len -= l; - rune._len = l; - return rune; - } - - /* - * @dev Returns the first rune in the slice, advancing the slice to point - * to the next rune. - * @param self The slice to operate on. - * @return A slice containing only the first rune from `self`. - */ - function nextRune(slice memory self) internal pure returns (slice memory ret) { - nextRune(self, ret); - } - - /* - * @dev Returns the number of the first codepoint in the slice. - * @param self The slice to operate on. - * @return The number of the first codepoint in the slice. - */ - function ord(slice memory self) internal pure returns (uint ret) { - if (self._len == 0) { - return 0; - } - - uint word; - uint length; - uint divisor = 2 ** 248; - - // Load the rune into the MSBs of b - assembly { word:= mload(mload(add(self, 32))) } - uint b = word / divisor; - if (b < 0x80) { - ret = b; - length = 1; - } else if(b < 0xE0) { - ret = b & 0x1F; - length = 2; - } else if(b < 0xF0) { - ret = b & 0x0F; - length = 3; - } else { - ret = b & 0x07; - length = 4; - } - - // Check for truncated codepoints - if (length > self._len) { - return 0; - } - - for (uint i = 1; i < length; i++) { - divisor = divisor / 256; - b = (word / divisor) & 0xFF; - if (b & 0xC0 != 0x80) { - // Invalid UTF-8 sequence - return 0; - } - ret = (ret * 64) | (b & 0x3F); - } - - return ret; - } - - /* - * @dev Returns the keccak-256 hash of the slice. - * @param self The slice to hash. - * @return The hash of the slice. - */ - function keccak(slice memory self) internal pure returns (bytes32 ret) { - assembly { - ret := keccak256(mload(add(self, 32)), mload(self)) - } - } - - /* - * @dev Returns true if `self` starts with `needle`. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return True if the slice starts with the provided text, false otherwise. - */ - function startsWith(slice memory self, slice memory needle) internal pure returns (bool) { - if (self._len < needle._len) { - return false; - } - - if (self._ptr == needle._ptr) { - return true; - } - - bool equal; - assembly { - let length := mload(needle) - let selfptr := mload(add(self, 0x20)) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - return equal; - } - - /* - * @dev If `self` starts with `needle`, `needle` is removed from the - * beginning of `self`. Otherwise, `self` is unmodified. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return `self` - */ - function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) { - if (self._len < needle._len) { - return self; - } - - bool equal = true; - if (self._ptr != needle._ptr) { - assembly { - let length := mload(needle) - let selfptr := mload(add(self, 0x20)) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - } - - if (equal) { - self._len -= needle._len; - self._ptr += needle._len; - } - - return self; - } - - /* - * @dev Returns true if the slice ends with `needle`. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return True if the slice starts with the provided text, false otherwise. - */ - function endsWith(slice memory self, slice memory needle) internal pure returns (bool) { - if (self._len < needle._len) { - return false; - } - - uint selfptr = self._ptr + self._len - needle._len; - - if (selfptr == needle._ptr) { - return true; - } - - bool equal; - assembly { - let length := mload(needle) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - - return equal; - } - - /* - * @dev If `self` ends with `needle`, `needle` is removed from the - * end of `self`. Otherwise, `self` is unmodified. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return `self` - */ - function until(slice memory self, slice memory needle) internal pure returns (slice memory) { - if (self._len < needle._len) { - return self; - } - - uint selfptr = self._ptr + self._len - needle._len; - bool equal = true; - if (selfptr != needle._ptr) { - assembly { - let length := mload(needle) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - } - - if (equal) { - self._len -= needle._len; - } - - return self; - } - - // Returns the memory address of the first byte of the first occurrence of - // `needle` in `self`, or the first byte after `self` if not found. - function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { - uint ptr = selfptr; - uint idx; - - if (needlelen <= selflen) { - if (needlelen <= 32) { - bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); - - bytes32 needledata; - assembly { needledata := and(mload(needleptr), mask) } - - uint end = selfptr + selflen - needlelen; - bytes32 ptrdata; - assembly { ptrdata := and(mload(ptr), mask) } - - while (ptrdata != needledata) { - if (ptr >= end) - return selfptr + selflen; - ptr++; - assembly { ptrdata := and(mload(ptr), mask) } - } - return ptr; - } else { - // For long needles, use hashing - bytes32 hash; - assembly { hash := keccak256(needleptr, needlelen) } - - for (idx = 0; idx <= selflen - needlelen; idx++) { - bytes32 testHash; - assembly { testHash := keccak256(ptr, needlelen) } - if (hash == testHash) - return ptr; - ptr += 1; - } - } - } - return selfptr + selflen; - } - - // Returns the memory address of the first byte after the last occurrence of - // `needle` in `self`, or the address of `self` if not found. - function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { - uint ptr; - - if (needlelen <= selflen) { - if (needlelen <= 32) { - bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); - - bytes32 needledata; - assembly { needledata := and(mload(needleptr), mask) } - - ptr = selfptr + selflen - needlelen; - bytes32 ptrdata; - assembly { ptrdata := and(mload(ptr), mask) } - - while (ptrdata != needledata) { - if (ptr <= selfptr) - return selfptr; - ptr--; - assembly { ptrdata := and(mload(ptr), mask) } - } - return ptr + needlelen; - } else { - // For long needles, use hashing - bytes32 hash; - assembly { hash := keccak256(needleptr, needlelen) } - ptr = selfptr + (selflen - needlelen); - while (ptr >= selfptr) { - bytes32 testHash; - assembly { testHash := keccak256(ptr, needlelen) } - if (hash == testHash) - return ptr + needlelen; - ptr -= 1; - } - } - } - return selfptr; - } - - /* - * @dev Modifies `self` to contain everything from the first occurrence of - * `needle` to the end of the slice. `self` is set to the empty slice - * if `needle` is not found. - * @param self The slice to search and modify. - * @param needle The text to search for. - * @return `self`. - */ - function find(slice memory self, slice memory needle) internal pure returns (slice memory) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); - self._len -= ptr - self._ptr; - self._ptr = ptr; - return self; - } - - /* - * @dev Modifies `self` to contain the part of the string from the start of - * `self` to the end of the first occurrence of `needle`. If `needle` - * is not found, `self` is set to the empty slice. - * @param self The slice to search and modify. - * @param needle The text to search for. - * @return `self`. - */ - function rfind(slice memory self, slice memory needle) internal pure returns (slice memory) { - uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); - self._len = ptr - self._ptr; - return self; - } - - /* - * @dev Splits the slice, setting `self` to everything after the first - * occurrence of `needle`, and `token` to everything before it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and `token` is set to the entirety of `self`. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @param token An output parameter to which the first token is written. - * @return `token`. - */ - function split(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); - token._ptr = self._ptr; - token._len = ptr - self._ptr; - if (ptr == self._ptr + self._len) { - // Not found - self._len = 0; - } else { - self._len -= token._len + needle._len; - self._ptr = ptr + needle._len; - } - return token; - } - - /* - * @dev Splits the slice, setting `self` to everything after the first - * occurrence of `needle`, and returning everything before it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and the entirety of `self` is returned. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @return The part of `self` up to the first occurrence of `delim`. - */ - function split(slice memory self, slice memory needle) internal pure returns (slice memory token) { - split(self, needle, token); - } - - /* - * @dev Splits the slice, setting `self` to everything before the last - * occurrence of `needle`, and `token` to everything after it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and `token` is set to the entirety of `self`. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @param token An output parameter to which the first token is written. - * @return `token`. - */ - function rsplit(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { - uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); - token._ptr = ptr; - token._len = self._len - (ptr - self._ptr); - if (ptr == self._ptr) { - // Not found - self._len = 0; - } else { - self._len -= token._len + needle._len; - } - return token; - } - - /* - * @dev Splits the slice, setting `self` to everything before the last - * occurrence of `needle`, and returning everything after it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and the entirety of `self` is returned. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @return The part of `self` after the last occurrence of `delim`. - */ - function rsplit(slice memory self, slice memory needle) internal pure returns (slice memory token) { - rsplit(self, needle, token); - } - - /* - * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`. - * @param self The slice to search. - * @param needle The text to search for in `self`. - * @return The number of occurrences of `needle` found in `self`. - */ - function count(slice memory self, slice memory needle) internal pure returns (uint cnt) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len; - while (ptr <= self._ptr + self._len) { - cnt++; - ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len; - } - } - - /* - * @dev Returns True if `self` contains `needle`. - * @param self The slice to search. - * @param needle The text to search for in `self`. - * @return True if `needle` is found in `self`, false otherwise. - */ - function contains(slice memory self, slice memory needle) internal pure returns (bool) { - return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr; - } - - /* - * @dev Returns a newly allocated string containing the concatenation of - * `self` and `other`. - * @param self The first slice to concatenate. - * @param other The second slice to concatenate. - * @return The concatenation of the two strings. - */ - function concat(slice memory self, slice memory other) internal pure returns (string memory) { - string memory ret = new string(self._len + other._len); - uint retptr; - assembly { retptr := add(ret, 32) } - memcpy(retptr, self._ptr, self._len); - memcpy(retptr + self._len, other._ptr, other._len); - return ret; - } - - /* - * @dev Joins an array of slices, using `self` as a delimiter, returning a - * newly allocated string. - * @param self The delimiter to use. - * @param parts A list of slices to join. - * @return A newly allocated string containing all the slices in `parts`, - * joined with `self`. - */ - function join(slice memory self, slice[] memory parts) internal pure returns (string memory) { - if (parts.length == 0) - return ""; - - uint length = self._len * (parts.length - 1); - for(uint i = 0; i < parts.length; i++) - length += parts[i]._len; - - string memory ret = new string(length); - uint retptr; - assembly { retptr := add(ret, 32) } - - for(uint i = 0; i < parts.length; i++) { - memcpy(retptr, parts[i]._ptr, parts[i]._len); - retptr += parts[i]._len; - if (i < parts.length - 1) { - memcpy(retptr, self._ptr, self._len); - retptr += self._len; - } - } - - return ret; - } -} \ No newline at end of file diff --git a/contracts-test/BadFeature.sol b/contracts-test/BadFeature.sol deleted file mode 100644 index 63961ff44..000000000 --- a/contracts-test/BadFeature.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -import "../contracts/modules/common/BaseFeature.sol"; - -contract BadFeature is BaseFeature { - - constructor( - ILockStorage _lockStorage, - IVersionManager _versionManager - ) public BaseFeature(_lockStorage, _versionManager, "") {} - - uint uintVal; - function setIntOwnerOnly(address _wallet, uint _val) external { - uintVal = _val; - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - return (0, OwnerSignature.Required); - } -} \ No newline at end of file diff --git a/contracts-test/DummyUniV2Router.sol b/contracts-test/DummyUniV2Router.sol new file mode 100644 index 000000000..5f304d9af --- /dev/null +++ b/contracts-test/DummyUniV2Router.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.8.3; + +contract DummyUniV2Router { + address public WETH; + address public factory; +} \ No newline at end of file diff --git a/contracts-test/ERC20Approver.sol b/contracts-test/ERC20Approver.sol deleted file mode 100644 index b6d0fd849..000000000 --- a/contracts-test/ERC20Approver.sol +++ /dev/null @@ -1,28 +0,0 @@ -pragma solidity ^0.6.12; - -import "../contracts/wallet/BaseWallet.sol"; -import "../contracts/modules/common/BaseFeature.sol"; -import "../contracts/infrastructure/storage/ILockStorage.sol"; - -// SPDX-License-Identifier: GPL-3.0-only -contract ERC20Approver is BaseFeature { - - bytes32 constant NAME = "ERC20Approver"; - - constructor(IVersionManager _versionManager) BaseFeature(ILockStorage(0), _versionManager, NAME) public {} - - // used by NftTransfer's Tests - function approveERC20(address _wallet, address _erc20Contract, address _spender, uint256 _amount) - external - onlyWalletOwnerOrFeature(_wallet) - { - invokeWallet(_wallet, _erc20Contract, 0, abi.encodeWithSignature("approve(address,uint256)", _spender, _amount)); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } -} \ No newline at end of file diff --git a/contracts-test/FakeWallet.sol b/contracts-test/FakeWallet.sol index b74e1a5f9..50305dffb 100644 --- a/contracts-test/FakeWallet.sol +++ b/contracts-test/FakeWallet.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "../contracts/modules/common/IModule.sol"; import "../contracts/wallet/IWallet.sol"; @@ -28,7 +28,7 @@ contract FakeWallet is IWallet { address target; uint value; bytes data; - constructor(bool _targetIsModule, address _target, uint _value, bytes memory _data) public { + constructor(bool _targetIsModule, address _target, uint _value, bytes memory _data) { targetIsModule = _targetIsModule; target = _target; value = _value; diff --git a/contracts-test/KyberNetworkTest.sol b/contracts-test/KyberNetworkTest.sol index 64e613917..5af6c3544 100644 --- a/contracts-test/KyberNetworkTest.sol +++ b/contracts-test/KyberNetworkTest.sol @@ -1,13 +1,10 @@ -pragma solidity ^0.6.12; -import "../lib/other/ERC20.sol"; -import "../lib/other/KyberNetwork.sol"; -import "@openzeppelin/contracts/math/SafeMath.sol"; +pragma solidity ^0.8.3; +import "../lib_0.5/other/ERC20.sol"; +import "../lib_0.5/other/KyberNetwork.sol"; // SPDX-License-Identifier: GPL-3.0-only contract KyberNetworkTest is KyberNetwork { - using SafeMath for uint256; - // Mock token address for ETH address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; @@ -20,7 +17,7 @@ contract KyberNetworkTest is KyberNetwork { mapping (address => Token) public tokens; address owner; - constructor() public { + constructor() { owner = msg.sender; } @@ -76,10 +73,10 @@ contract KyberNetworkTest is KyberNetwork { uint srcAmount; if (address(_src) == ETH_TOKEN_ADDRESS) { expectedRate = 10**36 / tokens[address(_dest)].rate; - destAmount = expectedRate.mul(_srcAmount).div(10**(36 - tokens[address(_dest)].decimals)); + destAmount = expectedRate * _srcAmount / 10**(36 - tokens[address(_dest)].decimals); if (destAmount > _maxDestAmount) { destAmount = _maxDestAmount; - srcAmount = _maxDestAmount.mul(10**(36 - tokens[address(_dest)].decimals)).div(expectedRate); + srcAmount = _maxDestAmount * 10**(36 - tokens[address(_dest)].decimals) / expectedRate; } else { srcAmount = _srcAmount; } @@ -92,10 +89,10 @@ contract KyberNetworkTest is KyberNetwork { require(ERC20(_dest).transfer(_destAddress, destAmount), "KyberNetwork: ERC20 transfer failed"); } else if (address(_dest) == ETH_TOKEN_ADDRESS) { expectedRate = tokens[address(_src)].rate; - destAmount = expectedRate.mul(_srcAmount).div(10**tokens[address(_src)].decimals); + destAmount = expectedRate * _srcAmount / 10**tokens[address(_src)].decimals; if (destAmount > _maxDestAmount) { destAmount = _maxDestAmount; - srcAmount = _maxDestAmount.mul(10**tokens[address(_src)].decimals).div(expectedRate); + srcAmount = _maxDestAmount * 10**tokens[address(_src)].decimals / expectedRate; } else { srcAmount = _srcAmount; } diff --git a/contracts-test/NonCompliantERC20.sol b/contracts-test/NonCompliantERC20.sol index 5c62b3ec8..a74c8507b 100644 --- a/contracts-test/NonCompliantERC20.sol +++ b/contracts-test/NonCompliantERC20.sol @@ -1,7 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "@openzeppelin/contracts/math/SafeMath.sol"; +pragma solidity ^0.8.3; /** * NonCompliantERC20 test contract. @@ -12,7 +10,6 @@ import "@openzeppelin/contracts/math/SafeMath.sol"; */ contract BasicToken { - using SafeMath for uint; mapping(address => uint) balances; @@ -36,8 +33,8 @@ contract BasicToken { * @param _value The amount to be transferred. */ function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) { - balances[msg.sender] = balances[msg.sender].sub(_value); - balances[_to] = balances[_to].add(_value); + balances[msg.sender] = balances[msg.sender] - _value; + balances[_to] = balances[_to] + _value; emit Transfer(msg.sender, _to, _value); } @@ -52,7 +49,6 @@ contract BasicToken { } contract StandardToken is BasicToken { - mapping (address => mapping (address => uint)) allowed; /** @@ -64,12 +60,12 @@ contract StandardToken is BasicToken { function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) { uint _allowance = allowed[_from][msg.sender]; - // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met + // Check is not needed because (_allowance - _value) will already throw if this condition is not met // if (_value > _allowance) throw; - balances[_to] = balances[_to].add(_value); - balances[_from] = balances[_from].sub(_value); - allowed[_from][msg.sender] = _allowance.sub(_value); + balances[_to] = balances[_to] + _value; + balances[_from] = balances[_from] - _value; + allowed[_from][msg.sender] = _allowance - _value; emit Transfer(_from, _to, _value); } @@ -124,8 +120,8 @@ contract MintableToken is StandardToken { * @return A boolean that indicates if the operation was successful. */ function mint(address _to, uint _amount) public canMint returns (bool) { - totalSupply = totalSupply.add(_amount); - balances[_to] = balances[_to].add(_amount); + totalSupply = totalSupply + _amount; + balances[_to] = balances[_to] + _amount; emit Mint(_to, _amount); return true; } diff --git a/contracts-test/NonCompliantGuardian.sol b/contracts-test/NonCompliantGuardian.sol index 546c99fc1..154299476 100644 --- a/contracts-test/NonCompliantGuardian.sol +++ b/contracts-test/NonCompliantGuardian.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; /** * @title NonCompliantGuardian diff --git a/contracts-test/TestContract.sol b/contracts-test/TestContract.sol index 048575560..9b308fe43 100644 --- a/contracts-test/TestContract.sol +++ b/contracts-test/TestContract.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "./TokenConsumer.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /** * @title TestContract @@ -14,8 +15,9 @@ contract TestContract { TokenConsumer public tokenConsumer; event StateSet(uint256 indexed _state, uint256 indexed _value); + event GasUsed(uint _gas); - constructor() public { + constructor() { tokenConsumer = new TokenConsumer(); } @@ -37,4 +39,11 @@ contract TestContract { emit StateSet(_state, _amount); } } + + function testERC165Gas(address _wallet, bytes4 _interfaceId) external { + uint startGas = gasleft(); + IERC165(_wallet).supportsInterface{gas: 10000}(_interfaceId); + uint endGas = gasleft(); + emit GasUsed(startGas - endGas); + } } \ No newline at end of file diff --git a/contracts-test/TestDapp.sol b/contracts-test/TestDapp.sol index 8ac93c382..c055d40ad 100644 --- a/contracts-test/TestDapp.sol +++ b/contracts-test/TestDapp.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; contract TestDapp { function noReturn() external {} diff --git a/contracts-test/TestERC1155.sol b/contracts-test/TestERC1155.sol new file mode 100644 index 000000000..88c00e727 --- /dev/null +++ b/contracts-test/TestERC1155.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract TestERC1155 is ERC1155 { + constructor() ERC1155("") { + } + + function mint(address to, uint256 tokenId, uint256 amount) public { + _mint(to, tokenId, amount, ""); + } +} diff --git a/contracts-test/TestERC20.sol b/contracts-test/TestERC20.sol index b684dbc92..0013e1f91 100644 --- a/contracts-test/TestERC20.sol +++ b/contracts-test/TestERC20.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -7,8 +7,10 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; * ERC20 test contract. */ contract TestERC20 is ERC20("ArgentToken", "AGT") { - constructor (address[] memory _initialAccounts, uint _supply, uint8 _decimals) public { - _setupDecimals(_decimals); + uint8 internal tokenDecimals; + + constructor (address[] memory _initialAccounts, uint _supply, uint8 _decimals) { + tokenDecimals = _decimals; for (uint i = 0; i < _initialAccounts.length; i++) { _mint(_initialAccounts[i], _supply * 10**uint(_decimals)); } @@ -21,4 +23,8 @@ contract TestERC20 is ERC20("ArgentToken", "AGT") { function burn(address account, uint256 amount) public { _burn(account, amount); } + + function decimals() public view override returns (uint8) { + return tokenDecimals; + } } diff --git a/contracts-test/TestERC721.sol b/contracts-test/TestERC721.sol index 3428c7430..83ec2b803 100644 --- a/contracts-test/TestERC721.sol +++ b/contracts-test/TestERC721.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract TestERC721 is ERC721 { - constructor() ERC721("Argent Kitties", "AGKT") public { + constructor() ERC721("Argent Kitties", "AGKT") { } function mint(address to, uint256 tokenId) public { diff --git a/contracts-test/TestFeature.sol b/contracts-test/TestFeature.sol deleted file mode 100644 index 720d72b88..000000000 --- a/contracts-test/TestFeature.sol +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -import "../contracts/infrastructure/storage/ILockStorage.sol"; -import "../contracts/modules/common/BaseFeature.sol"; -import "./TestDapp.sol"; - -/** - * @title TestModule - * @notice Basic test module - * @author Olivier VDB - - */ -contract TestFeature is BaseFeature { - - bytes32 constant NAME = "TestFeature"; - - uint uintVal; - TestDapp public dapp; - - constructor( - ILockStorage _lockStorage, - IVersionManager _versionManager, - uint _uintVal - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - uintVal = _uintVal; - dapp = new TestDapp(); - } - - function invalidOwnerChange(address _wallet) external { - versionManager.setOwner(_wallet, address(0)); // this should fail - } - - function setIntOwnerOnly(address _wallet, uint _val) external onlyWalletOwnerOrFeature(_wallet) { - uintVal = _val; - } - function clearInt() external { - uintVal = 0; - } - - // used to simulate a bad module in MakerV2Loan tests - function isNewVersion(address _addr) external view returns (bytes32) { - return bytes4(keccak256("isNewVersion(address)")); - } - - function callContract(address _contract, uint256 _value, bytes calldata _data) external { - (bool success,) = _contract.call{value: _value}(_data); - if (!success) { - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } - } - ///////////////// - - /** - * @inheritdoc IFeature - */ - function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) { - _sigs = new bytes4[](4); - _sigs[0] = bytes4(keccak256("getBoolean()")); - _sigs[1] = bytes4(keccak256("getUint()")); - _sigs[2] = bytes4(keccak256("getAddress(address)")); - _sigs[3] = bytes4(keccak256("badStaticCall()")); - } - - function getBoolean() public view returns (bool) { - return true; - } - - function getUint() public view returns (uint) { - return 42; - } - - function getAddress(address _addr) public pure returns (address) { - return _addr; - } - - function badStaticCall() external { - uintVal = 123456; - } - - function callDapp(address _wallet) - external - { - invokeWallet(_wallet, address(dapp), 0, abi.encodeWithSignature("noReturn()")); - } - - function callDapp2(address _wallet, uint256 _val, bool _isNewWallet) - external returns (uint256 _ret) - { - bytes memory result = invokeWallet(_wallet, address(dapp), 0, abi.encodeWithSignature("uintReturn(uint256)", _val)); - if (_isNewWallet) { - require(result.length > 0, "TestModule: callDapp2 returned no result"); - (_ret) = abi.decode(result, (uint256)); - require(_ret == _val, "TestModule: invalid val"); - } else { - require(result.length == 0, "TestModule: callDapp2 returned some result"); - } - } - - function fail(address _wallet, string calldata reason) external { - invokeWallet(_wallet, address(dapp), 0, abi.encodeWithSignature("doFail(string)", reason)); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - function invokeStorage(address _wallet, address _storage, bytes calldata _data) external { - versionManager.invokeStorage(_wallet, _storage, _data); - } -} \ No newline at end of file diff --git a/contracts/infrastructure/storage/ILockStorage.sol b/contracts-test/TestFilter.sol similarity index 62% rename from contracts/infrastructure/storage/ILockStorage.sol rename to contracts-test/TestFilter.sol index f1e2abff9..f8d71ee19 100644 --- a/contracts/infrastructure/storage/ILockStorage.sol +++ b/contracts-test/TestFilter.sol @@ -1,4 +1,4 @@ -// Copyright (C) 2018 Argent Labs Ltd. +// Copyright (C) 2021 Argent Labs Ltd. // 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 @@ -14,14 +14,13 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.8.3; -interface ILockStorage { - function isLocked(address _wallet) external view returns (bool); +import "../contracts/infrastructure/dapp/IFilter.sol"; - function getLock(address _wallet) external view returns (uint256); - - function getLocker(address _wallet) external view returns (address); - - function setLock(address _wallet, address _locker, uint256 _releaseAfter) external; +contract TestFilter is IFilter { + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external override pure returns (bool valid) { + uint256 state = abi.decode(_data[4:], (uint256)); + return state != 5; + } } \ No newline at end of file diff --git a/contracts-test/TestLimitFeature.sol b/contracts-test/TestLimitFeature.sol deleted file mode 100644 index 626c0a3d9..000000000 --- a/contracts-test/TestLimitFeature.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "../contracts/modules/common/BaseFeature.sol"; -import "../contracts/modules/common/LimitUtils.sol"; - -/** - * @title TestLimitFeature - * @notice Basic feature to set the daily limit - */ -contract TestLimitFeature is BaseFeature { - - bytes32 constant NAME = "TestLimitModule"; - - using SafeMath for uint256; - - ILimitStorage public limitStorage; - - constructor( - ILockStorage _lockStorage, - ILimitStorage _limitStorage, - IVersionManager _versionManager - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - limitStorage = _limitStorage; - } - - function setLimitAndDailySpent( - address _wallet, - uint256 _limit, - uint256 _alredySpent - ) - external - { - setLimit(_wallet, ILimitStorage.Limit(LimitUtils.safe128(_limit), 0, 0)); - setDailySpent(_wallet, ILimitStorage.DailySpent(LimitUtils.safe128(_alredySpent), LimitUtils.safe64(block.timestamp.add(100)))); - } - - function getDailySpent(address _wallet) external view returns (uint256) { - ILimitStorage.DailySpent memory dailySpent = limitStorage.getDailySpent(_wallet); - return dailySpent.alreadySpent; - } - - function getLimit(address _wallet) external view returns (uint256) { - ILimitStorage.Limit memory limit = limitStorage.getLimit(_wallet); - return limit.current; - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - function setLimit(address _wallet, ILimitStorage.Limit memory _limit) internal { - versionManager.invokeStorage( - _wallet, - address(limitStorage), - abi.encodeWithSelector(limitStorage.setLimit.selector, _wallet, _limit) - ); - } - - function setDailySpent(address _wallet, ILimitStorage.DailySpent memory _dailySpent) internal { - versionManager.invokeStorage( - _wallet, - address(limitStorage), - abi.encodeWithSelector(limitStorage.setDailySpent.selector, _wallet, _dailySpent) - ); - } -} \ No newline at end of file diff --git a/contracts-test/TestOwnedContract.sol b/contracts-test/TestOwnedContract.sol index fcb582fc6..3c2f7903b 100644 --- a/contracts-test/TestOwnedContract.sol +++ b/contracts-test/TestOwnedContract.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "../contracts/infrastructure/base/Owned.sol"; /** diff --git a/contracts-test/TestRegistry.sol b/contracts-test/TestRegistry.sol index f13de484e..ca6d534c7 100644 --- a/contracts-test/TestRegistry.sol +++ b/contracts-test/TestRegistry.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only // Source https://github.com/christianlundkvist/simple-multisig/blob/master/contracts/TestRegistry.sol -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; // This contract is only used for testing the MultiSigWallet contract TestRegistry { diff --git a/contracts-test/TestSimpleOracle.sol b/contracts-test/TestSimpleOracle.sol new file mode 100644 index 000000000..593850578 --- /dev/null +++ b/contracts-test/TestSimpleOracle.sol @@ -0,0 +1,45 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../contracts/modules/common/SimpleOracle.sol"; + +contract TestSimpleOracle is SimpleOracle { + + bytes32 internal creationCode; + + constructor(address _uniswapRouter) SimpleOracle(_uniswapRouter) { + address uniswapV2Factory = IUniswapV2Router01(_uniswapRouter).factory(); + (bool success, bytes memory _res) = uniswapV2Factory.staticcall(abi.encodeWithSignature("getKeccakOfPairCreationCode()")); + if (success) { + creationCode = abi.decode(_res, (bytes32)); + } + } + + function ethToToken(address _token, uint256 _ethAmount) external view returns (uint256) { + return inToken(_token, _ethAmount); + } + + function getPairForSorted(address tokenA, address tokenB) internal override view returns (address pair) { + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + uniswapV2Factory, + keccak256(abi.encodePacked(tokenA, tokenB)), + creationCode + ))))); + } +} \ No newline at end of file diff --git a/contracts-test/TestUpgradedMakerV2Manager.sol b/contracts-test/TestUpgradedMakerV2Manager.sol deleted file mode 100644 index d5b8fc85b..000000000 --- a/contracts-test/TestUpgradedMakerV2Manager.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "../contracts/modules/maker/MakerV2Manager.sol"; - -/** - * @title TestUpgradedMakerV2Manager - * @notice Test upgraded MakerV2 module. - * @author Olivier VDB - - */ -contract TestUpgradedMakerV2Manager is MakerV2Manager { - - MakerV2Manager private previousMakerV2Manager; - - constructor( - ILockStorage _lockStorage, - ScdMcdMigrationLike _scdMcdMigration, - PotLike _pot, - JugLike _jug, - IMakerRegistry _makerRegistry, - IUniswapFactory _uniswapFactory, - MakerV2Manager _previousMakerV2Manager, - IVersionManager _versionManager - ) - - MakerV2Manager( - _lockStorage, - _scdMcdMigration, - _pot, - _jug, - _makerRegistry, - _uniswapFactory, - _versionManager - ) - public - - { - previousMakerV2Manager = _previousMakerV2Manager; - } - - function isNewVersion(address _addr) external view returns (bytes32) { - if (_addr == address(previousMakerV2Manager)) { - return bytes4(keccak256("isNewVersion(address)")); - } - } - - function init(address _wallet) public override onlyVersionManager { - address[] memory tokens = makerRegistry.getCollateralTokens(); - for (uint256 i = 0; i < tokens.length; i++) { - bytes32 loanId = previousMakerV2Manager.loanIds(_wallet, makerRegistry.getIlk(tokens[i])); - if (loanId != 0) { - previousMakerV2Manager.giveVault(_wallet, loanId); - assignLoanToWallet(_wallet, loanId); - } - } - } -} \ No newline at end of file diff --git a/contracts-test/TokenConsumer.sol b/contracts-test/TokenConsumer.sol index 3fc64c10b..9ebeb8fe5 100644 --- a/contracts-test/TokenConsumer.sol +++ b/contracts-test/TokenConsumer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts-test/aaveV2/AaveV2ATokenMock.sol b/contracts-test/aaveV2/AaveV2ATokenMock.sol new file mode 100644 index 000000000..9c722265a --- /dev/null +++ b/contracts-test/aaveV2/AaveV2ATokenMock.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.8.3; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + + +contract AaveV2ATokenMock is ERC20("aToken", "AERC20") { + address asset; + address lendingPool; + + constructor(address _asset) { + asset = _asset; + lendingPool = msg.sender; + } + + modifier onlyLendingPool() { + require(msg.sender == lendingPool, "not lending pool"); + _; + } + + function mint(address _user, uint _amount) external onlyLendingPool { + _mint(_user, _amount); + } + + function burn(address _user, address _to, uint _amount) external onlyLendingPool { + _burn(_user, _amount); + require(IERC20(asset).transfer(_to, _amount), "asset transfer failed"); + } +} \ No newline at end of file diff --git a/contracts-test/aaveV2/AaveV2LendingPoolMock.sol b/contracts-test/aaveV2/AaveV2LendingPoolMock.sol new file mode 100644 index 000000000..96a502936 --- /dev/null +++ b/contracts-test/aaveV2/AaveV2LendingPoolMock.sol @@ -0,0 +1,30 @@ +pragma solidity 0.8.3; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./AaveV2ATokenMock.sol"; + +contract AaveV2LendingPoolMock { + mapping (address => address) public aTokens; + + constructor(address[] memory _assets) { + for(uint i = 0; i < _assets.length; i++) { + aTokens[_assets[i]] = address(new AaveV2ATokenMock(_assets[i])); + } + } + + function deposit(address _asset, uint256 _amount, address _onBehalfOf, uint16 /* _referralCode */) external { + address aToken = aTokens[_asset]; + require(aToken != address(0), "unknown asset"); + require(IERC20(_asset).transferFrom(msg.sender, aToken, _amount), "asset transfer failed"); + AaveV2ATokenMock(aToken).mint(_onBehalfOf, _amount); + } + + function withdraw(address _asset, uint256 _amount, address _to) external returns (uint256) { + address aToken = aTokens[_asset]; + require(aToken != address(0), "unknown asset"); + AaveV2ATokenMock(aToken).burn(msg.sender, _to, _amount); + } + + function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf) external {} + function repay(address asset, uint256 amount, uint256 rateMode, address onBehalfOf) external {} +} \ No newline at end of file diff --git a/contracts-test/maker/FaucetUser.sol b/contracts-test/maker/FaucetUser.sol index 8bd1f69a9..1b7529c32 100644 --- a/contracts-test/maker/FaucetUser.sol +++ b/contracts-test/maker/FaucetUser.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; -import "../../lib/maker/DS/IERC20.sol"; +import "../../lib_0.5/maker/DS/IERC20.sol"; interface MakerFaucet { function gulp(address gem) external; } contract FaucetUser { - constructor(MakerFaucet _faucet, IERC20 _gem) public { + constructor(MakerFaucet _faucet, IERC20 _gem) { // `gulp` can only be called once by a given account. Hence, // this wrapper contract is a hack that lets us call `gulp` multiple times // for the same token recipient. _faucet.gulp(address(_gem)); _gem.transfer(msg.sender, _gem.balanceOf(address(this))); - selfdestruct(msg.sender); + selfdestruct(payable(msg.sender)); } } \ No newline at end of file diff --git a/contracts-test/maker/TestCdpManager.sol b/contracts-test/maker/TestCdpManager.sol index 7f994eea6..56bdb00d2 100644 --- a/contracts-test/maker/TestCdpManager.sol +++ b/contracts-test/maker/TestCdpManager.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; abstract contract TestCdpManager { function urns(uint) public virtual view returns (address); diff --git a/contracts-test/uniswapV2/UniZap.sol b/contracts-test/uniswapV2/UniZap.sol new file mode 100644 index 000000000..bca0620a9 --- /dev/null +++ b/contracts-test/uniswapV2/UniZap.sol @@ -0,0 +1,222 @@ +pragma solidity ^0.8.3; + +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IERC20.sol"; +import "@uniswap/lib/contracts/libraries/Babylonian.sol"; +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "./UniswapV2LibraryMock.sol"; + +// enables adding and removing liquidity with a single token to/from a pair +// adds liquidity via a single token of the pair, by first swapping against the pair and then adding liquidity +// removes liquidity in a single token, by removing liquidity and then immediately swapping +contract UniZap { + + IUniswapV2Factory public factory; + IUniswapV2Router01 public router; + IWETH public weth; + + constructor(IUniswapV2Factory factory_, IUniswapV2Router01 router_, IWETH weth_) { + factory = factory_; + router = router_; + weth = weth_; + } + + // grants unlimited approval for a token to the router unless the existing allowance is high enough + function approveRouter(address _token, uint256 _amount) internal { + uint256 allowance = IERC20(_token).allowance(address(this), address(router)); + if (allowance < _amount) { + if (allowance > 0) { + // clear the existing allowance + TransferHelper.safeApprove(_token, address(router), 0); + } + TransferHelper.safeApprove(_token, address(router), uint256(int256(int8(-1)))); + } + } + + // returns the amount of token that should be swapped in such that ratio of reserves in the pair is equivalent + // to the swapper's ratio of tokens + // note this depends only on the number of tokens the caller wishes to swap and the current reserves of that token, + // and not the current reserves of the other token + function calculateSwapInAmount(uint reserveIn, uint userIn) public pure returns (uint) { + return Babylonian.sqrt((reserveIn * userIn * 3988000 + reserveIn * 3988009) - (reserveIn * 1997)) / 1994; + } + + // internal function shared by the ETH/non-ETH versions + function _swapExactTokensAndAddLiquidity( + address from, + address tokenIn, + address otherToken, + uint amountIn, + uint minOtherTokenIn, + address to, + uint deadline + ) internal returns (uint amountTokenIn, uint amountTokenOther, uint liquidity) { + // compute how much we should swap in to match the reserve ratio of tokenIn / otherToken of the pair + uint swapInAmount; + { + (uint reserveIn,) = UniswapV2LibraryMock.getReserves(address(factory), tokenIn, otherToken); + swapInAmount = calculateSwapInAmount(reserveIn, amountIn); + } + + // first take possession of the full amount from the caller, unless caller is this contract + if (from != address(this)) { + TransferHelper.safeTransferFrom(tokenIn, from, address(this), amountIn); + } + // approve for the swap, and then later the add liquidity. total is amountIn + approveRouter(tokenIn, amountIn); + + { + address[] memory path = new address[](2); + path[0] = tokenIn; + path[1] = otherToken; + + amountTokenOther = router.swapExactTokensForTokens( + swapInAmount, + minOtherTokenIn, + path, + address(this), + deadline + )[1]; + } + + // approve the other token for the add liquidity call + approveRouter(otherToken, amountTokenOther); + amountTokenIn = amountIn - swapInAmount; + + // no need to check that we transferred everything because minimums == total balance of this contract + (,,liquidity) = router.addLiquidity( + tokenIn, + otherToken, + // desired amountA, amountB + amountTokenIn, + amountTokenOther, + // amountTokenIn and amountTokenOther should match the ratio of reserves of tokenIn to otherToken + // thus we do not need to constrain the minimums here + 0, + 0, + to, + deadline + ); + } + + // computes the exact amount of tokens that should be swapped before adding liquidity for a given token + // does the swap and then adds liquidity + // minOtherToken should be set to the minimum intermediate amount of token1 that should be received to prevent + // excessive slippage or front running + // liquidity provider shares are minted to the 'to' address + function swapExactTokensAndAddLiquidity( + address tokenIn, + address otherToken, + uint amountIn, + uint minOtherTokenIn, + address to, + uint deadline + ) external returns (uint amountTokenIn, uint amountTokenOther, uint liquidity) { + return _swapExactTokensAndAddLiquidity( + msg.sender, tokenIn, otherToken, amountIn, minOtherTokenIn, to, deadline + ); + } + + // similar to the above method but handles converting ETH to WETH + function swapExactETHAndAddLiquidity( + address token, + uint minTokenIn, + address to, + uint deadline + ) external payable returns (uint amountETHIn, uint amountTokenIn, uint liquidity) { + weth.deposit{value: msg.value}(); + return _swapExactTokensAndAddLiquidity( + address(this), address(weth), token, msg.value, minTokenIn, to, deadline + ); + } + + // internal function shared by the ETH/non-ETH versions + function _removeLiquidityAndSwap( + address from, + address undesiredToken, + address desiredToken, + uint liquidity, + uint minDesiredTokenOut, + address to, + uint deadline + ) internal returns (uint amountDesiredTokenOut) { + address pair = UniswapV2LibraryMock.pairFor(address(factory), undesiredToken, desiredToken); + // take possession of liquidity and give access to the router + TransferHelper.safeTransferFrom(pair, from, address(this), liquidity); + approveRouter(pair, liquidity); + + (uint amountInToSwap, uint amountOutToTransfer) = router.removeLiquidity( + undesiredToken, + desiredToken, + liquidity, + // amount minimums are applied in the swap + 0, + 0, + // contract must receive both tokens because we want to swap the undesired token + address(this), + deadline + ); + + // send the amount in that we received in the burn + approveRouter(undesiredToken, amountInToSwap); + + address[] memory path = new address[](2); + path[0] = undesiredToken; + path[1] = desiredToken; + + uint amountOutSwap = router.swapExactTokensForTokens( + amountInToSwap, + // we must get at least this much from the swap to meet the minDesiredTokenOut parameter + minDesiredTokenOut > amountOutToTransfer ? minDesiredTokenOut - amountOutToTransfer : 0, + path, + to, + deadline + )[1]; + + // we do this after the swap to save gas in the case where we do not meet the minimum output + if (to != address(this)) { + TransferHelper.safeTransfer(desiredToken, to, amountOutToTransfer); + } + amountDesiredTokenOut = amountOutToTransfer + amountOutSwap; + } + + // burn the liquidity and then swap one of the two tokens to the other + // enforces that at least minDesiredTokenOut tokens are received from the combination of burn and swap + function removeLiquidityAndSwapToToken( + address undesiredToken, + address desiredToken, + uint liquidity, + uint minDesiredTokenOut, + address to, + uint deadline + ) external returns (uint amountDesiredTokenOut) { + return _removeLiquidityAndSwap( + msg.sender, undesiredToken, desiredToken, liquidity, minDesiredTokenOut, to, deadline + ); + } + + // only WETH can send to this contract without a function call. + receive() payable external { + require(msg.sender == address(weth), 'CombinedSwapAddRemoveLiquidity: RECEIVE_NOT_FROM_WETH'); + } + + // similar to the above method but for when the desired token is WETH, handles unwrapping + function removeLiquidityAndSwapToETH( + address token, + uint liquidity, + uint minDesiredETH, + address to, + uint deadline + ) external returns (uint amountETHOut) { + // do the swap remove and swap to this address + amountETHOut = _removeLiquidityAndSwap( + msg.sender, token, address(weth), liquidity, minDesiredETH, address(this), deadline + ); + + // now withdraw to ETH and forward to the recipient + weth.withdraw(amountETHOut); + TransferHelper.safeTransferETH(to, amountETHOut); + } +} \ No newline at end of file diff --git a/lib/uniswapV2/uniswap-v2-core/UniswapV2ERC20.sol b/contracts-test/uniswapV2/UniswapV2ERC20Mock.sol similarity index 83% rename from lib/uniswapV2/uniswap-v2-core/UniswapV2ERC20.sol rename to contracts-test/uniswapV2/UniswapV2ERC20Mock.sol index 8cb8f803b..862f1df1e 100644 --- a/lib/uniswapV2/uniswap-v2-core/UniswapV2ERC20.sol +++ b/contracts-test/uniswapV2/UniswapV2ERC20Mock.sol @@ -1,10 +1,9 @@ -pragma solidity ^0.5.4; +pragma solidity >=0.5.4; -import './interfaces/IUniswapV2ERC20.sol'; -import './libraries/SafeMath.sol'; +contract UniswapV2ERC20Mock { -contract UniswapV2ERC20 is IUniswapV2ERC20 { - using SafeMath for uint; + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); string public constant name = 'Uniswap V2'; string public constant symbol = 'UNI-V2'; @@ -17,11 +16,8 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; mapping(address => uint) public nonces; - - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - constructor() public { + + constructor() { // uint chainId; // assembly { // chainId := chainid @@ -38,14 +34,14 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { } function _mint(address to, uint value) internal { - totalSupply = totalSupply.add(value); - balanceOf[to] = balanceOf[to].add(value); + totalSupply = totalSupply + value; + balanceOf[to] = balanceOf[to] + value; emit Transfer(address(0), to, value); } function _burn(address from, uint value) internal { - balanceOf[from] = balanceOf[from].sub(value); - totalSupply = totalSupply.sub(value); + balanceOf[from] = balanceOf[from] - value; + totalSupply = totalSupply - value; emit Transfer(from, address(0), value); } @@ -55,8 +51,8 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { } function _transfer(address from, address to, uint value) private { - balanceOf[from] = balanceOf[from].sub(value); - balanceOf[to] = balanceOf[to].add(value); + balanceOf[from] = balanceOf[from] - value; + balanceOf[to] = balanceOf[to] + value; emit Transfer(from, to, value); } @@ -71,8 +67,8 @@ contract UniswapV2ERC20 is IUniswapV2ERC20 { } function transferFrom(address from, address to, uint value) external returns (bool) { - if (allowance[from][msg.sender] != uint(-1)) { - allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); + if (allowance[from][msg.sender] != uint(int256(int8(-1)))) { + allowance[from][msg.sender] = allowance[from][msg.sender] - value; } _transfer(from, to, value); return true; diff --git a/lib/uniswapV2/uniswap-v2-core/UniswapV2Factory.sol b/contracts-test/uniswapV2/UniswapV2FactoryMock.sol similarity index 84% rename from lib/uniswapV2/uniswap-v2-core/UniswapV2Factory.sol rename to contracts-test/uniswapV2/UniswapV2FactoryMock.sol index 8003772fe..de9333e77 100644 --- a/lib/uniswapV2/uniswap-v2-core/UniswapV2Factory.sol +++ b/contracts-test/uniswapV2/UniswapV2FactoryMock.sol @@ -1,9 +1,9 @@ -pragma solidity ^0.5.4; +pragma solidity >=0.5.4; -import './interfaces/IUniswapV2Factory.sol'; -import './UniswapV2Pair.sol'; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import "./UniswapV2PairMock.sol"; -contract UniswapV2Factory is IUniswapV2Factory { +contract UniswapV2FactoryMock { address public feeTo; address public feeToSetter; @@ -12,7 +12,7 @@ contract UniswapV2Factory is IUniswapV2Factory { event PairCreated(address indexed token0, address indexed token1, address pair, uint); - constructor(address _feeToSetter) public { + constructor(address _feeToSetter) { feeToSetter = _feeToSetter; } @@ -23,7 +23,7 @@ contract UniswapV2Factory is IUniswapV2Factory { // !! Argent Modification !! // The following method was added to be able to use the correct UniswapV2Pair init code in UniswapV2Library function getKeccakOfPairCreationCode() external pure returns (bytes32) { - return keccak256(type(UniswapV2Pair).creationCode); + return keccak256(type(UniswapV2PairMock).creationCode); } function createPair(address tokenA, address tokenB) external returns (address pair) { @@ -31,7 +31,7 @@ contract UniswapV2Factory is IUniswapV2Factory { (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS'); require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient - bytes memory bytecode = type(UniswapV2Pair).creationCode; + bytes memory bytecode = type(UniswapV2PairMock).creationCode; bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2Library.sol b/contracts-test/uniswapV2/UniswapV2LibraryMock.sol similarity index 77% rename from contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2Library.sol rename to contracts-test/uniswapV2/UniswapV2LibraryMock.sol index 98bea98d2..9d7d9a864 100644 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2Library.sol +++ b/contracts-test/uniswapV2/UniswapV2LibraryMock.sol @@ -1,14 +1,9 @@ pragma solidity >=0.5.0; -import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol'; -import './SafeMath.sol'; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; // !! Argent Modification !! -// The following import was added to the original file -// to be able to use the correct UniswapV2Pair init code -import '../../../../lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Factory.sol'; - -library UniswapV2Library { - using SafeMath for uint256; +// To be able to use the correct UniswapV2Pair init code. +library UniswapV2LibraryMock { // returns sorted token addresses, used to handle return values from pairs sorted in this order function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { @@ -22,24 +17,25 @@ library UniswapV2Library { address factory, address tokenA, address tokenB - ) internal pure returns (address pair) { + ) internal view returns (address pair) { (address token0, address token1) = sortTokens(tokenA, tokenB); // !! Argent Modification !! // the original file used the following hardcoded value as init code hash: // hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' - bytes32 initCode = IUniswapV2Factory(factory).getKeccakOfPairCreationCode(); - pair = address( - uint256( - keccak256( - abi.encodePacked( - hex'ff', - factory, - keccak256(abi.encodePacked(token0, token1)), - initCode + (bool success, bytes memory _res) = factory.staticcall(abi.encodeWithSignature("getKeccakOfPairCreationCode()")); + if (success) { + bytes32 initCode = abi.decode(_res, (bytes32)); + pair = address(uint160(uint256( + keccak256( + abi.encodePacked( + hex'ff', + factory, + keccak256(abi.encodePacked(token0, token1)), + initCode + ) ) - ) - ) - ); + ))); + } } // fetches and sorts the reserves for a pair @@ -62,7 +58,7 @@ library UniswapV2Library { ) internal pure returns (uint256 amountB) { require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT'); require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); - amountB = amountA.mul(reserveB) / reserveA; + amountB = amountA * reserveB / reserveA; } // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset @@ -73,9 +69,9 @@ library UniswapV2Library { ) internal pure returns (uint256 amountOut) { require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); - uint256 amountInWithFee = amountIn.mul(997); - uint256 numerator = amountInWithFee.mul(reserveOut); - uint256 denominator = reserveIn.mul(1000).add(amountInWithFee); + uint256 amountInWithFee = amountIn * 997; + uint256 numerator = amountInWithFee * reserveOut; + uint256 denominator = reserveIn * 1000 + amountInWithFee; amountOut = numerator / denominator; } @@ -87,9 +83,9 @@ library UniswapV2Library { ) internal pure returns (uint256 amountIn) { require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); - uint256 numerator = reserveIn.mul(amountOut).mul(1000); - uint256 denominator = reserveOut.sub(amountOut).mul(997); - amountIn = (numerator / denominator).add(1); + uint256 numerator = reserveIn * amountOut * 1000; + uint256 denominator = (reserveOut - amountOut) * 997; + amountIn = (numerator / denominator) + 1; } // performs chained getAmountOut calculations on any number of pairs diff --git a/lib/uniswapV2/uniswap-v2-core/UniswapV2Pair.sol b/contracts-test/uniswapV2/UniswapV2PairMock.sol similarity index 82% rename from lib/uniswapV2/uniswap-v2-core/UniswapV2Pair.sol rename to contracts-test/uniswapV2/UniswapV2PairMock.sol index 4bfd3d8ce..d080c7a91 100644 --- a/lib/uniswapV2/uniswap-v2-core/UniswapV2Pair.sol +++ b/contracts-test/uniswapV2/UniswapV2PairMock.sol @@ -1,15 +1,14 @@ -pragma solidity ^0.5.4; - -import './interfaces/IUniswapV2Pair.sol'; -import './UniswapV2ERC20.sol'; -import './libraries/Math.sol'; -import './libraries/UQ112x112.sol'; -import './interfaces/IERC20.sol'; -import './interfaces/IUniswapV2Factory.sol'; -import './interfaces/IUniswapV2Callee.sol'; - -contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { - using SafeMath for uint; +pragma solidity >=0.5.4; + +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol"; +import "@uniswap/v2-core/contracts/interfaces/IERC20.sol"; + +import "./UniswapV2ERC20Mock.sol"; +import "./libraries/Math.sol"; +import "./libraries/UQ112x112.sol"; + +contract UniswapV2PairMock is UniswapV2ERC20Mock { using UQ112x112 for uint224; uint public constant MINIMUM_LIQUIDITY = 10**3; @@ -58,7 +57,7 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { ); event Sync(uint112 reserve0, uint112 reserve1); - constructor() public { + constructor() { factory = msg.sender; } @@ -71,7 +70,7 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { // update reserves and, on the first call per block, price accumulators function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { - require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); + require(balance0 <= uint112(int112(int8(-1))) && balance1 <= uint112(int112(int8(-1))), 'UniswapV2: OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { @@ -92,11 +91,11 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { uint _kLast = kLast; // gas savings if (feeOn) { if (_kLast != 0) { - uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); + uint rootK = Math.sqrt(uint(_reserve0) * _reserve1); uint rootKLast = Math.sqrt(_kLast); if (rootK > rootKLast) { - uint numerator = totalSupply.mul(rootK.sub(rootKLast)); - uint denominator = rootK.mul(5).add(rootKLast); + uint numerator = totalSupply * (rootK - rootKLast); + uint denominator = rootK * 5 + rootKLast; uint liquidity = numerator / denominator; if (liquidity > 0) _mint(feeTo, liquidity); } @@ -111,22 +110,22 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); - uint amount0 = balance0.sub(_reserve0); - uint amount1 = balance1.sub(_reserve1); + uint amount0 = balance0 - _reserve0; + uint amount1 = balance1 - _reserve1; bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { - liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); + liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { - liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); + liquidity = Math.min(amount0 * _totalSupply / _reserve0, amount1 * _totalSupply / _reserve1); } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity); _update(balance0, balance1, _reserve0, _reserve1); - if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date + if (feeOn) kLast = uint(reserve0) * reserve1; // reserve0 and reserve1 are up-to-date emit Mint(msg.sender, amount0, amount1); } @@ -141,8 +140,8 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee - amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution - amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution + amount0 = liquidity * balance0 / _totalSupply; // using balances ensures pro-rata distribution + amount1 = liquidity * balance1 / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); _burn(address(this), liquidity); _safeTransfer(_token0, to, amount0); @@ -151,7 +150,7 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { balance1 = IERC20(_token1).balanceOf(address(this)); _update(balance0, balance1, _reserve0, _reserve1); - if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date + if (feeOn) kLast = uint(reserve0) * reserve1; // reserve0 and reserve1 are up-to-date emit Burn(msg.sender, amount0, amount1, to); } @@ -177,9 +176,9 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors - uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); - uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); - require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); + uint balance0Adjusted = (balance0*1000) - (amount0In*3); + uint balance1Adjusted = (balance1*1000) - (amount1In*3); + require(balance0Adjusted * balance1Adjusted >= uint(_reserve0) * _reserve1 * 1000**2, 'UniswapV2: K'); } _update(balance0, balance1, _reserve0, _reserve1); @@ -190,8 +189,8 @@ contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { function skim(address to) external lock { address _token0 = token0; // gas savings address _token1 = token1; // gas savings - _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); - _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); + _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)) - reserve0); + _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)) - reserve1); } // force reserves to match balances diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/UniswapV2Router01.sol b/contracts-test/uniswapV2/UniswapV2Router01Mock.sol similarity index 68% rename from contracts-test/uniswapV2/uniswap-V2-periphery/UniswapV2Router01.sol rename to contracts-test/uniswapV2/UniswapV2Router01Mock.sol index f2a8b27f1..df04b4521 100644 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/UniswapV2Router01.sol +++ b/contracts-test/uniswapV2/UniswapV2Router01Mock.sol @@ -1,26 +1,21 @@ -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; -import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IERC20.sol"; +import "./UniswapV2LibraryMock.sol"; -import './libraries/UniswapV2Library.sol'; -import './interfaces/IUniswapV2Router01.sol'; -import './interfaces/IWETH.sol'; -// !! Argent Modification !! -// The following two imports were removed from the original file -// because they are already imported in the modified UniswapV2Library.sol -// import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol'; -// import './interfaces/IERC20.sol'; - -contract UniswapV2Router01 is IUniswapV2Router01 { - address public immutable override factory; - address public immutable override WETH; +contract UniswapV2Router01Mock { + address public immutable factory; + address public immutable WETH; modifier ensure(uint deadline) { require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED'); _; } - constructor(address _factory, address _WETH) public { + constructor(address _factory, address _WETH) { factory = _factory; WETH = _WETH; } @@ -42,16 +37,16 @@ contract UniswapV2Router01 is IUniswapV2Router01 { if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) { IUniswapV2Factory(factory).createPair(tokenA, tokenB); } - (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB); + (uint reserveA, uint reserveB) = UniswapV2LibraryMock.getReserves(factory, tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { - uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB); + uint amountBOptimal = UniswapV2LibraryMock.quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT'); (amountA, amountB) = (amountADesired, amountBOptimal); } else { - uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA); + uint amountAOptimal = UniswapV2LibraryMock.quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT'); (amountA, amountB) = (amountAOptimal, amountBDesired); @@ -67,9 +62,9 @@ contract UniswapV2Router01 is IUniswapV2Router01 { uint amountBMin, address to, uint deadline - ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { + ) external ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); - address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); + address pair = UniswapV2LibraryMock.pairFor(factory, tokenA, tokenB); TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA); TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB); liquidity = IUniswapV2Pair(pair).mint(to); @@ -81,7 +76,7 @@ contract UniswapV2Router01 is IUniswapV2Router01 { uint amountETHMin, address to, uint deadline - ) external override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) { + ) external payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) { (amountToken, amountETH) = _addLiquidity( token, WETH, @@ -90,7 +85,7 @@ contract UniswapV2Router01 is IUniswapV2Router01 { amountTokenMin, amountETHMin ); - address pair = UniswapV2Library.pairFor(factory, token, WETH); + address pair = UniswapV2LibraryMock.pairFor(factory, token, WETH); TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken); IWETH(WETH).deposit{value: amountETH}(); assert(IWETH(WETH).transfer(pair, amountETH)); @@ -107,11 +102,11 @@ contract UniswapV2Router01 is IUniswapV2Router01 { uint amountBMin, address to, uint deadline - ) public override ensure(deadline) returns (uint amountA, uint amountB) { - address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); + ) public ensure(deadline) returns (uint amountA, uint amountB) { + address pair = UniswapV2LibraryMock.pairFor(factory, tokenA, tokenB); IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to); - (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB); + (address token0,) = UniswapV2LibraryMock.sortTokens(tokenA, tokenB); (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0); require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT'); require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT'); @@ -123,7 +118,7 @@ contract UniswapV2Router01 is IUniswapV2Router01 { uint amountETHMin, address to, uint deadline - ) public override ensure(deadline) returns (uint amountToken, uint amountETH) { + ) public ensure(deadline) returns (uint amountToken, uint amountETH) { (amountToken, amountETH) = removeLiquidity( token, WETH, @@ -146,9 +141,9 @@ contract UniswapV2Router01 is IUniswapV2Router01 { address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external override returns (uint amountA, uint amountB) { - address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); - uint value = approveMax ? uint(-1) : liquidity; + ) external returns (uint amountA, uint amountB) { + address pair = UniswapV2LibraryMock.pairFor(factory, tokenA, tokenB); + uint value = approveMax ? uint(int256(int8(-1))) : liquidity; IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline); } @@ -160,9 +155,9 @@ contract UniswapV2Router01 is IUniswapV2Router01 { address to, uint deadline, bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external override returns (uint amountToken, uint amountETH) { - address pair = UniswapV2Library.pairFor(factory, token, WETH); - uint value = approveMax ? uint(-1) : liquidity; + ) external returns (uint amountToken, uint amountETH) { + address pair = UniswapV2LibraryMock.pairFor(factory, token, WETH); + uint value = approveMax ? uint(int256(int8(-1))) : liquidity; IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s); (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline); } @@ -172,11 +167,11 @@ contract UniswapV2Router01 is IUniswapV2Router01 { function _swap(uint[] memory amounts, address[] memory path, address _to) private { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); - (address token0,) = UniswapV2Library.sortTokens(input, output); + (address token0,) = UniswapV2LibraryMock.sortTokens(input, output); uint amountOut = amounts[i + 1]; (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); - address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to; - IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(amount0Out, amount1Out, to, new bytes(0)); + address to = i < path.length - 2 ? UniswapV2LibraryMock.pairFor(factory, output, path[i + 2]) : _to; + IUniswapV2Pair(UniswapV2LibraryMock.pairFor(factory, input, output)).swap(amount0Out, amount1Out, to, new bytes(0)); } } function swapExactTokensForTokens( @@ -185,10 +180,10 @@ contract UniswapV2Router01 is IUniswapV2Router01 { address[] calldata path, address to, uint deadline - ) external override ensure(deadline) returns (uint[] memory amounts) { - amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); + ) external ensure(deadline) returns (uint[] memory amounts) { + amounts = UniswapV2LibraryMock.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); - TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); + TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function swapTokensForExactTokens( @@ -197,87 +192,83 @@ contract UniswapV2Router01 is IUniswapV2Router01 { address[] calldata path, address to, uint deadline - ) external override ensure(deadline) returns (uint[] memory amounts) { - amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path); + ) external ensure(deadline) returns (uint[] memory amounts) { + amounts = UniswapV2LibraryMock.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT'); - TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); + TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); } function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external - override payable ensure(deadline) returns (uint[] memory amounts) { require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH'); - amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path); + amounts = UniswapV2LibraryMock.getAmountsOut(factory, msg.value, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); IWETH(WETH).deposit{value: amounts[0]}(); - assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0])); + assert(IWETH(WETH).transfer(UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0])); _swap(amounts, path, to); } function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) external - override ensure(deadline) returns (uint[] memory amounts) { require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH'); - amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path); + amounts = UniswapV2LibraryMock.getAmountsIn(factory, amountOut, path); require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT'); - TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); + TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, address(this)); IWETH(WETH).withdraw(amounts[amounts.length - 1]); TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]); } function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external - override ensure(deadline) returns (uint[] memory amounts) { require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH'); - amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); + amounts = UniswapV2LibraryMock.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); - TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); + TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, address(this)); IWETH(WETH).withdraw(amounts[amounts.length - 1]); TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]); } function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) external - override payable ensure(deadline) returns (uint[] memory amounts) { require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH'); - amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path); + amounts = UniswapV2LibraryMock.getAmountsIn(factory, amountOut, path); require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT'); IWETH(WETH).deposit{value: amounts[0]}(); - assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0])); + assert(IWETH(WETH).transfer(UniswapV2LibraryMock.pairFor(factory, path[0], path[1]), amounts[0])); _swap(amounts, path, to); if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]); // refund dust eth, if any } - function quote(uint amountA, uint reserveA, uint reserveB) public pure override returns (uint amountB) { - return UniswapV2Library.quote(amountA, reserveA, reserveB); + function quote(uint amountA, uint reserveA, uint reserveB) public pure returns (uint amountB) { + return UniswapV2LibraryMock.quote(amountA, reserveA, reserveB); } - function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure override returns (uint amountOut) { - return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) { + return UniswapV2LibraryMock.getAmountOut(amountIn, reserveIn, reserveOut); } - function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure override returns (uint amountIn) { - return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure returns (uint amountIn) { + return UniswapV2LibraryMock.getAmountOut(amountOut, reserveIn, reserveOut); } - function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) { - return UniswapV2Library.getAmountsOut(factory, amountIn, path); + function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts) { + return UniswapV2LibraryMock.getAmountsOut(factory, amountIn, path); } - function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) { - return UniswapV2Library.getAmountsIn(factory, amountOut, path); + function getAmountsIn(uint amountOut, address[] memory path) public view returns (uint[] memory amounts) { + return UniswapV2LibraryMock.getAmountsIn(factory, amountOut, path); } } diff --git a/lib/uniswapV2/uniswap-v2-core/libraries/Math.sol b/contracts-test/uniswapV2/libraries/Math.sol similarity index 95% rename from lib/uniswapV2/uniswap-v2-core/libraries/Math.sol rename to contracts-test/uniswapV2/libraries/Math.sol index 7b940833b..d1dbc8046 100644 --- a/lib/uniswapV2/uniswap-v2-core/libraries/Math.sol +++ b/contracts-test/uniswapV2/libraries/Math.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity >=0.5.4; // a library for performing various math operations diff --git a/lib/uniswapV2/uniswap-v2-core/libraries/UQ112x112.sol b/contracts-test/uniswapV2/libraries/UQ112x112.sol similarity index 95% rename from lib/uniswapV2/uniswap-v2-core/libraries/UQ112x112.sol rename to contracts-test/uniswapV2/libraries/UQ112x112.sol index d574db68c..78759ce98 100644 --- a/lib/uniswapV2/uniswap-v2-core/libraries/UQ112x112.sol +++ b/contracts-test/uniswapV2/libraries/UQ112x112.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity >=0.5.4; // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IERC20.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IERC20.sol deleted file mode 100644 index c1e8c3e65..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IERC20.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity >=0.5.0; - -interface IERC20 { - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function decimals() external view returns (uint8); - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function allowance(address owner, address spender) external view returns (uint); - - function approve(address spender, uint value) external returns (bool); - function transfer(address to, uint value) external returns (bool); - function transferFrom(address from, address to, uint value) external returns (bool); -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Migrator.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Migrator.sol deleted file mode 100644 index 723e3487c..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Migrator.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV2Migrator { - function migrate(address token, uint amountTokenMin, uint amountETHMin, address to, uint deadline) external; -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Router01.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Router01.sol deleted file mode 100644 index 461996766..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IUniswapV2Router01.sol +++ /dev/null @@ -1,95 +0,0 @@ -pragma solidity >=0.6.2; - -interface IUniswapV2Router01 { - function factory() external pure returns (address); - function WETH() external pure returns (address); - - function addLiquidity( - address tokenA, - address tokenB, - uint amountADesired, - uint amountBDesired, - uint amountAMin, - uint amountBMin, - address to, - uint deadline - ) external returns (uint amountA, uint amountB, uint liquidity); - function addLiquidityETH( - address token, - uint amountTokenDesired, - uint amountTokenMin, - uint amountETHMin, - address to, - uint deadline - ) external payable returns (uint amountToken, uint amountETH, uint liquidity); - function removeLiquidity( - address tokenA, - address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, - address to, - uint deadline - ) external returns (uint amountA, uint amountB); - function removeLiquidityETH( - address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, - address to, - uint deadline - ) external returns (uint amountToken, uint amountETH); - function removeLiquidityWithPermit( - address tokenA, - address tokenB, - uint liquidity, - uint amountAMin, - uint amountBMin, - address to, - uint deadline, - bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external returns (uint amountA, uint amountB); - function removeLiquidityETHWithPermit( - address token, - uint liquidity, - uint amountTokenMin, - uint amountETHMin, - address to, - uint deadline, - bool approveMax, uint8 v, bytes32 r, bytes32 s - ) external returns (uint amountToken, uint amountETH); - function swapExactTokensForTokens( - uint amountIn, - uint amountOutMin, - address[] calldata path, - address to, - uint deadline - ) external returns (uint[] memory amounts); - function swapTokensForExactTokens( - uint amountOut, - uint amountInMax, - address[] calldata path, - address to, - uint deadline - ) external returns (uint[] memory amounts); - function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) - external - payable - returns (uint[] memory amounts); - function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) - external - returns (uint[] memory amounts); - function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) - external - returns (uint[] memory amounts); - function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) - external - payable - returns (uint[] memory amounts); - - function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); - function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); - function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); - function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); - function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IWETH.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IWETH.sol deleted file mode 100644 index e05fb770e..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/IWETH.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity >=0.5.0; - -interface IWETH { - function deposit() external payable; - function transfer(address to, uint value) external returns (bool); - function withdraw(uint) external; -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Exchange.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Exchange.sol deleted file mode 100644 index 039060fbd..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Exchange.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV1Exchange { - function balanceOf(address owner) external view returns (uint); - function transferFrom(address from, address to, uint value) external returns (bool); - function removeLiquidity(uint, uint, uint, uint) external returns (uint, uint); - function tokenToEthSwapInput(uint, uint, uint) external returns (uint); - function ethToTokenSwapInput(uint, uint) external payable returns (uint); -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Factory.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Factory.sol deleted file mode 100644 index 1c302fbe3..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/interfaces/V1/IUniswapV1Factory.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV1Factory { - function getExchange(address) external view returns (address); -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/SafeMath.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/SafeMath.sol deleted file mode 100644 index 65e004c32..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/SafeMath.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.6.12; - -// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) - -library SafeMath { - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, 'ds-math-add-overflow'); - } - - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, 'ds-math-sub-underflow'); - } - - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); - } -} diff --git a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2OracleLibrary.sol b/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2OracleLibrary.sol deleted file mode 100644 index 484266df6..000000000 --- a/contracts-test/uniswapV2/uniswap-V2-periphery/libraries/UniswapV2OracleLibrary.sol +++ /dev/null @@ -1,35 +0,0 @@ -pragma solidity >=0.5.0; - -import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol'; -import '@uniswap/lib/contracts/libraries/FixedPoint.sol'; - -// library with helper methods for oracles that are concerned with computing average prices -library UniswapV2OracleLibrary { - using FixedPoint for *; - - // helper function that returns the current block timestamp within the range of uint32, i.e. [0, 2**32 - 1] - function currentBlockTimestamp() internal view returns (uint32) { - return uint32(block.timestamp % 2 ** 32); - } - - // produces the cumulative price using counterfactuals to save gas and avoid a call to sync. - function currentCumulativePrices( - address pair - ) internal view returns (uint32 blockTimestamp, uint price0Cumulative, uint price1Cumulative) { - blockTimestamp = currentBlockTimestamp(); - price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast(); - price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast(); - - // if time has elapsed since the last update on the pair, mock the accumulated price values - (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves(); - if (blockTimestampLast != blockTimestamp) { - // subtraction overflow is desired - uint32 timeElapsed = blockTimestamp - blockTimestampLast; - // addition overflow is desired - // counterfactual - price0Cumulative += uint(FixedPoint.fraction(reserve1, reserve0)._x) * timeElapsed; - // counterfactual - price1Cumulative += uint(FixedPoint.fraction(reserve0, reserve1)._x) * timeElapsed; - } - } -} diff --git a/contracts/infrastructure/ArgentWalletDetector.sol b/contracts/infrastructure/ArgentWalletDetector.sol index 05d69e698..de0d70d7a 100644 --- a/contracts/infrastructure/ArgentWalletDetector.sol +++ b/contracts/infrastructure/ArgentWalletDetector.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "./base/Owned.sol"; interface IArgentWallet { @@ -54,7 +54,7 @@ contract ArgentWalletDetector is Owned { // emits when a new accepted implementation is added event ImplementationAdded(address indexed implementation); - constructor(bytes32[] memory _codes, address[] memory _implementations) public { + constructor(bytes32[] memory _codes, address[] memory _implementations) { for(uint i = 0; i < _codes.length; i++) { addCode(_codes[i]); } diff --git a/contracts/infrastructure/DappRegistry.sol b/contracts/infrastructure/DappRegistry.sol new file mode 100644 index 000000000..713375a54 --- /dev/null +++ b/contracts/infrastructure/DappRegistry.sol @@ -0,0 +1,277 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./IAuthoriser.sol"; +import "./dapp/IFilter.sol"; + +contract DappRegistry is IAuthoriser { + + // The timelock period + uint64 public timelockPeriod; + // The new timelock period + uint64 public newTimelockPeriod; + // Time at which the new timelock becomes effective + uint64 public timelockPeriodChangeAfter; + + // bit vector of enabled registry ids for each wallet + mapping (address => bytes32) public enabledRegistryIds; // [wallet] => [bit vector of 256 registry ids] + // authorised dapps and their filters for each registry id + mapping (uint8 => mapping (address => bytes32)) public authorisations; // [registryId] => [dapp] => [{filter:160}{validAfter:64}] + // pending authorised dapps and their filters for each registry id + mapping (uint8 => mapping (address => bytes32)) public pendingFilterUpdates; // [registryId] => [dapp] => [{filter:160}{validAfter:64}] + // owners for each registry id + mapping (uint8 => address) public registryOwners; // [registryId] => [owner] + + event RegistryCreated(uint8 registryId, address registryOwner); + event OwnerChanged(uint8 registryId, address newRegistryOwner); + event TimelockChangeRequested(uint64 newTimelockPeriod); + event TimelockChanged(uint64 newTimelockPeriod); + event FilterUpdated(uint8 indexed registryId, address dapp, address filter, uint256 validAfter); + event FilterUpdateRequested(uint8 indexed registryId, address dapp, address filter, uint256 validAfter); + event DappAdded(uint8 indexed registryId, address dapp, address filter, uint256 validAfter); + event DappRemoved(uint8 indexed registryId, address dapp); + event ToggledRegistry(address indexed sender, uint8 registryId, bool enabled); + + modifier onlyOwner(uint8 _registryId) { + validateOwner(_registryId); + _; + } + + constructor(uint64 _timelockPeriod) { + // set the timelock period + timelockPeriod = _timelockPeriod; + // set the owner of the Argent Registry (registryId = 0) + registryOwners[0] = msg.sender; + + emit RegistryCreated(0, msg.sender); + emit TimelockChanged(_timelockPeriod); + } + + /********* Wallet-centered functions *************/ + + /** + * @notice Returns whether a registry is enabled for a wallet + * @param _wallet The wallet + * @param _registryId The registry id + */ + function isEnabledRegistry(address _wallet, uint8 _registryId) external view returns (bool isEnabled) { + uint registries = uint(enabledRegistryIds[_wallet]); + return (((registries >> _registryId) & 1) > 0) /* "is bit set for regId?" */ == (_registryId > 0) /* "not Argent registry?" */; + } + + /** + * @notice Returns whether a (_spender, _to, _data) call is authorised for a wallet + * @param _wallet The wallet + * @param _spender The spender of the tokens for token approvals, or the target of the transaction otherwise + * @param _to The target of the transaction + * @param _data The calldata of the transaction + */ + function isAuthorised(address _wallet, address _spender, address _to, bytes calldata _data) public view override returns (bool) { + uint registries = uint(enabledRegistryIds[_wallet]); + // Check Argent Default Registry first. It is enabled by default, implying that a zero + // at position 0 of the `registries` bit vector means that the Argent Registry is enabled) + for(uint registryId = 0; registryId == 0 || (registries >> registryId) > 0; registryId++) { + bool isEnabled = (((registries >> registryId) & 1) > 0) /* "is bit set for regId?" */ == (registryId > 0) /* "not Argent registry?" */; + if(isEnabled) { // if registryId is enabled + uint auth = uint(authorisations[uint8(registryId)][_spender]); + uint validAfter = auth & 0xffffffffffffffff; + if (0 < validAfter && validAfter <= block.timestamp) { // if the current time is greater than the validity time + address filter = address(uint160(auth >> 64)); + if(filter == address(0) || IFilter(filter).isValid(_wallet, _spender, _to, _data)) { + return true; + } + } + } + } + return false; + } + + /** + * @notice Returns whether a collection of (_spender, _to, _data) calls are authorised for a wallet + * @param _wallet The wallet + * @param _spenders The spenders of the tokens for token approvals, or the targets of the transaction otherwise + * @param _to The targets of the transaction + * @param _data The calldata of the transaction + */ + function areAuthorised( + address _wallet, + address[] calldata _spenders, + address[] calldata _to, + bytes[] calldata _data + ) + external + view + override + returns (bool) + { + for(uint i = 0; i < _spenders.length; i++) { + if(!isAuthorised(_wallet, _spenders[i], _to[i], _data[i])) { + return false; + } + } + return true; + } + + /** + * @notice Allows a wallet to decide whether _registryId should be part of the list of enabled registries for that wallet + * @param _registryId The id of the registry to enable/disable + * @param _enabled Whether the registry should be enabled (true) or disabled (false) + */ + function toggleRegistry(uint8 _registryId, bool _enabled) external { + require(registryOwners[_registryId] != address(0), "DR: unknown registry"); + uint registries = uint(enabledRegistryIds[msg.sender]); + bool current = (((registries >> _registryId) & 1) > 0) /* "is bit set for regId?" */ == (_registryId > 0) /* "not Argent registry?" */; + if(current != _enabled) { + enabledRegistryIds[msg.sender] = bytes32(registries ^ (uint(1) << _registryId)); // toggle [_registryId]^th bit + emit ToggledRegistry(msg.sender, _registryId, _enabled); + } + } + + /************** Management of registry list *****************/ + + /** + * @notice Create a new registry. Only the owner of the Argent registry (i.e. the registry with id 0 -- hence the use of `onlyOwner(0)`) + * can create a new registry. + * @param _registryId The id of the registry to create + * @param _registryOwner The owner of that new registry + */ + function createRegistry(uint8 _registryId, address _registryOwner) external onlyOwner(0) { + require(_registryOwner != address(0), "DR: registry owner is 0"); + require(registryOwners[_registryId] == address(0), "DR: duplicate registry"); + registryOwners[_registryId] = _registryOwner; + emit RegistryCreated(_registryId, _registryOwner); + } + + // Note: removeRegistry is not supported because that would allow the owner to replace registries that + // have already been enabled by users with a new (potentially maliciously populated) registry + + /** + * @notice Lets a registry owner change the owner of the registry. + * @param _registryId The id of the registry + * @param _newRegistryOwner The new owner of the registry + */ + function changeOwner(uint8 _registryId, address _newRegistryOwner) external onlyOwner(_registryId) { + require(_newRegistryOwner != address(0), "DR: new registry owner is 0"); + registryOwners[_registryId] = _newRegistryOwner; + emit OwnerChanged(_registryId, _newRegistryOwner); + } + + /** + * @notice Request a change of the timelock value. Only the owner of the Argent registry (i.e. the registry with id 0 -- + * hence the use of `onlyOwner(0)`) can perform that action. This action can be confirmed after the (old) timelock period. + * @param _newTimelockPeriod The new timelock period + */ + function requestTimelockChange(uint64 _newTimelockPeriod) external onlyOwner(0) { + newTimelockPeriod = _newTimelockPeriod; + timelockPeriodChangeAfter = uint64(block.timestamp) + timelockPeriod; + emit TimelockChangeRequested(_newTimelockPeriod); + } + + /** + * @notice Confirm a change of the timelock value requested by `requestTimelockChange()`. + */ + function confirmTimelockChange() external { + uint64 newPeriod = newTimelockPeriod; + require(timelockPeriodChangeAfter > 0 && timelockPeriodChangeAfter <= block.timestamp, "DR: can't (yet) change timelock"); + timelockPeriod = newPeriod; + newTimelockPeriod = 0; + timelockPeriodChangeAfter = 0; + emit TimelockChanged(newPeriod); + } + + /************** Management of registries' content *****************/ + + /** + * @notice Returns the (filter, validAfter) tuple recorded for a dapp in a given registry. + * `filter` is the authorisation filter stored for the dapp (if any) and `validAfter` is the + * timestamp after which the filter becomes active. + * @param _registryId The registry id + * @param _dapp The dapp + */ + function getAuthorisation(uint8 _registryId, address _dapp) external view returns (address filter, uint64 validAfter) { + uint auth = uint(authorisations[_registryId][_dapp]); + filter = address(uint160(auth >> 64)); + validAfter = uint64(auth & 0xffffffffffffffff); + } + + /** + * @notice Add a new dapp to the registry with an optional filter + * @param _registryId The id of the registry to modify + * @param _dapp The address of the dapp contract to authorise. + * @param _filter The address of the filter contract to use, if any. + */ + function addDapp(uint8 _registryId, address _dapp, address _filter) external onlyOwner(_registryId) { + require(authorisations[_registryId][_dapp] == bytes32(0), "DR: dapp already added"); + uint validAfter = block.timestamp + timelockPeriod; + // Store the new authorisation as {filter:160}{validAfter:64}. + authorisations[_registryId][_dapp] = bytes32((uint(uint160(_filter)) << 64) | validAfter); + emit DappAdded(_registryId, _dapp, _filter, validAfter); + } + + + /** + * @notice Deauthorise a dapp in a registry + * @param _registryId The id of the registry to modify + * @param _dapp The address of the dapp contract to deauthorise. + */ + function removeDapp(uint8 _registryId, address _dapp) external onlyOwner(_registryId) { + require(authorisations[_registryId][_dapp] != bytes32(0), "DR: unknown dapp"); + delete authorisations[_registryId][_dapp]; + delete pendingFilterUpdates[_registryId][_dapp]; + emit DappRemoved(_registryId, _dapp); + } + + /** + * @notice Request to change an authorisation filter for a dapp that has previously been authorised. We cannot + * immediately override the existing filter and need to store the new filter for a timelock period before being + * able to change the filter. + * @param _registryId The id of the registry to modify + * @param _dapp The address of the dapp contract to authorise. + * @param _filter The address of the new filter contract to use. + */ + function requestFilterUpdate(uint8 _registryId, address _dapp, address _filter) external onlyOwner(_registryId) { + require(authorisations[_registryId][_dapp] != bytes32(0), "DR: unknown dapp"); + uint validAfter = block.timestamp + timelockPeriod; + // Store the future authorisation as {filter:160}{validAfter:64} + pendingFilterUpdates[_registryId][_dapp] = bytes32((uint(uint160(_filter)) << 64) | validAfter); + emit FilterUpdateRequested(_registryId, _dapp, _filter, validAfter); + } + + /** + * @notice Confirm the filter change requested by `requestFilterUpdate` + * @param _registryId The id of the registry to modify + * @param _dapp The address of the dapp contract to authorise. + */ + function confirmFilterUpdate(uint8 _registryId, address _dapp) external { + uint newAuth = uint(pendingFilterUpdates[_registryId][_dapp]); + require(newAuth > 0, "DR: no pending filter update"); + uint validAfter = newAuth & 0xffffffffffffffff; + require(validAfter <= block.timestamp, "DR: too early to confirm auth"); + authorisations[_registryId][_dapp] = bytes32(newAuth); + emit FilterUpdated(_registryId, _dapp, address(uint160(newAuth >> 64)), validAfter); + delete pendingFilterUpdates[_registryId][_dapp]; + } + + /******** Internal Functions ***********/ + + function validateOwner(uint8 _registryId) internal view { + address owner = registryOwners[_registryId]; + require(owner != address(0), "DR: unknown registry"); + require(msg.sender == owner, "DR: sender != registry owner"); + } +} \ No newline at end of file diff --git a/contracts/infrastructure/DexRegistry.sol b/contracts/infrastructure/DexRegistry.sol deleted file mode 100644 index d0d4fe822..000000000 --- a/contracts/infrastructure/DexRegistry.sol +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2020 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./base/Owned.sol"; -import "./IDexRegistry.sol"; - -/** - * @title DexRegistry - * @notice Simple registry containing whitelisted DEX adapters to be used with the TokenExchanger. - * @author Olivier VDB - - */ -contract DexRegistry is IDexRegistry, Owned { - - // Whitelisted DEX adapters - mapping(address => bool) public isAuthorised; - - event DexAdded(address indexed _dex); - event DexRemoved(address indexed _dex); - - - /** - * @notice Add/Remove a DEX adapter to/from the whitelist. - * @param _dexes array of DEX adapters to add to (or remove from) the whitelist - * @param _authorised array where each entry is true to add the corresponding DEX to the whitelist, false to remove it - */ - function setAuthorised(address[] calldata _dexes, bool[] calldata _authorised) external onlyOwner { - for(uint256 i = 0; i < _dexes.length; i++) { - if(isAuthorised[_dexes[i]] != _authorised[i]) { - isAuthorised[_dexes[i]] = _authorised[i]; - if(_authorised[i]) { - emit DexAdded(_dexes[i]); - } else { - emit DexRemoved(_dexes[i]); - } - } - } - } - - function verifyExchangeAdapters(IAugustusSwapper.Path[] calldata _path) external override view { - for (uint i = 0; i < _path.length; i++) { - for (uint j = 0; j < _path[i].routes.length; j++) { - require(isAuthorised[_path[i].routes[j].exchange], "DR: Unauthorised DEX"); - } - } - } - - function verifyExchangeAdapters(IAugustusSwapper.BuyRoute[] calldata _routes) external override view { - for (uint j = 0; j < _routes.length; j++) { - require(isAuthorised[_routes[j].exchange], "DR: Unauthorised DEX"); - } - } - - -} \ No newline at end of file diff --git a/contracts/infrastructure/IAuthoriser.sol b/contracts/infrastructure/IAuthoriser.sol new file mode 100644 index 000000000..0528df6c2 --- /dev/null +++ b/contracts/infrastructure/IAuthoriser.sol @@ -0,0 +1,30 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +interface IAuthoriser { + function isAuthorised(address _sender, address _spender, address _to, bytes calldata _data) external view returns (bool); + function areAuthorised( + address _spender, + address[] calldata _spenders, + address[] calldata _to, + bytes[] calldata _data + ) + external + view + returns (bool); +} \ No newline at end of file diff --git a/contracts/infrastructure/IMakerRegistry.sol b/contracts/infrastructure/IMakerRegistry.sol deleted file mode 100644 index 214373c19..000000000 --- a/contracts/infrastructure/IMakerRegistry.sol +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2020 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; - -import "../../lib/maker/MakerInterfaces.sol"; -/** - * @title IMakerRegistry - * @notice Interface for the MakerRegistry - */ -interface IMakerRegistry { - function collaterals(address _collateral) external view returns (bool exists, uint128 index, JoinLike join, bytes32 ilk); - function addCollateral(JoinLike _joinAdapter) external; - function removeCollateral(address _token) external; - function getCollateralTokens() external view returns (address[] memory _tokens); - function getIlk(address _token) external view returns (bytes32 _ilk); - function getCollateral(bytes32 _ilk) external view returns (JoinLike _join, GemLike _token); -} \ No newline at end of file diff --git a/contracts/infrastructure/IModuleRegistry.sol b/contracts/infrastructure/IModuleRegistry.sol index b522d5b2c..789fe0180 100644 --- a/contracts/infrastructure/IModuleRegistry.sol +++ b/contracts/infrastructure/IModuleRegistry.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; /** * @title IModuleRegistry diff --git a/contracts/infrastructure/ITokenPriceRegistry.sol b/contracts/infrastructure/ITokenRegistry.sol similarity index 78% rename from contracts/infrastructure/ITokenPriceRegistry.sol rename to contracts/infrastructure/ITokenRegistry.sol index 239efa2af..9a0cdbb40 100644 --- a/contracts/infrastructure/ITokenPriceRegistry.sol +++ b/contracts/infrastructure/ITokenRegistry.sol @@ -12,13 +12,13 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; /** - * @title ITokenPriceRegistry - * @notice TokenPriceRegistry interface + * @title ITokenRegistry + * @notice TokenRegistry interface */ -interface ITokenPriceRegistry { - function getTokenPrice(address _token) external view returns (uint184 _price); +interface ITokenRegistry { function isTokenTradable(address _token) external view returns (bool _isTradable); + function areTokensTradable(address[] calldata _tokens) external view returns (bool _areTradable); } \ No newline at end of file diff --git a/contracts/infrastructure/TokenPriceRegistry.sol b/contracts/infrastructure/TokenPriceRegistry.sol deleted file mode 100644 index 1764ee3d7..000000000 --- a/contracts/infrastructure/TokenPriceRegistry.sol +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./ITokenPriceRegistry.sol"; -import "./base/Managed.sol"; - -/** - * @title TokenPriceRegistry - * @notice Contract storing the token prices. - * @notice Note that prices stored here = price per token * 10^(18-token decimals) - * The contract only defines basic setters and getters with no logic. - * Only managers of this contract can modify its state. - */ -contract TokenPriceRegistry is ITokenPriceRegistry, Managed { - struct TokenInfo { - uint184 cachedPrice; - uint64 updatedAt; - bool isTradable; - } - - // Price info per token - mapping(address => TokenInfo) public tokenInfo; - // The minimum period between two price updates - uint256 public minPriceUpdatePeriod; - - - // Getters - - function getTokenPrice(address _token) external override view returns (uint184 _price) { - _price = tokenInfo[_token].cachedPrice; - } - function isTokenTradable(address _token) external override view returns (bool _isTradable) { - _isTradable = tokenInfo[_token].isTradable; - } - function getPriceForTokenList(address[] calldata _tokens) external view returns (uint184[] memory _prices) { - _prices = new uint184[](_tokens.length); - for (uint256 i = 0; i < _tokens.length; i++) { - _prices[i] = tokenInfo[_tokens[i]].cachedPrice; - } - } - function getTradableForTokenList(address[] calldata _tokens) external view returns (bool[] memory _tradable) { - _tradable = new bool[](_tokens.length); - for (uint256 i = 0; i < _tokens.length; i++) { - _tradable[i] = tokenInfo[_tokens[i]].isTradable; - } - } - - // Setters - - function setMinPriceUpdatePeriod(uint256 _newPeriod) external onlyOwner { - minPriceUpdatePeriod = _newPeriod; - } - function setPriceForTokenList(address[] calldata _tokens, uint184[] calldata _prices) external onlyManager { - require(_tokens.length == _prices.length, "TPS: Array length mismatch"); - for (uint i = 0; i < _tokens.length; i++) { - uint64 updatedAt = tokenInfo[_tokens[i]].updatedAt; - require(updatedAt == 0 || block.timestamp >= updatedAt + minPriceUpdatePeriod, "TPS: Price updated too early"); - tokenInfo[_tokens[i]].cachedPrice = _prices[i]; - tokenInfo[_tokens[i]].updatedAt = uint64(block.timestamp); - } - } - function setTradableForTokenList(address[] calldata _tokens, bool[] calldata _tradable) external { - require(_tokens.length == _tradable.length, "TPS: Array length mismatch"); - for (uint256 i = 0; i < _tokens.length; i++) { - require(msg.sender == owner || (!_tradable[i] && managers[msg.sender]), "TPS: Unauthorised"); - tokenInfo[_tokens[i]].isTradable = _tradable[i]; - } - } -} \ No newline at end of file diff --git a/contracts/infrastructure/TokenRegistry.sol b/contracts/infrastructure/TokenRegistry.sol new file mode 100644 index 000000000..bf79daf8a --- /dev/null +++ b/contracts/infrastructure/TokenRegistry.sol @@ -0,0 +1,66 @@ +// Copyright (C) 2018 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./ITokenRegistry.sol"; +import "./base/Managed.sol"; + +/** + * @title TokenRegistry + * @notice Contract storing a list of tokens that can be safely traded. + * @notice Only the owner can make a token tradable. Managers can make a token untradable. + */ +contract TokenRegistry is ITokenRegistry, Managed { + + // Tradable flag per token + mapping(address => bool) public isTradable; + + function isTokenTradable(address _token) external override view returns (bool _isTradable) { + _isTradable = isTradable[_token]; + } + + function areTokensTradable(address[] calldata _tokens) external override view returns (bool _areTradable) { + for (uint256 i = 0; i < _tokens.length; i++) { + if(!isTradable[_tokens[i]]) { + return false; + } + } + return true; + } + + function getTradableForTokenList(address[] calldata _tokens) external view returns (bool[] memory _tradable) { + _tradable = new bool[](_tokens.length); + for (uint256 i = 0; i < _tokens.length; i++) { + _tradable[i] = isTradable[_tokens[i]]; + } + } + + function setTradableForTokenList(address[] calldata _tokens, bool[] calldata _tradable) external { + require(_tokens.length == _tradable.length, "TR: Array length mismatch"); + if(msg.sender == owner) { + for (uint256 i = 0; i < _tokens.length; i++) { + isTradable[_tokens[i]] = _tradable[i]; + } + } else { + require(managers[msg.sender], "TR: Unauthorised"); + for (uint256 i = 0; i < _tokens.length; i++) { + require(_tradable[i] == false, "TR: Unauthorised operation"); + isTradable[_tokens[i]] = _tradable[i]; + } + } + } +} \ No newline at end of file diff --git a/contracts/infrastructure/WalletFactory.sol b/contracts/infrastructure/WalletFactory.sol index e06d2f8a8..1fb2c43e9 100644 --- a/contracts/infrastructure/WalletFactory.sol +++ b/contracts/infrastructure/WalletFactory.sol @@ -14,38 +14,32 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "../wallet/Proxy.sol"; import "../wallet/BaseWallet.sol"; -import "./base/Owned.sol"; import "./base/Managed.sol"; import "./storage/IGuardianStorage.sol"; -import "./IModuleRegistry.sol"; -import "../modules/common/IVersionManager.sol"; import "../modules/common/Utils.sol"; /** * @title WalletFactory * @notice The WalletFactory contract creates and assigns wallets to accounts. - * @author Julien Niset - + * @author Julien Niset, Olivier VDB - , */ -contract WalletFactory is Owned, Managed { +contract WalletFactory is Managed { - address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address constant internal ETH_TOKEN = address(0); - // The address of the module dregistry - address public moduleRegistry; // The address of the base wallet implementation - address public walletImplementation; + address immutable public walletImplementation; // The address of the GuardianStorage - address public guardianStorage; + address immutable public guardianStorage; // The recipient of the refund address public refundAddress; // *************** Events *************************** // - event ModuleRegistryChanged(address addr); event RefundAddressChanged(address addr); event WalletCreated(address indexed wallet, address indexed owner, address indexed guardian, address refundToken, uint256 refundAmount); @@ -54,48 +48,56 @@ contract WalletFactory is Owned, Managed { /** * @notice Default constructor. */ - constructor(address _moduleRegistry, address _walletImplementation, address _guardianStorage, address _refundAddress) public { - require(_moduleRegistry != address(0), "WF: ModuleRegistry address not defined"); - require(_walletImplementation != address(0), "WF: WalletImplementation address not defined"); - require(_guardianStorage != address(0), "WF: GuardianStorage address not defined"); - require(_refundAddress != address(0), "WF: refund address not defined"); - moduleRegistry = _moduleRegistry; + constructor(address _walletImplementation, address _guardianStorage, address _refundAddress) { + require(_walletImplementation != address(0), "WF: empty wallet implementation"); + require(_guardianStorage != address(0), "WF: empty guardian storage"); + require(_refundAddress != address(0), "WF: empty refund address"); walletImplementation = _walletImplementation; guardianStorage = _guardianStorage; refundAddress = _refundAddress; } // *************** External Functions ********************* // + + /** + * @notice Disables the ability for the owner of the factory to revoke a manager. + */ + function revokeManager(address /*_manager*/) override external pure { + revert("WF: managers can't be revoked"); + } /** - * @notice Lets the manager create a wallet for an owner account at a specific address. - * The wallet is initialised with the version manager module, the version number and a first guardian. - * The wallet is created using the CREATE2 opcode. + * @notice Creates a wallet for an owner account at a specific address. + * The wallet is initialised with the target modules and a first guardian by default. + * The wallet is created using the CREATE2 opcode and must have been approved + * by a manager of the factory. * @param _owner The account address. - * @param _versionManager The version manager module + * @param _modules The list of modules for the wallet. * @param _guardian The guardian address. * @param _salt The salt. - * @param _version The version of the feature bundle. + * @param _refundAmount The amount to refund to the relayer. + * @param _refundToken The token to use to refund the relayer. + * @param _ownerSignature The owner signature on the refund info. + * @param _managerSignature The manager signature on the wallet address. */ function createCounterfactualWallet( address _owner, - address _versionManager, + address[] calldata _modules, address _guardian, - bytes32 _salt, - uint256 _version, + bytes20 _salt, uint256 _refundAmount, address _refundToken, - bytes calldata _ownerSignature + bytes calldata _ownerSignature, + bytes calldata _managerSignature ) external - onlyManager returns (address _wallet) { - validateInputs(_owner, _versionManager, _guardian, _version); - bytes32 newsalt = newSalt(_salt, _owner, _versionManager, _guardian, _version); - Proxy proxy = new Proxy{salt: newsalt}(walletImplementation); - address payable wallet = address(proxy); - configureWallet(BaseWallet(wallet), _owner, _versionManager, _guardian, _version); + validateInputs(_owner, _modules, _guardian); + bytes32 newsalt = newSalt(_salt, _owner, _modules, _guardian); + address payable wallet = payable(new Proxy{salt: newsalt}(walletImplementation)); + validateAuthorisedCreation(wallet, _managerSignature); + configureWallet(BaseWallet(wallet), _owner, _modules, _guardian); if (_refundAmount > 0 && _ownerSignature.length == 65) { validateAndRefund(wallet, _owner, _refundAmount, _refundToken, _ownerSignature); } @@ -111,53 +113,41 @@ contract WalletFactory is Owned, Managed { /** * @notice Gets the address of a counterfactual wallet with a first default guardian. * @param _owner The account address. - * @param _versionManager The version manager module + * @param _modules The list of modules for wallet. * @param _guardian The guardian address. * @param _salt The salt. - * @param _version The version of feature bundle. * @return _wallet The address that the wallet will have when created using CREATE2 and the same input parameters. */ function getAddressForCounterfactualWallet( address _owner, - address _versionManager, + address[] calldata _modules, address _guardian, - bytes32 _salt, - uint256 _version + bytes20 _salt ) external view returns (address _wallet) { - validateInputs(_owner, _versionManager, _guardian, _version); - bytes32 newsalt = newSalt(_salt, _owner, _versionManager, _guardian, _version); - bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(walletImplementation)); + validateInputs(_owner, _modules, _guardian); + bytes32 newsalt = newSalt(_salt, _owner, _modules, _guardian); + bytes memory code = abi.encodePacked(type(Proxy).creationCode, uint256(uint160(walletImplementation))); bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), newsalt, keccak256(code))); _wallet = address(uint160(uint256(hash))); } /** - * @notice Lets the owner change the address of the module registry contract. - * @param _moduleRegistry The address of the module registry contract. - */ - function changeModuleRegistry(address _moduleRegistry) external onlyOwner { - require(_moduleRegistry != address(0), "WF: address cannot be null"); - moduleRegistry = _moduleRegistry; - emit ModuleRegistryChanged(_moduleRegistry); - } - - /** - * @notice Lets the owner change the refund address. + * @notice Lets the owner of the factory change the refund address. * @param _refundAddress The address to use for refunds. */ function changeRefundAddress(address _refundAddress) external onlyOwner { - require(_refundAddress != address(0), "WF: address cannot be null"); + require(_refundAddress != address(0), "WF: cannot set to empty"); refundAddress = _refundAddress; emit RefundAddressChanged(_refundAddress); } /** - * @notice Inits the module for a wallet by doing nothing. - * The method can only be called by the wallet itself. + * @notice Required to make the factory a module during the + * initialisation of the wallet. * @param _wallet The wallet. */ function init(BaseWallet _wallet) external pure { @@ -169,59 +159,66 @@ contract WalletFactory is Owned, Managed { /** * @notice Helper method to configure a wallet for a set of input parameters. * @param _wallet The target wallet - * @param _owner The account address. - * @param _versionManager The version manager module - * @param _guardian The guardian address. - * @param _version The version of the feature bundle. + * @param _owner The owner address. + * @param _modules The list of modules. + * @param _guardian The guardian. */ - function configureWallet( - BaseWallet _wallet, - address _owner, - address _versionManager, - address _guardian, - uint256 _version - ) - internal - { - // add the factory to modules so it can add a guardian and upgrade the wallet to the required version - address[] memory extendedModules = new address[](2); - extendedModules[0] = _versionManager; - extendedModules[1] = address(this); + function configureWallet(BaseWallet _wallet, address _owner, address[] calldata _modules, address _guardian) internal { + // add the factory to modules so it can add the first guardian and trigger the refund + address[] memory extendedModules = new address[](_modules.length + 1); + extendedModules[0] = address(this); + for (uint i = 0; i < _modules.length; i++) { + extendedModules[i + 1] = _modules[i]; + } // initialise the wallet with the owner and the extended modules _wallet.init(_owner, extendedModules); - // add guardian + // add the first guardian IGuardianStorage(guardianStorage).addGuardian(address(_wallet), _guardian); - - // upgrade the wallet - IVersionManager(_versionManager).upgradeWallet(address(_wallet), _version); } /** * @notice Generates a new salt based on a provided salt, an owner, a list of modules and an optional guardian. - * @param _salt The slat provided. + * The extra parameters are pre-hashed to be compatible with zk-sync CREATE2 API (!! the order of the parameters + * assumes https://github.com/matter-labs/zksync/pull/259 has been merged !!). + * @param _salt The salt provided. In practice the hash of the L2 public key. * @param _owner The owner address. - * @param _versionManager The version manager module + * @param _modules The list of modules for wallet. * @param _guardian The guardian address. - * @param _version The version of feature bundle */ - function newSalt(bytes32 _salt, address _owner, address _versionManager, address _guardian, uint256 _version) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(_salt, _owner, _versionManager, _guardian, _version)); + function newSalt(bytes20 _salt, address _owner, address[] calldata _modules, address _guardian) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(keccak256(abi.encodePacked(_owner, _modules, _guardian)), _salt)); } /** - * @notice Throws if the owner, guardian, version or version manager is invalid. + * @notice Throws if the owner, guardian, or module array is invalid. * @param _owner The owner address. - * @param _versionManager The version manager module - * @param _guardian The guardian address - * @param _version The version of feature bundle + * @param _modules The list of modules for the wallet. + * @param _guardian The guardian address. */ - function validateInputs(address _owner, address _versionManager, address _guardian, uint256 _version) internal view { - require(_owner != address(0), "WF: owner cannot be null"); - require(IModuleRegistry(moduleRegistry).isRegisteredModule(_versionManager), "WF: invalid _versionManager"); - require(_guardian != (address(0)), "WF: guardian cannot be null"); - require(_version > 0, "WF: invalid _version"); + function validateInputs(address _owner, address[] calldata _modules, address _guardian) internal pure { + require(_owner != address(0), "WF: empty owner address"); + require(_owner != _guardian, "WF: owner cannot be guardian"); + require(_modules.length > 0, "WF: empty modules"); + require(_guardian != (address(0)), "WF: empty guardian"); + } + + /** + * @notice Throws if the sender is not a manager and the manager's signature for the + * creation of the new wallet is invalid. + * @param _wallet The wallet address + * @param _managerSignature The manager's signature + */ + function validateAuthorisedCreation(address _wallet, bytes memory _managerSignature) internal view { + address manager; + if(_managerSignature.length != 65) { + manager = msg.sender; + } else { + bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", bytes32(uint256(uint160(_wallet))))); + manager = Utils.recoverSigner(signedHash, _managerSignature, 0); + } + require(managers[manager], "WF: unauthorised wallet creation"); } /** @@ -243,7 +240,7 @@ contract WalletFactory is Owned, Managed { { bytes32 signedHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", - keccak256(abi.encodePacked(_refundAmount, _refundToken)) + keccak256(abi.encodePacked(_wallet, _refundAmount, _refundToken)) )); address signer = Utils.recoverSigner(signedHash, _ownerSignature, 0); if (signer == _owner) { @@ -287,4 +284,4 @@ contract WalletFactory is Owned, Managed { } } } -} \ No newline at end of file +} diff --git a/contracts/infrastructure/base/Managed.sol b/contracts/infrastructure/base/Managed.sol index b28e2776a..9053f1d82 100644 --- a/contracts/infrastructure/base/Managed.sol +++ b/contracts/infrastructure/base/Managed.sol @@ -14,14 +14,14 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.8.3; import "./Owned.sol"; /** * @title Managed * @notice Basic contract that defines a set of managers. Only the owner can add/remove managers. - * @author Julien Niset - + * @author Julien Niset, Olivier VDB - , */ contract Managed is Owned { @@ -55,7 +55,8 @@ contract Managed is Owned { * @notice Revokes a manager. * @param _manager The address of the manager. */ - function revokeManager(address _manager) external onlyOwner { + function revokeManager(address _manager) external virtual onlyOwner { + // solhint-disable-next-line reason-string require(managers[_manager] == true, "M: Target must be an existing manager"); delete managers[_manager]; emit ManagerRevoked(_manager); diff --git a/contracts/infrastructure/base/Owned.sol b/contracts/infrastructure/base/Owned.sol index 935037ae8..5d725477c 100644 --- a/contracts/infrastructure/base/Owned.sol +++ b/contracts/infrastructure/base/Owned.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; /** * @title Owned diff --git a/contracts/infrastructure/ICompoundRegistry.sol b/contracts/infrastructure/dapp/AaveV1ATokenFilter.sol similarity index 59% rename from contracts/infrastructure/ICompoundRegistry.sol rename to contracts/infrastructure/dapp/AaveV1ATokenFilter.sol index 1a6a91872..0d68938a2 100644 --- a/contracts/infrastructure/ICompoundRegistry.sol +++ b/contracts/infrastructure/dapp/AaveV1ATokenFilter.sol @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Argent Labs Ltd. +// Copyright (C) 2021 Argent Labs Ltd. // 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 @@ -14,18 +14,17 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.8.3; -/** - * @title ICompoundRegistry - * @notice Interface for CompoundRegistry - */ -interface ICompoundRegistry { - function addCToken(address _underlying, address _cToken) external; +import "./BaseFilter.sol"; - function removeCToken(address _underlying) external; +contract AaveV1ATokenFilter is BaseFilter { - function getCToken(address _underlying) external view returns (address); + bytes4 private constant REDEEM = bytes4(keccak256("redeem(uint256)")); - function listUnderlyings() external view returns (address[] memory); + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + bytes4 method = getMethod(_data); + + return (method == REDEEM); + } } \ No newline at end of file diff --git a/contracts/infrastructure/IDexRegistry.sol b/contracts/infrastructure/dapp/AaveV1Filter.sol similarity index 58% rename from contracts/infrastructure/IDexRegistry.sol rename to contracts/infrastructure/dapp/AaveV1Filter.sol index 093112a7f..60a81e5b9 100644 --- a/contracts/infrastructure/IDexRegistry.sol +++ b/contracts/infrastructure/dapp/AaveV1Filter.sol @@ -1,4 +1,4 @@ -// Copyright (C) 2020 Argent Labs Ltd. +// Copyright (C) 2021 Argent Labs Ltd. // 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 @@ -14,17 +14,17 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "../../lib/paraswap/IAugustusSwapper.sol"; - -/** - * @title IDexRegistry - * @notice Interface for DexRegistry. - * @author Olivier VDB - - */ -interface IDexRegistry { - function verifyExchangeAdapters(IAugustusSwapper.Path[] calldata _path) external view; - function verifyExchangeAdapters(IAugustusSwapper.BuyRoute[] calldata _routes) external view; +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract AaveV1LendingPoolFilter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("deposit(address,uint256,uint16)")); + + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + bytes4 method = getMethod(_data); + + return (method == DEPOSIT); + } } \ No newline at end of file diff --git a/contracts/infrastructure/dapp/AaveV2Filter.sol b/contracts/infrastructure/dapp/AaveV2Filter.sol new file mode 100644 index 000000000..c66ef8364 --- /dev/null +++ b/contracts/infrastructure/dapp/AaveV2Filter.sol @@ -0,0 +1,42 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract AaveV2Filter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("deposit(address,uint256,address,uint16)")); + bytes4 private constant WITHDRAW = bytes4(keccak256("withdraw(address,uint256,address)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + function isValid(address _wallet, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + + // only allow deposits and withdrawals with wallet as beneficiary + if(method == DEPOSIT || method == WITHDRAW) { + return _wallet == abi.decode(_data[68:], (address)); + } + + // only allow approve (LendingPool can only transfer a valid asset => no need to validate the token address here) + return (method == ERC20_APPROVE); + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/BalancerFilter.sol b/contracts/infrastructure/dapp/BalancerFilter.sol new file mode 100644 index 000000000..072fa9943 --- /dev/null +++ b/contracts/infrastructure/dapp/BalancerFilter.sol @@ -0,0 +1,39 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract BalancerFilter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("joinswapExternAmountIn(address,uint256,uint256)")); + bytes4 private constant WITHDRAW1 = bytes4(keccak256("exitswapExternAmountOut(address,uint256,uint256)")); + bytes4 private constant WITHDRAW2 = bytes4(keccak256("exitswapPoolAmountIn(address,uint256,uint256)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + // approve() is authorised for approving an underlying token to the pool (in case of deposits) or + // for approving the pool LP token to itself (in case of withdrawals). Note that BPool can only + // transfer underlying pool tokens (or burn its own pool LP tokens) => no need to validate the token address here + return method == ERC20_APPROVE || method == DEPOSIT || method == WITHDRAW1 || method == WITHDRAW2; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/BaseFilter.sol b/contracts/infrastructure/dapp/BaseFilter.sol new file mode 100644 index 000000000..23f5eee73 --- /dev/null +++ b/contracts/infrastructure/dapp/BaseFilter.sol @@ -0,0 +1,28 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./IFilter.sol"; + +abstract contract BaseFilter is IFilter { + function getMethod(bytes memory _data) internal pure returns (bytes4 method) { + // solhint-disable-next-line no-inline-assembly + assembly { + method := mload(add(_data, 0x20)) + } + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/CompoundCTokenFilter.sol b/contracts/infrastructure/dapp/CompoundCTokenFilter.sol new file mode 100644 index 000000000..039fca067 --- /dev/null +++ b/contracts/infrastructure/dapp/CompoundCTokenFilter.sol @@ -0,0 +1,67 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract CompoundCTokenFilter is BaseFilter { + + bytes4 private constant CETH_MINT = bytes4(keccak256("mint()")); + bytes4 private constant CERC20_MINT = bytes4(keccak256("mint(uint256)")); + bytes4 private constant CTOKEN_REDEEM = bytes4(keccak256("redeem(uint256)")); + bytes4 private constant CTOKEN_REDEEM_UNDERLYING = bytes4(keccak256("redeemUnderlying(uint256)")); + bytes4 private constant CTOKEN_BORROW = bytes4(keccak256("borrow(uint256)")); + bytes4 private constant CETH_REPAY_BORROW = bytes4(keccak256("repayBorrow()")); + bytes4 private constant CERC20_REPAY_BORROW = bytes4(keccak256("repayBorrow(uint256)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + address public immutable underlying; + + constructor (address _underlying) { + underlying = _underlying; + } + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer for cErc20 + if (_data.length < 4) { + return (_data.length == 0) && (underlying == address(0)); + } + bytes4 method = getMethod(_data); + // cToken methods + if (_spender == _to) { + if (underlying == address(0)) { + return ( + method == CETH_MINT || + method == CTOKEN_REDEEM || + method == CTOKEN_REDEEM_UNDERLYING || + method == CTOKEN_BORROW || + method == CETH_REPAY_BORROW); + } else { + return ( + method == CERC20_MINT || + method == CTOKEN_REDEEM || + method == CTOKEN_REDEEM_UNDERLYING || + method == CTOKEN_BORROW || + method == CERC20_REPAY_BORROW); + } + // ERC20 methods + } else { + // only allow an approve on the underlying + return (method == ERC20_APPROVE && underlying == _to); + } + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/CurveFilter.sol b/contracts/infrastructure/dapp/CurveFilter.sol new file mode 100644 index 000000000..8f8ea8fec --- /dev/null +++ b/contracts/infrastructure/dapp/CurveFilter.sol @@ -0,0 +1,45 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +/** + * @title CurveFilter + * @notice Filter used for calls to every supported Curve exchange (pool), e.g. 0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51 + * @author Olivier VDB - + */ +contract CurveFilter is BaseFilter { + + bytes4 private constant EXCHANGE = bytes4(keccak256("exchange(int128,int128,uint256,uint256)")); + bytes4 private constant EXCHANGE_UNDERLYING = bytes4(keccak256("exchange_underlying(int128,int128,uint256,uint256)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + + bytes4 methodId = getMethod(_data); + if(_spender == _to) { + return (methodId == EXCHANGE || methodId == EXCHANGE_UNDERLYING); + } else { + return (methodId == ERC20_APPROVE); + } + } +} \ No newline at end of file diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSStop.sol b/contracts/infrastructure/dapp/IFilter.sol similarity index 61% rename from contracts-legacy/v1.6.0/lib/maker/DS/DSStop.sol rename to contracts/infrastructure/dapp/IFilter.sol index c97f5ccb1..8fd162b3a 100644 --- a/contracts-legacy/v1.6.0/lib/maker/DS/DSStop.sol +++ b/contracts/infrastructure/dapp/IFilter.sol @@ -1,6 +1,4 @@ -/// stop.sol -- mixin for enable/disable functionality - -// Copyright (C) 2017 DappHub, LLC +// Copyright (C) 2021 Argent Labs Ltd. // 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 @@ -15,23 +13,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; - -import "./DSAuth.sol"; - -contract DSStop is DSAuth { - - bool public stopped; - - modifier stoppable { - require(!stopped); - _; - } - function stop() public auth { - stopped = true; - } - function start() public auth { - stopped = false; - } +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; -} +interface IFilter { + function isValid(address _wallet, address _spender, address _to, bytes calldata _data) external view returns (bool valid); +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/LidoFilter.sol b/contracts/infrastructure/dapp/LidoFilter.sol new file mode 100644 index 000000000..0e5d5dd68 --- /dev/null +++ b/contracts/infrastructure/dapp/LidoFilter.sol @@ -0,0 +1,34 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract LidoFilter is BaseFilter { + bytes4 private constant SUBMIT = bytes4(keccak256("submit(address)")); + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external pure override returns (bool valid) { + // Allow sending ETH as well as calls to submit(address) + if (_data.length == 0) { + return true; + } + if(_spender == _to && _data.length >= 4) { + bytes4 methodId = getMethod(_data); + return (methodId == SUBMIT); + } + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/OnlyApproveFilter.sol b/contracts/infrastructure/dapp/OnlyApproveFilter.sol new file mode 100644 index 000000000..7cfb5ed1f --- /dev/null +++ b/contracts/infrastructure/dapp/OnlyApproveFilter.sol @@ -0,0 +1,28 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract OnlyApproveFilter is BaseFilter { + + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external pure override returns (bool valid) { + return (_spender != _to && _data.length >= 4 && getMethod(_data) == ERC20_APPROVE); + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/UniswapV2UniZapFilter.sol b/contracts/infrastructure/dapp/UniswapV2UniZapFilter.sol new file mode 100644 index 000000000..9cb24f05a --- /dev/null +++ b/contracts/infrastructure/dapp/UniswapV2UniZapFilter.sol @@ -0,0 +1,95 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract UniswapV2UniZapFilter is BaseFilter { + + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + bytes4 constant internal ADD_LIQUIDITY_WITH_ETH = bytes4(keccak256("swapExactETHAndAddLiquidity(address,uint256,address,uint256)")); + bytes4 constant internal REMOVE_LIQUIDITY_TO_ETH = bytes4(keccak256("removeLiquidityAndSwapToETH(address,uint256,uint256,address,uint256)")); + bytes4 constant internal ADD_LIQUIDITY_WITH_TOKEN = bytes4( + keccak256( + "swapExactTokensAndAddLiquidity(address,address,uint256,uint256,address,uint256)" + ) + ); + bytes4 constant internal REMOVE_LIQUIDITY_TO_TOKEN = bytes4( + keccak256( + "removeLiquidityAndSwapToToken(address,address,uint256,uint256,address,uint256)" + ) + ); + + // Token registry + address public immutable tokenRegistry; + // Uniswap V2 factory + address public immutable uniFactory; + // Uniswap v2 pair init code + bytes32 public immutable uniInitCode; + // WETH address + address public immutable weth; + + constructor (address _tokenRegistry, address _uniFactory, bytes32 _uniInitCode, address _weth) { + tokenRegistry = _tokenRegistry; + uniFactory = _uniFactory; + uniInitCode = _uniInitCode; + weth = _weth; + } + + function isValid(address _wallet, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // not needed but detects failure early + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + // UniZap method: check that pair is valid and recipient is the wallet + if (_spender == _to) { + if (method == ADD_LIQUIDITY_WITH_TOKEN || method == REMOVE_LIQUIDITY_TO_TOKEN) { + (address tokenA, address tokenB, , , address recipient) = abi.decode(_data[4:], (address, address, uint256, uint256, address)); + return isValidPair(tokenA, tokenB) && recipient == _wallet; + } + if (method == ADD_LIQUIDITY_WITH_ETH) { + (address token, , address recipient) = abi.decode(_data[4:], (address, uint256, address)); + return isValidPair(token, weth) && recipient == _wallet; + } + if (method == REMOVE_LIQUIDITY_TO_ETH) { + (address token, , , address recipient) = abi.decode(_data[4:], (address, uint256, uint256, address)); + return isValidPair(token, weth) && recipient == _wallet; + } + // ERC20 methods + } else { + // only allow approve + return (method == ERC20_APPROVE); + } + } + + function isValidPair(address _tokenA, address _tokenB) internal view returns (bool) { + address pair = pairFor(_tokenA, _tokenB); + (bool success, bytes memory res) = tokenRegistry.staticcall(abi.encodeWithSignature("isTokenTradable(address)", pair)); + return success && abi.decode(res, (bool)); + } + + function pairFor(address _tokenA, address _tokenB) internal view returns (address) { + (address token0, address token1) = _tokenA < _tokenB ? (_tokenA, _tokenB) : (_tokenB, _tokenA); + return(address(uint160(uint256(keccak256(abi.encodePacked( + hex"ff", + uniFactory, + keccak256(abi.encodePacked(token0, token1)), + uniInitCode + )))))); + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/YearnFilter.sol b/contracts/infrastructure/dapp/YearnFilter.sol new file mode 100644 index 000000000..5e27404cd --- /dev/null +++ b/contracts/infrastructure/dapp/YearnFilter.sol @@ -0,0 +1,60 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./BaseFilter.sol"; + +contract YearnFilter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("deposit(uint256)")); + bytes4 private constant DEPOSIT_ETH = bytes4(keccak256("depositETH()")); + bytes4 private constant WITHDRAW = bytes4(keccak256("withdraw(uint256)")); + bytes4 private constant WITHDRAW_ALL = bytes4(keccak256("withdrawAll()")); + bytes4 private constant WITHDRAW_ETH = bytes4(keccak256("withdrawETH(uint256)")); + bytes4 private constant WITHDRAW_ALL_ETH = bytes4(keccak256("withdrawAllETH()")); + + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + bool public immutable isWeth; + + constructor (bool _isWeth) { + isWeth = _isWeth; + } + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer, except for WETH vault + if (_data.length < 4) { + return (_data.length == 0) && isWeth; + } + bytes4 method = getMethod(_data); + + if(_spender == _to) { + return + method == DEPOSIT || + method == WITHDRAW || + method == WITHDRAW_ALL || + isWeth && ( + method == DEPOSIT_ETH || + method == WITHDRAW_ETH || + method == WITHDRAW_ALL_ETH + ); + } + + // Note that yVault can only call transferFrom on the underlying token => no need to validate the token address here + return method == ERC20_APPROVE; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/dsr/DaiJoinFilter.sol b/contracts/infrastructure/dapp/dsr/DaiJoinFilter.sol new file mode 100644 index 000000000..1cf9e13c6 --- /dev/null +++ b/contracts/infrastructure/dapp/dsr/DaiJoinFilter.sol @@ -0,0 +1,41 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; + +contract DaiJoinFilter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("join(address,uint256)")); + bytes4 private constant WITHDRAW = bytes4(keccak256("exit(address,uint256)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + function isValid(address _wallet, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + + // only allow deposits and withdrawals with wallet as beneficiary + if(method == DEPOSIT || method == WITHDRAW) { + return _wallet == abi.decode(_data[4:], (address)); + } + + return method == ERC20_APPROVE; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/dsr/PotFilter.sol b/contracts/infrastructure/dapp/dsr/PotFilter.sol new file mode 100644 index 000000000..aedb87f60 --- /dev/null +++ b/contracts/infrastructure/dapp/dsr/PotFilter.sol @@ -0,0 +1,36 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; + +contract PotFilter is BaseFilter { + + bytes4 private constant DEPOSIT = bytes4(keccak256("join(uint256)")); + bytes4 private constant WITHDRAW = bytes4(keccak256("exit(uint256)")); + bytes4 private constant DRIP = bytes4(keccak256("drip()")); + + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external pure override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + + return method == DEPOSIT || method == WITHDRAW || method == DRIP; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/dsr/VatFilter.sol b/contracts/infrastructure/dapp/dsr/VatFilter.sol new file mode 100644 index 000000000..63031b259 --- /dev/null +++ b/contracts/infrastructure/dapp/dsr/VatFilter.sol @@ -0,0 +1,45 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; + +contract VatFilter is BaseFilter { + + bytes4 private constant HOPE = bytes4(keccak256("hope(address)")); + + address public immutable daiJoin; + address public immutable pot; + + constructor(address _daiJoin, address _pot) { + daiJoin = _daiJoin; + pot = _pot; + } + + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + bytes4 method = getMethod(_data); + + if(method == HOPE) { + address hoped = abi.decode(_data[4:], (address)); + return hoped == daiJoin || hoped == pot; + } + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/paraswap/ParaswapFilter.sol b/contracts/infrastructure/dapp/paraswap/ParaswapFilter.sol new file mode 100644 index 000000000..4c4b9c50e --- /dev/null +++ b/contracts/infrastructure/dapp/paraswap/ParaswapFilter.sol @@ -0,0 +1,370 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; +import "./ParaswapUtils.sol"; +import "../../IAuthoriser.sol"; +import "../../../modules/common/Utils.sol"; + +interface IUniswapV1Factory { + function getExchange(address token) external view returns (address); +} + +interface IParaswapUniswapProxy { + function UNISWAP_FACTORY() external view returns (address); + function UNISWAP_INIT_CODE() external view returns (bytes32); + function WETH() external view returns (address); +} + +interface IParaswap { + struct Route { + address payable exchange; + address targetExchange; + uint256 percent; + bytes payload; + uint256 networkFee; + } + + struct Path { + address to; + uint256 totalNetworkFee; + Route[] routes; + } + + struct SellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + Path[] path; + } + + struct MegaSwapPath { + uint256 fromAmountPercent; + Path[] path; + } + + struct MegaSwapSellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + MegaSwapPath[] path; + } + + struct UniswapV2Data { + address[] path; + } + + function getUniswapProxy() external view returns (address); +} + +contract ParaswapFilter is BaseFilter { + + bytes4 constant internal MULTISWAP = bytes4(keccak256( + "multiSwap((address,uint256,uint256,uint256,address,string,bool,(address,uint256,(address,address,uint256,bytes,uint256)[])[]))" + )); + bytes4 constant internal SIMPLESWAP = bytes4(keccak256( + "simpleSwap(address,address,uint256,uint256,uint256,address[],bytes,uint256[],uint256[],address,string,bool)" + )); + bytes4 constant internal SWAP_ON_UNI = bytes4(keccak256( + "swapOnUniswap(uint256,uint256,address[],uint8)" + )); + bytes4 constant internal SWAP_ON_UNI_FORK = bytes4(keccak256( + "swapOnUniswapFork(address,bytes32,uint256,uint256,address[],uint8)" + )); + bytes4 constant internal MEGASWAP = bytes4(keccak256( + "megaSwap((address,uint256,uint256,uint256,address,string,bool,(uint256,(address,uint256,(address,address,uint256,bytes,uint256)[])[])[]))" + )); + + // The token registry + address public immutable tokenRegistry; + // Paraswap entrypoint + address public immutable augustus; + // Supported Paraswap targetExchanges + mapping(address => bool) public targetExchanges; + // Supported ParaswapPool market makers + mapping(address => bool) public marketMakers; + // The supported adapters + address public immutable uniV1Adapter; + address public immutable uniV2Adapter; + address public immutable sushiswapAdapter; + address public immutable linkswapAdapter; + address public immutable defiswapAdapter; + address public immutable zeroExV2Adapter; + address public immutable zeroExV4Adapter; + address public immutable curveAdapter; + address public immutable wethAdapter; + // The Dapp registry (used to authorise simpleSwap()) + IAuthoriser public immutable authoriser; + // Uniswap Proxy used by Paraswap's AugustusSwapper contract + address public immutable uniswapProxy; + // Whether the uniswap proxy has been changed -> needs manual update + bool public isValidUniswapProxy = true; + // WETH address + address public immutable weth; + + // Supported Uniswap Fork (factory, initcode) couples. + // Note that a `mapping(address => bytes32) public supportedInitCodes;` would be cleaner + // but would cost one storage read to authorise each uni fork swap. + address public immutable uniFactory; // uniswap + address public immutable uniForkFactory1; // sushiswap + address public immutable uniForkFactory2; // linkswap + address public immutable uniForkFactory3; // defiswap + bytes32 public immutable uniInitCode; // uniswap + bytes32 public immutable uniForkInitCode1; // sushiswap + bytes32 public immutable uniForkInitCode2; // linkswap + bytes32 public immutable uniForkInitCode3; // defiswap + + constructor( + address _tokenRegistry, + IAuthoriser _authoriser, + address _augustus, + address _uniswapProxy, + address[3] memory _uniFactories, + bytes32[3] memory _uniInitCodes, + address[9] memory _adapters, + address[] memory _targetExchanges, + address[] memory _marketMakers + ) { + tokenRegistry = _tokenRegistry; + authoriser = _authoriser; + augustus = _augustus; + uniswapProxy = _uniswapProxy; + weth = IParaswapUniswapProxy(_uniswapProxy).WETH(); + uniFactory = IParaswapUniswapProxy(_uniswapProxy).UNISWAP_FACTORY(); + uniInitCode = IParaswapUniswapProxy(_uniswapProxy).UNISWAP_INIT_CODE(); + uniForkFactory1 = _uniFactories[0]; + uniForkFactory2 = _uniFactories[1]; + uniForkFactory3 = _uniFactories[2]; + uniForkInitCode1 = _uniInitCodes[0]; + uniForkInitCode2 = _uniInitCodes[1]; + uniForkInitCode3 = _uniInitCodes[2]; + uniV1Adapter = _adapters[0]; + uniV2Adapter = _adapters[1]; + sushiswapAdapter = _adapters[2]; + linkswapAdapter = _adapters[3]; + defiswapAdapter = _adapters[4]; + zeroExV2Adapter = _adapters[5]; + zeroExV4Adapter = _adapters[6]; + curveAdapter = _adapters[7]; + wethAdapter = _adapters[8]; + for(uint i = 0; i < _targetExchanges.length; i++) { + targetExchanges[_targetExchanges[i]] = true; + } + for(uint i = 0; i < _marketMakers.length; i++) { + marketMakers[_marketMakers[i]] = true; + } + } + + function updateIsValidUniswapProxy() external { + isValidUniswapProxy = (uniswapProxy == IParaswap(augustus).getUniswapProxy()); + } + + function isValid(address _wallet, address /*_spender*/, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer & unsupported Paraswap entrypoints + if (_data.length < 4 || _to != augustus) { + return false; + } + bytes4 methodId = getMethod(_data); + if(methodId == MULTISWAP) { + return isValidMultiSwap(_wallet, _data); + } + if(methodId == SIMPLESWAP) { + return isValidSimpleSwap(_wallet, _to, _data); + } + if(methodId == SWAP_ON_UNI) { + return isValidUniSwap(_data); + } + if(methodId == SWAP_ON_UNI_FORK) { + return isValidUniForkSwap(_data); + } + if(methodId == MEGASWAP) { + return isValidMegaSwap(_wallet, _data); + } + return false; + } + + function isValidMultiSwap(address _wallet, bytes calldata _data) internal view returns (bool) { + (IParaswap.SellData memory sell) = abi.decode(_data[4:], (IParaswap.SellData)); + return hasValidBeneficiary(_wallet, sell.beneficiary) && hasValidPath(sell.fromToken, sell.path); + } + + function isValidSimpleSwap(address _wallet, address _augustus, bytes calldata _data) internal view returns (bool) { + (,address toToken,, address[] memory callees,, uint256[] memory startIndexes,, address beneficiary) + = abi.decode(_data[4:], (address, address, uint256[3],address[],bytes,uint256[],uint256[],address)); + return hasValidBeneficiary(_wallet, beneficiary) && + hasTradableToken(toToken) && + hasAuthorisedCallees(_augustus, callees, startIndexes, _data); + } + + function isValidUniSwap(bytes calldata _data) internal view returns (bool) { + if(!isValidUniswapProxy) { + return false; + } + (, address[] memory path) = abi.decode(_data[4:], (uint256[2], address[])); + return ParaswapUtils.hasValidUniV2Path(path, tokenRegistry, uniFactory, uniInitCode, weth); + } + + function isValidUniForkSwap(bytes calldata _data) internal view returns (bool) { + if(!isValidUniswapProxy) { + return false; + } + (address factory, bytes32 initCode,, address[] memory path) = abi.decode(_data[4:], (address, bytes32, uint256[2], address[])); + return factory != address(0) && initCode != bytes32(0) && ( + ( + factory == uniForkFactory1 && + initCode == uniForkInitCode1 && + ParaswapUtils.hasValidUniV2Path(path, tokenRegistry, uniForkFactory1, uniForkInitCode1, weth) + ) || ( + factory == uniForkFactory2 && + initCode == uniForkInitCode2 && + ParaswapUtils.hasValidUniV2Path(path, tokenRegistry, uniForkFactory2, uniForkInitCode2, weth) + ) || ( + factory == uniForkFactory3 && + initCode == uniForkInitCode3 && + ParaswapUtils.hasValidUniV2Path(path, tokenRegistry, uniForkFactory3, uniForkInitCode3, weth) + ) + ); + } + + function isValidMegaSwap(address _wallet, bytes calldata _data) internal view returns (bool) { + (IParaswap.MegaSwapSellData memory sell) = abi.decode(_data[4:], (IParaswap.MegaSwapSellData)); + return hasValidBeneficiary(_wallet, sell.beneficiary) && hasValidMegaPath(sell.fromToken, sell.path); + } + + function hasAuthorisedCallees( + address _augustus, + address[] memory _callees, + uint256[] memory _startIndexes, + bytes calldata _data + ) + internal + view + returns (bool) + { + // _data = {sig:4}{six params:192}{exchangeDataOffset:32}{...} + // we add 4+32=36 to the offset to skip the method sig and the size of the exchangeData array + uint256 exchangeDataOffset = 36 + abi.decode(_data[196:228], (uint256)); + address[] memory spenders = new address[](_callees.length); + bytes[] memory allData = new bytes[](_callees.length); + for(uint256 i = 0; i < _callees.length; i++) { + bytes calldata slicedExchangeData = _data[exchangeDataOffset+_startIndexes[i] : exchangeDataOffset+_startIndexes[i+1]]; + allData[i] = slicedExchangeData; + spenders[i] = Utils.recoverSpender(_callees[i], slicedExchangeData); + } + return authoriser.areAuthorised(_augustus, spenders, _callees, allData); + } + + function hasValidBeneficiary(address _wallet, address _beneficiary) internal pure returns (bool) { + return (_beneficiary == address(0) || _beneficiary == _wallet); + } + + function hasTradableToken(address _destToken) internal view returns (bool) { + if(_destToken == ParaswapUtils.ETH_TOKEN) { + return true; + } + (bool success, bytes memory res) = tokenRegistry.staticcall(abi.encodeWithSignature("isTokenTradable(address)", _destToken)); + return success && abi.decode(res, (bool)); + } + + function hasValidPath(address _fromToken, IParaswap.Path[] memory _path) internal view returns (bool) { + for (uint i = 0; i < _path.length; i++) { + for (uint j = 0; j < _path[i].routes.length; j++) { + if(!hasValidRoute(_path[i].routes[j], (i == 0) ? _fromToken : _path[i-1].to, _path[i].to)) { + return false; + } + } + } + return true; + } + + function hasValidRoute(IParaswap.Route memory _route, address _fromToken, address _toToken) internal view returns (bool) { + if(_route.targetExchange != address(0) && !targetExchanges[_route.targetExchange]) { + return false; + } + if(_route.exchange == wethAdapter) { + return true; + } + if(_route.exchange == uniV2Adapter) { + return hasValidUniV2Route(_route.payload, uniFactory, uniInitCode); + } + if(_route.exchange == sushiswapAdapter) { + return hasValidUniV2Route(_route.payload, uniForkFactory1, uniForkInitCode1); + } + if(_route.exchange == zeroExV4Adapter) { + return hasValidZeroExV4Route(_route.payload); + } + if(_route.exchange == zeroExV2Adapter) { + return hasValidZeroExV2Route(_route.payload); + } + if(_route.exchange == curveAdapter) { + return true; + } + if(_route.exchange == linkswapAdapter) { + return hasValidUniV2Route(_route.payload, uniForkFactory2, uniForkInitCode2); + } + if(_route.exchange == defiswapAdapter) { + return hasValidUniV2Route(_route.payload, uniForkFactory3, uniForkInitCode3); + } + if(_route.exchange == uniV1Adapter) { + return hasValidUniV1Route(_route.targetExchange, _fromToken, _toToken); + } + return false; + } + + function hasValidUniV2Route(bytes memory _payload, address _factory, bytes32 _initCode) internal view returns (bool) { + IParaswap.UniswapV2Data memory data = abi.decode(_payload, (IParaswap.UniswapV2Data)); + return ParaswapUtils.hasValidUniV2Path(data.path, tokenRegistry, _factory, _initCode, weth); + } + + function hasValidUniV1Route(address _uniV1Factory, address _fromToken, address _toToken) internal view returns (bool) { + address pool = IUniswapV1Factory(_uniV1Factory).getExchange(_fromToken == ParaswapUtils.ETH_TOKEN ? _toToken : _fromToken); + return hasTradableToken(pool); + } + + function hasValidZeroExV4Route(bytes memory _payload) internal view returns (bool) { + ParaswapUtils.ZeroExV4Data memory data = abi.decode(_payload, (ParaswapUtils.ZeroExV4Data)); + return marketMakers[data.order.maker]; + } + + function hasValidZeroExV2Route(bytes memory _payload) internal view returns (bool) { + ParaswapUtils.ZeroExV2Data memory data = abi.decode(_payload, (ParaswapUtils.ZeroExV2Data)); + for(uint i = 0; i < data.orders.length; i++) { + if(!marketMakers[data.orders[i].makerAddress]) { + return false; + } + } + return true; + } + + function hasValidMegaPath(address _fromToken, IParaswap.MegaSwapPath[] memory _megaPath) internal view returns (bool) { + for(uint i = 0; i < _megaPath.length; i++) { + if(!hasValidPath(_fromToken, _megaPath[i].path)) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/paraswap/ParaswapUniV2RouterFilter.sol b/contracts/infrastructure/dapp/paraswap/ParaswapUniV2RouterFilter.sol new file mode 100644 index 000000000..7e087480c --- /dev/null +++ b/contracts/infrastructure/dapp/paraswap/ParaswapUniV2RouterFilter.sol @@ -0,0 +1,68 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; +import "./ParaswapUtils.sol"; + +/** + * @title ParaswapUniV2RouterFilter + * @notice Filter used for calls to Paraswap's "UniswapV3Router", which is Paraswap's custom UniswapV2 router. + UniswapV3Router is deployed at: + - 0x86d3579b043585A97532514016dCF0C2d6C4b6a1 for UniswapV2 + - 0xBc1315CD2671BC498fDAb42aE1214068003DC51e for SushiSwap + - 0xEC4c8110E5B5Bf0ad8aa89e3371d9C3b8CdCD778 for LinkSwap + - 0xF806F9972F9A34FC05394cA6CF2cc606297Ca6D5 for DefiSwap + * @author Olivier VDB - + */ +contract ParaswapUniV2RouterFilter is BaseFilter { + + bytes4 private constant SWAP = bytes4(keccak256("swap(uint256,uint256,address[])")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + // The token registry + address public immutable tokenRegistry; + // The UniV2 factory + address public immutable factory; + // The UniV2 initCode + bytes32 public immutable initCode; + // The WETH address + address public immutable weth; + + constructor(address _tokenRegistry, address _factory, bytes32 _initCode, address _weth) { + tokenRegistry = _tokenRegistry; + factory = _factory; + initCode = _initCode; + weth = _weth; + } + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + + bytes4 methodId = getMethod(_data); + + if(methodId == SWAP) { + (,, address[] memory path) = abi.decode(_data[4:], (uint256, uint256, address[])); + return ParaswapUtils.hasValidUniV2Path(path, tokenRegistry, factory, initCode, weth); + } + + return methodId == ERC20_APPROVE && _spender != _to; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/paraswap/ParaswapUtils.sol b/contracts/infrastructure/dapp/paraswap/ParaswapUtils.sol new file mode 100644 index 000000000..abdc7390e --- /dev/null +++ b/contracts/infrastructure/dapp/paraswap/ParaswapUtils.sol @@ -0,0 +1,104 @@ +// Copyright (C) 2020 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +/** + * @title ParaswapUtils + * @notice Common methods used by Paraswap filters + */ +library ParaswapUtils { + address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + struct ZeroExV2Order { + address makerAddress; + address takerAddress; + address feeRecipientAddress; + address senderAddress; + uint256 makerAssetAmount; + uint256 takerAssetAmount; + uint256 makerFee; + uint256 takerFee; + uint256 expirationTimeSeconds; + uint256 salt; + bytes makerAssetData; + bytes takerAssetData; + } + + struct ZeroExV2Data { + ZeroExV2Order[] orders; + bytes[] signatures; + } + + struct ZeroExV4Order { + address makerToken; + address takerToken; + uint128 makerAmount; + uint128 takerAmount; + address maker; + address taker; + address txOrigin; + bytes32 pool; + uint64 expiry; + uint256 salt; + } + + struct ZeroExV4Signature { + uint8 signatureType; + uint8 v; + bytes32 r; + bytes32 s; + } + + struct ZeroExV4Data { + ZeroExV4Order order; + ZeroExV4Signature signature; + } + + function hasValidUniV2Path( + address[] memory _path, + address _tokenRegistry, + address _factory, + bytes32 _initCode, + address _weth + ) + internal + view + returns (bool) + { + address[] memory lpTokens = new address[](_path.length - 1); + for(uint i = 0; i < lpTokens.length; i++) { + lpTokens[i] = pairFor(_path[i], _path[i+1], _factory, _initCode, _weth); + } + return hasTradableTokens(_tokenRegistry, lpTokens); + } + + function pairFor(address _tokenA, address _tokenB, address _factory, bytes32 _initCode, address _weth) internal pure returns (address) { + (address tokenA, address tokenB) = (_tokenA == ETH_TOKEN ? _weth : _tokenA, _tokenB == ETH_TOKEN ? _weth : _tokenB); + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + return(address(uint160(uint(keccak256(abi.encodePacked( + hex"ff", + _factory, + keccak256(abi.encodePacked(token0, token1)), + _initCode + )))))); + } + + function hasTradableTokens(address _tokenRegistry, address[] memory _tokens) internal view returns (bool) { + (bool success, bytes memory res) = _tokenRegistry.staticcall(abi.encodeWithSignature("areTokensTradable(address[])", _tokens)); + return success && abi.decode(res, (bool)); + } +} diff --git a/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV2Filter.sol b/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV2Filter.sol new file mode 100644 index 000000000..17ff12c73 --- /dev/null +++ b/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV2Filter.sol @@ -0,0 +1,63 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; +import "./ParaswapUtils.sol"; + +/** + * @title WhitelistedZeroExV2Filter + * @notice Filter used for calls to the ZeroExV2 exchange at 0x080bf510fcbf18b91105470639e9561022937712. + * Only trades with whitelisted market makers are allowed. Currently deployed to work with Paraswap's market makers only. + * @author Olivier VDB - + */ +contract WhitelistedZeroExV2Filter is BaseFilter { + + bytes4 private constant SELL = bytes4(keccak256( + "marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])" + )); + + // Supported ParaswapPool market makers + mapping(address => bool) public marketMakers; + + constructor(address[] memory _marketMakers) { + for(uint i = 0; i < _marketMakers.length; i++) { + marketMakers[_marketMakers[i]] = true; + } + } + + function isValid(address /*_wallet*/, address /*_spender*/, address /*_to*/, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + + bytes4 methodId = getMethod(_data); + + if(methodId == SELL) { + ParaswapUtils.ZeroExV2Order[] memory orders = abi.decode(_data[4:], (ParaswapUtils.ZeroExV2Order[])); + for(uint i = 0; i < orders.length; i++) { + if(!marketMakers[orders[i].makerAddress]) { + return false; + } + } + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV4Filter.sol b/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV4Filter.sol new file mode 100644 index 000000000..f5b309a06 --- /dev/null +++ b/contracts/infrastructure/dapp/paraswap/WhitelistedZeroExV4Filter.sol @@ -0,0 +1,59 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../BaseFilter.sol"; +import "./ParaswapUtils.sol"; + +/** + * @title WhitelistedZeroExV4Filter + * @notice Filter used for calls to the ZeroExV4 exchange at 0xdef1c0ded9bec7f1a1670819833240f027b25eff. + * Only trades with whitelisted market makers are allowed. Currently deployed to work with Paraswap's market makers only. + * @author Olivier VDB - + */ +contract WhitelistedZeroExV4Filter is BaseFilter { + + bytes4 private constant FILL = bytes4(keccak256( + "fillRfqOrder((address,address,uint128,uint128,address,address,address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32),uint128)" + )); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + + // Supported ParaswapPool market makers + mapping(address => bool) public marketMakers; + + constructor(address[] memory _marketMakers) { + for(uint i = 0; i < _marketMakers.length; i++) { + marketMakers[_marketMakers[i]] = true; + } + } + + function isValid(address /*_wallet*/, address _spender, address _to, bytes calldata _data) external view override returns (bool valid) { + // disable ETH transfer + if (_data.length < 4) { + return false; + } + + bytes4 methodId = getMethod(_data); + + if(methodId == FILL) { + ParaswapUtils.ZeroExV4Order memory order = abi.decode(_data[4:], (ParaswapUtils.ZeroExV4Order)); + return marketMakers[order.maker]; + } + + return methodId == ERC20_APPROVE && _spender != _to; + } +} \ No newline at end of file diff --git a/contracts/infrastructure_0.5/ens/ArgentENSManager.sol b/contracts/infrastructure/ens/ArgentENSManager.sol similarity index 52% rename from contracts/infrastructure_0.5/ens/ArgentENSManager.sol rename to contracts/infrastructure/ens/ArgentENSManager.sol index 25019b279..54808af2d 100644 --- a/contracts/infrastructure_0.5/ens/ArgentENSManager.sol +++ b/contracts/infrastructure/ens/ArgentENSManager.sol @@ -13,37 +13,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; -import "../../../lib/ens/ENS.sol"; -import "../../../lib/utils/strings.sol"; -import "../../infrastructure/ens/IENSManager.sol"; -import "./ENSResolver.sol"; -import "./ENSReverseRegistrar.sol"; -import "../../infrastructure/base/Managed.sol"; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; +import "../../../lib_0.5/ens/ENS.sol"; +import "../../modules/common/Utils.sol"; +import "./IENSManager.sol"; +import "./IENSResolver.sol"; +import "./IReverseRegistrar.sol"; +import "../base/Managed.sol"; /** * @title ArgentENSManager - * @notice Implementation of an ENS manager that orchestrates the complete - * registration of subdomains for a single root (e.g. argent.eth). - * The contract defines a manager role who is the only role that can trigger the registration of - * a new subdomain. + * @notice Implementation of an ENS manager that orchestrates the complete registration of subdomains for a single root (e.g. argent.eth). + * The contract defines a manager role who is the only role that can trigger the registration of a new subdomain. * @author Julien Niset - */ contract ArgentENSManager is IENSManager, Owned, Managed { - using strings for *; - // The managed root name string public rootName; // The managed root node - bytes32 public rootNode; - - ENS public ensRegistry; - ENSResolver public ensResolver; + bytes32 immutable public rootNode; + // The ENS registry + ENS immutable public ensRegistry; + // The ENS resolver to use + IENSResolver public ensResolver; // namehash('addr.reverse') bytes32 constant public ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; + + modifier validateENSLabel(string memory _label) { + require(bytes(_label).length != 0, "AEM: ENS label must be defined"); + _; + } + // *************** Constructor ********************** // /** @@ -53,21 +57,19 @@ contract ArgentENSManager is IENSManager, Owned, Managed { * @param _ensRegistry The address of the ENS registry * @param _ensResolver The address of the ENS resolver */ - constructor(string memory _rootName, bytes32 _rootNode, address _ensRegistry, address _ensResolver) public { + constructor(string memory _rootName, bytes32 _rootNode, address _ensRegistry, address _ensResolver) { rootName = _rootName; rootNode = _rootNode; ensRegistry = ENS(_ensRegistry); - ensResolver = ENSResolver(_ensResolver); + ensResolver = IENSResolver(_ensResolver); } // *************** External Functions ********************* // /** - * @notice This function must be called when the ENS Manager contract is replaced - * and the address of the new Manager should be provided. - * @param _newOwner The address of the new ENS manager that will manage the root node. + * @inheritdoc IENSManager */ - function changeRootnodeOwner(address _newOwner) external onlyOwner { + function changeRootnodeOwner(address _newOwner) external override onlyOwner { ensRegistry.setOwner(rootNode, _newOwner); emit RootnodeOwnerChange(rootNode, _newOwner); } @@ -77,55 +79,78 @@ contract ArgentENSManager is IENSManager, Owned, Managed { * @param _ensResolver The address of the ENS resolver contract. */ function changeENSResolver(address _ensResolver) external onlyOwner { - require(_ensResolver != address(0), "WF: address cannot be null"); - ensResolver = ENSResolver(_ensResolver); + require(_ensResolver != address(0), "AEM: cannot set empty resolver"); + ensResolver = IENSResolver(_ensResolver); emit ENSResolverChanged(_ensResolver); } /** - * @notice Lets the manager assign an ENS subdomain of the root node to a target address. - * Registers both the forward and reverse ENS. - * @param _label The subdomain label. - * @param _owner The owner of the subdomain. - */ - function register(string calldata _label, address _owner) external onlyManager { + * @inheritdoc IENSManager + */ + function register(string calldata _label, address _owner, bytes calldata _managerSignature) external override validateENSLabel(_label) { + bytes32 signedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(abi.encodePacked(_owner, _label)))); + validateManagerSignature(signedHash, _managerSignature); + + _register(_label, _owner); + } + + /** + * @inheritdoc IENSManager + */ + function register(string calldata _label, address _owner) external override onlyManager validateENSLabel(_label) { + _register(_label, _owner); + } + + function _register(string calldata _label, address _owner) internal { bytes32 labelNode = keccak256(abi.encodePacked(_label)); bytes32 node = keccak256(abi.encodePacked(rootNode, labelNode)); address currentOwner = ensRegistry.owner(node); - require(currentOwner == address(0), "AEM: _label is alrealdy owned"); + require(currentOwner == address(0), "AEM: label is already owned"); // Forward ENS ensRegistry.setSubnodeRecord(rootNode, labelNode, _owner, address(ensResolver), 0); ensResolver.setAddr(node, _owner); - // Reverse ENS - strings.slice[] memory parts = new strings.slice[](2); - parts[0] = _label.toSlice(); - parts[1] = rootName.toSlice(); - string memory name = ".".toSlice().join(parts); - ENSReverseRegistrar reverseRegistrar = ENSReverseRegistrar(_getENSReverseRegistrar()); - bytes32 reverseNode = reverseRegistrar.node(_owner); - ensResolver.setName(reverseNode, name); + string memory name = string(abi.encodePacked(_label, ".", rootName)); + + // Optionally set the reverse ENS + bytes32 reverseNode = IReverseRegistrar(_getENSReverseRegistrar()).node(_owner); + + if(ensRegistry.resolver(reverseNode) == address(ensResolver)) { + ensResolver.setName(reverseNode, name); + } emit Registered(_owner, name); } /** - * @notice Gets the official ENS reverse registrar. - * @return Address of the ENS reverse registrar. - */ - function getENSReverseRegistrar() external view returns (address) { + * @notice Throws if the sender is not a manager and the manager's signature for the creation of the new wallet is invalid. + * @param _signedHash The signed hash + * @param _managerSignature The manager's signature + */ + function validateManagerSignature(bytes32 _signedHash, bytes memory _managerSignature) internal view { + address user; + if(_managerSignature.length != 65) { + user = msg.sender; + } else { + user = Utils.recoverSigner(_signedHash, _managerSignature, 0); + } + require(managers[user], "AEM: user is not manager"); + } + + /** + * @inheritdoc IENSManager + */ + function getENSReverseRegistrar() external view override returns (address) { return _getENSReverseRegistrar(); } // *************** Public Functions ********************* // /** - * @notice Returns true is a given subnode is available. - * @param _subnode The target subnode. - * @return true if the subnode is available. + * @inheritdoc IENSManager */ - function isAvailable(bytes32 _subnode) public view returns (bool) { + function isAvailable(bytes32 _subnode) public view override returns (bool) { bytes32 node = keccak256(abi.encodePacked(rootNode, _subnode)); address currentOwner = ensRegistry.owner(node); if (currentOwner == address(0)) { diff --git a/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol b/contracts/infrastructure/ens/ArgentENSResolver.sol similarity index 86% rename from contracts/infrastructure_0.5/ens/ArgentENSResolver.sol rename to contracts/infrastructure/ens/ArgentENSResolver.sol index daa4144e0..30a0d6e59 100644 --- a/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol +++ b/contracts/infrastructure/ens/ArgentENSResolver.sol @@ -13,10 +13,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; -import "../../infrastructure/base/Owned.sol"; -import "../../infrastructure/base/Managed.sol"; -import "./ENSResolver.sol"; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; +import "../base/Managed.sol"; +import "./IENSResolver.sol"; /** * @title ArgentENSResolver @@ -25,7 +25,7 @@ import "./ENSResolver.sol"; * to the list of resolved names. * @author Julien Niset - */ -contract ArgentENSResolver is Owned, Managed, ENSResolver { +contract ArgentENSResolver is Owned, Managed, IENSResolver { bytes4 constant SUPPORT_INTERFACE_ID = 0x01ffc9a7; bytes4 constant ADDR_INTERFACE_ID = 0x3b3b57de; @@ -46,7 +46,7 @@ contract ArgentENSResolver is Owned, Managed, ENSResolver { * @param _node The node to update. * @param _addr The address to set. */ - function setAddr(bytes32 _node, address _addr) public onlyManager { + function setAddr(bytes32 _node, address _addr) public override onlyManager { records[_node].addr = _addr; emit AddrChanged(_node, _addr); } @@ -56,7 +56,7 @@ contract ArgentENSResolver is Owned, Managed, ENSResolver { * @param _node The node to update. * @param _name The name to set. */ - function setName(bytes32 _node, string memory _name) public onlyManager { + function setName(bytes32 _node, string memory _name) public override onlyManager { records[_node].name = _name; emit NameChanged(_node, _name); } @@ -66,7 +66,7 @@ contract ArgentENSResolver is Owned, Managed, ENSResolver { * @param _node The target node. * @return the address of the target node. */ - function addr(bytes32 _node) public view returns (address) { + function addr(bytes32 _node) public view override returns (address) { return records[_node].addr; } @@ -75,7 +75,7 @@ contract ArgentENSResolver is Owned, Managed, ENSResolver { * @param _node The target ENS node. * @return the name of the target ENS node. */ - function name(bytes32 _node) public view returns (string memory) { + function name(bytes32 _node) public view override returns (string memory) { return records[_node].name; } diff --git a/contracts/infrastructure/ens/IENSManager.sol b/contracts/infrastructure/ens/IENSManager.sol index 10b5aee4f..6a3fbaba7 100644 --- a/contracts/infrastructure/ens/IENSManager.sol +++ b/contracts/infrastructure/ens/IENSManager.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.8.3; /** * @notice Interface for an ENS Mananger. @@ -34,7 +34,17 @@ interface IENSManager { /** * @notice Lets the manager assign an ENS subdomain of the root node to a target address. - * Registers both the forward and reverse ENS. + * Registers forward ENS and optionally reverse ENS. + * @param _label The subdomain label. + * @param _owner The owner of the subdomain. + * @param _managerSignature The manager signature of the hash of _owner and _label. + */ + function register(string calldata _label, address _owner, bytes calldata _managerSignature) external; + + /** + * @notice Backward compatible overload for WalletFactory 1.6 + * Lets the manager assign an ENS subdomain of the root node to a target address. + * Registers forward ENS and optionally reverse ENS. * @param _label The subdomain label. * @param _owner The owner of the subdomain. */ @@ -52,10 +62,4 @@ interface IENSManager { * @return Address of the ENS reverse registrar. */ function getENSReverseRegistrar() external view returns (address); - - /** - * @notice Gets the ENS Resolver. - * @return Address of the ENS resolver. - */ - function ensResolver() external view returns (address); } \ No newline at end of file diff --git a/contracts/infrastructure_0.5/ens/ENSResolver.sol b/contracts/infrastructure/ens/IENSResolver.sol similarity index 71% rename from contracts/infrastructure_0.5/ens/ENSResolver.sol rename to contracts/infrastructure/ens/IENSResolver.sol index 5a63819d6..6325e8138 100644 --- a/contracts/infrastructure_0.5/ens/ENSResolver.sol +++ b/contracts/infrastructure/ens/IENSResolver.sol @@ -13,17 +13,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; /** * @notice ENS Resolver interface. */ -contract ENSResolver { +interface IENSResolver { event AddrChanged(bytes32 indexed _node, address _addr); event NameChanged(bytes32 indexed _node, string _name); - function addr(bytes32 _node) public view returns (address); - function setAddr(bytes32 _node, address _addr) public; - function name(bytes32 _node) public view returns (string memory); - function setName(bytes32 _node, string memory _name) public; + function addr(bytes32 _node) external view returns (address); + function setAddr(bytes32 _node, address _addr) external; + function name(bytes32 _node) external view returns (string memory); + function setName(bytes32 _node, string memory _name) external; } \ No newline at end of file diff --git a/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol b/contracts/infrastructure/ens/IReverseRegistrar.sol similarity index 71% rename from contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol rename to contracts/infrastructure/ens/IReverseRegistrar.sol index b34a3eb15..74742bc11 100644 --- a/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol +++ b/contracts/infrastructure/ens/IReverseRegistrar.sol @@ -13,14 +13,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; /** * @notice ENS Reverse Registrar interface. */ -contract ENSReverseRegistrar { - function claim(address _owner) public returns (bytes32); - function claimWithResolver(address _owner, address _resolver) public returns (bytes32); - function setName(string memory _name) public returns (bytes32); - function node(address _addr) public pure returns (bytes32); +interface IReverseRegistrar { + function claim(address _owner) external returns (bytes32); + function claimWithResolver(address _owner, address _resolver) external returns (bytes32); + function setName(string memory _name) external returns (bytes32); + function node(address _addr) external pure returns (bytes32); } \ No newline at end of file diff --git a/contracts/infrastructure/helper/IDappRegistry.sol b/contracts/infrastructure/helper/IDappRegistry.sol new file mode 100644 index 000000000..76da842d8 --- /dev/null +++ b/contracts/infrastructure/helper/IDappRegistry.sol @@ -0,0 +1,23 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; +pragma experimental ABIEncoderV2; + +interface IDappRegistry { + function enabledRegistryIds(address _wallet) external view returns (bytes32); + function authorisations(uint8 _registryId, address _dapp) external view returns (bytes32); +} \ No newline at end of file diff --git a/contracts/infrastructure/helper/MultiCallHelper.sol b/contracts/infrastructure/helper/MultiCallHelper.sol new file mode 100644 index 000000000..d2ea4dbf0 --- /dev/null +++ b/contracts/infrastructure/helper/MultiCallHelper.sol @@ -0,0 +1,134 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../dapp/IFilter.sol"; +import "./IDappRegistry.sol"; +import "../storage/ITransferStorage.sol"; +import "../../modules/common/Utils.sol"; + +/** + * @title MultiCallHelper + * @notice Helper contract that can be used to check in 1 call if and why a sequence of transactions is authorised to be executed by a wallet. + * @author Julien Niset - + */ +contract MultiCallHelper { + + uint256 private constant MAX_UINT = type(uint256).max; + + struct Call { + address to; + uint256 value; + bytes data; + } + + // The trusted contacts storage + ITransferStorage internal immutable userWhitelist; + // The dapp registry contract + IDappRegistry internal immutable dappRegistry; + + constructor(ITransferStorage _userWhitelist, IDappRegistry _dappRegistry) { + userWhitelist = _userWhitelist; + dappRegistry = _dappRegistry; + } + + /** + * @notice Checks if a sequence of transactions is authorised to be executed by a wallet. + * The method returns false if any of the inner transaction is not to a trusted contact or an authorised dapp. + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function isMultiCallAuthorised(address _wallet, Call[] calldata _transactions) external view returns (bool) { + for(uint i = 0; i < _transactions.length; i++) { + address spender = Utils.recoverSpender(_transactions[i].to, _transactions[i].data); + if ( + (spender != _transactions[i].to && _transactions[i].value != 0) || + (!isWhitelisted(_wallet, spender) && isAuthorised(_wallet, spender, _transactions[i].to, _transactions[i].data) == MAX_UINT) + ) { + return false; + } + } + return true; + } + + /** + * @notice Checks if each of the transaction of a sequence of transactions is authorised to be executed by a wallet. + * For each transaction of the sequence it returns an Id where: + * - Id is in [0,255]: the transaction is to an address authorised in registry Id of the DappRegistry + * - Id = 256: the transaction is to an address authorised in the trusted contacts of the wallet + * - Id = MAX_UINT: the transaction is not authorised + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function multiCallAuthorisation(address _wallet, Call[] calldata _transactions) external view returns (uint256[] memory registryIds) { + registryIds = new uint256[](_transactions.length); + for(uint i = 0; i < _transactions.length; i++) { + address spender = Utils.recoverSpender(_transactions[i].to, _transactions[i].data); + if (spender != _transactions[i].to && _transactions[i].value != 0) { + registryIds[i] = MAX_UINT; + } else if (isWhitelisted(_wallet, spender)) { + registryIds[i] = 256; + } else { + registryIds[i] = isAuthorised(_wallet, spender, _transactions[i].to, _transactions[i].data); + } + } + } + + function isAuthorised(address _wallet, address _spender, address _to, bytes calldata _data) internal view returns (uint256) { + uint registries = uint(dappRegistry.enabledRegistryIds(_wallet)); + // Check Argent Default Registry first. It is enabled by default, implying that a zero + // at position 0 of the `registries` bit vector means that the Argent Registry is enabled) + for(uint registryId = 0; registryId == 0 || (registries >> registryId) > 0; registryId++) { + bool isEnabled = (((registries >> registryId) & 1) > 0) /* "is bit set for regId?" */ == (registryId > 0) /* "not Argent registry?" */; + if(isEnabled) { // if registryId is enabled + uint auth = uint(dappRegistry.authorisations(uint8(registryId), _spender)); + uint validAfter = auth & 0xffffffffffffffff; + if (0 < validAfter && validAfter <= block.timestamp) { // if the current time is greater than the validity time + address filter = address(uint160(auth >> 64)); + if(filter == address(0) || IFilter(filter).isValid(_wallet, _spender, _to, _data)) { + return registryId; + } + } + } + } + return MAX_UINT; + } + + function isAuthorisedInRegistry(address _wallet, Call[] calldata _transactions, uint8 _registryId) external view returns (bool) { + for(uint i = 0; i < _transactions.length; i++) { + address spender = Utils.recoverSpender(_transactions[i].to, _transactions[i].data); + + uint auth = uint(dappRegistry.authorisations(_registryId, spender)); + uint validAfter = auth & 0xffffffffffffffff; + if (0 < validAfter && validAfter <= block.timestamp) { // if the current time is greater than the validity time + address filter = address(uint160(auth >> 64)); + if(filter != address(0) && !IFilter(filter).isValid(_wallet, spender, _transactions[i].to, _transactions[i].data)) { + return false; + } + } else { + return false; + } + } + + return true; + } + + function isWhitelisted(address _wallet, address _target) internal view returns (bool _isWhitelisted) { + uint whitelistAfter = userWhitelist.getWhitelist(_wallet, _target); + return whitelistAfter > 0 && whitelistAfter < block.timestamp; + } +} \ No newline at end of file diff --git a/contracts/infrastructure/storage/IGuardianStorage.sol b/contracts/infrastructure/storage/IGuardianStorage.sol index 03bfd1478..c6ee4966e 100644 --- a/contracts/infrastructure/storage/IGuardianStorage.sol +++ b/contracts/infrastructure/storage/IGuardianStorage.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; interface IGuardianStorage { diff --git a/contracts/infrastructure/storage/ILimitStorage.sol b/contracts/infrastructure/storage/ILimitStorage.sol deleted file mode 100644 index 8ea3e141d..000000000 --- a/contracts/infrastructure/storage/ILimitStorage.sol +++ /dev/null @@ -1,51 +0,0 @@ -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -/** - * @title ILimitStorage - * @notice LimitStorage interface - */ -interface ILimitStorage { - - struct Limit { - // the current limit - uint128 current; - // the pending limit if any - uint128 pending; - // when the pending limit becomes the current limit - uint64 changeAfter; - } - - struct DailySpent { - // The amount already spent during the current period - uint128 alreadySpent; - // The end of the current period - uint64 periodEnd; - } - - function setLimit(address _wallet, Limit memory _limit) external; - - function getLimit(address _wallet) external view returns (Limit memory _limit); - - function setDailySpent(address _wallet, DailySpent memory _dailySpent) external; - - function getDailySpent(address _wallet) external view returns (DailySpent memory _dailySpent); - - function setLimitAndDailySpent(address _wallet, Limit memory _limit, DailySpent memory _dailySpent) external; - - function getLimitAndDailySpent(address _wallet) external view returns (Limit memory _limit, DailySpent memory _dailySpent); -} \ No newline at end of file diff --git a/contracts/infrastructure/storage/ITransferStorage.sol b/contracts/infrastructure/storage/ITransferStorage.sol index a604f58d6..f332bd5f5 100644 --- a/contracts/infrastructure/storage/ITransferStorage.sol +++ b/contracts/infrastructure/storage/ITransferStorage.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; /** * @title ITransferStorage diff --git a/contracts/infrastructure/storage/LimitStorage.sol b/contracts/infrastructure/storage/LimitStorage.sol deleted file mode 100644 index 299e03c9a..000000000 --- a/contracts/infrastructure/storage/LimitStorage.sol +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./ILimitStorage.sol"; -import "./Storage.sol"; - -/** - * @title LimitStorage - * @notice Contract storing the state of wallets related daily limits. - * The contract only defines basic setters and getters with no logic. - * Only the modules of a wallet can modify its state. - * @author Julien Niset - - */ -contract LimitStorage is ILimitStorage, Storage { - - struct LimitManagerConfig { - // The daily limit - ILimitStorage.Limit limit; - // The current usage - ILimitStorage.DailySpent dailySpent; - } - - // wallet specific storage - mapping (address => LimitManagerConfig) internal limits; - - function setLimit(address _wallet, ILimitStorage.Limit memory _limit) external override onlyModule(_wallet) { - limits[_wallet].limit = _limit; - } - - function getLimit(address _wallet) external override view returns (ILimitStorage.Limit memory _limit) { - return limits[_wallet].limit; - } - - function setDailySpent(address _wallet, ILimitStorage.DailySpent memory _dailySpent) external override onlyModule(_wallet) { - limits[_wallet].dailySpent = _dailySpent; - } - - function getDailySpent(address _wallet) external override view returns (ILimitStorage.DailySpent memory _dailySpent) { - return limits[_wallet].dailySpent; - } - - function setLimitAndDailySpent( - address _wallet, - ILimitStorage.Limit memory _limit, - ILimitStorage.DailySpent memory _dailySpent - ) - external - override - onlyModule(_wallet) - { - limits[_wallet].limit = _limit; - limits[_wallet].dailySpent = _dailySpent; - } - - function getLimitAndDailySpent( - address _wallet - ) - external - override - view - returns (ILimitStorage.Limit memory _limit, ILimitStorage.DailySpent memory _dailySpent) - { - return (limits[_wallet].limit, limits[_wallet].dailySpent); - } -} \ No newline at end of file diff --git a/contracts/infrastructure/storage/LockStorage.sol b/contracts/infrastructure/storage/LockStorage.sol deleted file mode 100644 index 6bc0a601f..000000000 --- a/contracts/infrastructure/storage/LockStorage.sol +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.6.12; -import "../../wallet/BaseWallet.sol"; -import "./Storage.sol"; -import "./ILockStorage.sol"; - -/** - * @title LockStorage - * @dev Contract storing the state of wallets related to guardians and lock. - * The contract only defines basic setters and getters with no logic. Only modules authorised - * for a wallet can modify its state. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract LockStorage is ILockStorage, Storage { - - struct LockStorageConfig { - // the lock's release timestamp - uint256 lock; - // the module that set the last lock - address locker; - } - - // wallet specific storage - mapping (address => LockStorageConfig) internal configs; - - // *************** External Functions ********************* // - - /** - * @dev Lets an authorised module set the lock for a wallet. - * @param _wallet The target wallet. - * @param _locker The feature doing the lock. - * @param _releaseAfter The epoch time at which the lock should automatically release. - */ - function setLock(address _wallet, address _locker, uint256 _releaseAfter) external override onlyModule(_wallet) { - configs[_wallet].lock = _releaseAfter; - if (_releaseAfter != 0 && _locker != configs[_wallet].locker) { - configs[_wallet].locker = _locker; - } - } - - /** - * @dev Checks if the lock is set for a wallet. - * @param _wallet The target wallet. - * @return true if the lock is set for the wallet. - */ - function isLocked(address _wallet) external view override returns (bool) { - return configs[_wallet].lock > now; - } - - /** - * @dev Gets the time at which the lock of a wallet will release. - * @param _wallet The target wallet. - * @return the time at which the lock of a wallet will release, or zero if there is no lock set. - */ - function getLock(address _wallet) external view override returns (uint256) { - return configs[_wallet].lock; - } - - /** - * @dev Gets the address of the last module that modified the lock for a wallet. - * @param _wallet The target wallet. - * @return the address of the last module that modified the lock for a wallet. - */ - function getLocker(address _wallet) external view override returns (address) { - return configs[_wallet].locker; - } -} \ No newline at end of file diff --git a/contracts/infrastructure_0.5/CompoundRegistry.sol b/contracts/infrastructure_0.5/CompoundRegistry.sol deleted file mode 100644 index 037f89bee..000000000 --- a/contracts/infrastructure_0.5/CompoundRegistry.sol +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -pragma solidity ^0.5.4; -import "../infrastructure/base/Owned.sol"; - -/** - * @title CompoundRegistry - * @notice Simple registry containing a mapping between underlying assets and their corresponding cToken. - * @author Julien Niset - - */ -contract CompoundRegistry is Owned { - - address[] tokens; - - mapping (address => CTokenInfo) internal cToken; - - struct CTokenInfo { - bool exists; - uint128 index; - address market; - } - - event CTokenAdded(address indexed _underlying, address indexed _cToken); - event CTokenRemoved(address indexed _underlying); - - /** - * @notice Adds a new cToken to the registry. - * @param _underlying The underlying asset. - * @param _cToken The cToken. - */ - function addCToken(address _underlying, address _cToken) external onlyOwner { - require(!cToken[_underlying].exists, "CR: cToken already added"); - cToken[_underlying].exists = true; - cToken[_underlying].index = uint128(tokens.push(_underlying) - 1); - cToken[_underlying].market = _cToken; - emit CTokenAdded(_underlying, _cToken); - } - - /** - * @notice Removes a cToken from the registry. - * @param _underlying The underlying asset. - */ - function removeCToken(address _underlying) external onlyOwner { - require(cToken[_underlying].exists, "CR: cToken does not exist"); - address last = tokens[tokens.length - 1]; - if (_underlying != last) { - uint128 targetIndex = cToken[_underlying].index; - tokens[targetIndex] = last; - cToken[last].index = targetIndex; - } - tokens.length --; - delete cToken[_underlying]; - emit CTokenRemoved(_underlying); - } - - /** - * @notice Gets the cToken for a given underlying asset. - * @param _underlying The underlying asset. - */ - function getCToken(address _underlying) external view returns (address) { - return cToken[_underlying].market; - } - - /** - * @notice Gets the list of supported underlyings. - */ - function listUnderlyings() external view returns (address[] memory) { - address[] memory underlyings = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { - underlyings[i] = tokens[i]; - } - return underlyings; - } -} \ No newline at end of file diff --git a/contracts/infrastructure_0.5/MakerRegistry.sol b/contracts/infrastructure_0.5/MakerRegistry.sol deleted file mode 100644 index e3c2dac4d..000000000 --- a/contracts/infrastructure_0.5/MakerRegistry.sol +++ /dev/null @@ -1,93 +0,0 @@ -pragma solidity ^0.5.4; -import "../infrastructure/base/Owned.sol"; -import "../../lib/maker/MakerInterfaces.sol"; - -/** - * @title MakerRegistry - * @notice Simple registry containing a mapping between token collaterals and their corresponding Maker Join adapters. - * @author Olivier VDB - - */ -contract MakerRegistry is Owned { - - VatLike public vat; - address[] public tokens; - mapping (address => Collateral) public collaterals; - mapping (bytes32 => address) public collateralTokensByIlks; - - struct Collateral { - bool exists; - uint128 index; - JoinLike join; - bytes32 ilk; - } - - event CollateralAdded(address indexed _token); - event CollateralRemoved(address indexed _token); - - constructor(VatLike _vat) public { - vat = _vat; - } - - /** - * @notice Adds a new token as possible CDP collateral. - * @param _joinAdapter The Join Adapter for the token. - */ - function addCollateral(JoinLike _joinAdapter) external onlyOwner { - require(vat.wards(address(_joinAdapter)) == 1, "MR: _joinAdapter not authorised in vat"); - address token = address(_joinAdapter.gem()); - require(!collaterals[token].exists, "MR: collateral already added"); - collaterals[token].exists = true; - collaterals[token].index = uint128(tokens.push(token) - 1); - collaterals[token].join = _joinAdapter; - bytes32 ilk = _joinAdapter.ilk(); - collaterals[token].ilk = ilk; - collateralTokensByIlks[ilk] = token; - emit CollateralAdded(token); - } - - /** - * @notice Removes a token as possible CDP collateral. - * @param _token The token to remove as collateral. - */ - function removeCollateral(address _token) external onlyOwner { - require(collaterals[_token].exists, "MR: collateral does not exist"); - delete collateralTokensByIlks[collaterals[_token].ilk]; - - address last = tokens[tokens.length - 1]; - if (_token != last) { - uint128 targetIndex = collaterals[_token].index; - tokens[targetIndex] = last; - collaterals[last].index = targetIndex; - } - tokens.length --; - delete collaterals[_token]; - emit CollateralRemoved(_token); - } - - /** - * @notice Gets the list of supported collaterals. - */ - function getCollateralTokens() external view returns (address[] memory _tokens) { - _tokens = new address[](tokens.length); - for (uint256 i = 0; i < tokens.length; i++) { - _tokens[i] = tokens[i]; - } - return _tokens; - } - - /** - * @notice Gets the ilk for a given token collateral. - * @param _token The token collateral. - */ - function getIlk(address _token) external view returns (bytes32 _ilk) { - _ilk = collaterals[_token].ilk; - } - - /** - * @notice Gets the join adapter and collateral token for a given ilk. - */ - function getCollateral(bytes32 _ilk) external view returns (JoinLike _join, GemLike _token) { - _token = GemLike(collateralTokensByIlks[_ilk]); - _join = collaterals[address(_token)].join; - } -} \ No newline at end of file diff --git a/contracts/infrastructure_0.5/ModuleRegistry.sol b/contracts/infrastructure_0.5/ModuleRegistry.sol index 168c91250..3af0e3279 100644 --- a/contracts/infrastructure_0.5/ModuleRegistry.sol +++ b/contracts/infrastructure_0.5/ModuleRegistry.sol @@ -15,7 +15,7 @@ pragma solidity ^0.5.4; import "../infrastructure/base/Owned.sol"; -import "../../lib/other/ERC20.sol"; +import "../../lib_0.5/other/ERC20.sol"; /** * @title ModuleRegistry diff --git a/contracts/infrastructure_0.5/MultiSigWallet.sol b/contracts/infrastructure_0.5/MultiSigWallet.sol index 636c74b32..c55e0d1b5 100644 --- a/contracts/infrastructure_0.5/MultiSigWallet.sol +++ b/contracts/infrastructure_0.5/MultiSigWallet.sol @@ -80,7 +80,7 @@ contract MultiSigWallet { bytes32 s; uint256 count = _signatures.length / 65; require(count >= threshold, "MSW: Not enough signatures"); - bytes32 txHash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), _to, _value, _data, nonce)); + bytes32 txHash = keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), address(this), _to, _value, _data, nonce)); nonce += 1; uint256 valid = 0; address lastSigner = address(0); diff --git a/contracts/infrastructure_0.5/storage/GuardianStorage.sol b/contracts/infrastructure_0.5/storage/GuardianStorage.sol index 9f932889e..69b569a10 100644 --- a/contracts/infrastructure_0.5/storage/GuardianStorage.sol +++ b/contracts/infrastructure_0.5/storage/GuardianStorage.sol @@ -15,7 +15,7 @@ pragma solidity ^0.5.4; -import "../../infrastructure/storage/Storage.sol"; +import "./Storage.sol"; import "../../infrastructure/storage/IGuardianStorage.sol"; /** diff --git a/contracts/infrastructure/storage/Storage.sol b/contracts/infrastructure_0.5/storage/Storage.sol similarity index 93% rename from contracts/infrastructure/storage/Storage.sol rename to contracts/infrastructure_0.5/storage/Storage.sol index 9b04c9c26..2b5dcc366 100644 --- a/contracts/infrastructure/storage/Storage.sol +++ b/contracts/infrastructure_0.5/storage/Storage.sol @@ -13,8 +13,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.5.4; import "../../wallet/IWallet.sol"; @@ -29,6 +28,7 @@ contract Storage { * @notice Throws if the caller is not an authorised module. */ modifier onlyModule(address _wallet) { + // solhint-disable-next-line reason-string require(IWallet(_wallet).authorised(msg.sender), "TS: must be an authorized module to call this method"); _; } diff --git a/contracts/infrastructure_0.5/storage/TransferStorage.sol b/contracts/infrastructure_0.5/storage/TransferStorage.sol index 2624117bb..adbcd1273 100644 --- a/contracts/infrastructure_0.5/storage/TransferStorage.sol +++ b/contracts/infrastructure_0.5/storage/TransferStorage.sol @@ -15,7 +15,7 @@ pragma solidity ^0.5.4; -import "../../infrastructure/storage/Storage.sol"; +import "./Storage.sol"; import "../../infrastructure/storage/ITransferStorage.sol"; /** diff --git a/contracts/modules/ApprovedTransfer.sol b/contracts/modules/ApprovedTransfer.sol deleted file mode 100644 index 1cddec440..000000000 --- a/contracts/modules/ApprovedTransfer.sol +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./common/Utils.sol"; -import "./common/LimitUtils.sol"; -import "./common/BaseTransfer.sol"; -import "../infrastructure/storage/ILimitStorage.sol"; -import "../infrastructure/storage/IGuardianStorage.sol"; - -/** - * @title ApprovedTransfer - * @notice Feature to transfer tokens (ETH or ERC20) or call third-party contracts with the approval of guardians. - * @author Julien Niset - - */ -contract ApprovedTransfer is BaseTransfer { - - bytes32 constant NAME = "ApprovedTransfer"; - - // The guardian storage - IGuardianStorage public guardianStorage; - // The limit storage - ILimitStorage public limitStorage; - - constructor( - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - ILimitStorage _limitStorage, - IVersionManager _versionManager, - address _wethToken - ) - BaseFeature(_lockStorage, _versionManager, NAME) - BaseTransfer(_wethToken) - public - { - guardianStorage = _guardianStorage; - limitStorage = _limitStorage; - } - - /** - * @notice Transfers tokens (ETH or ERC20) from a wallet. - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _to The destination address - * @param _amount The amount of token to transfer - * @param _data The data for the transaction (only for ETH transfers) - */ - function transferToken( - address _wallet, - address _token, - address _to, - uint256 _amount, - bytes calldata _data - ) - external - onlyWalletFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - doTransfer(_wallet, _token, _to, _amount, _data); - LimitUtils.resetDailySpent(versionManager, limitStorage, _wallet); - } - - /** - * @notice Call a contract. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - * @param _value The amount of ETH to transfer as part of call - * @param _data The encoded method data - */ - function callContract( - address _wallet, - address _contract, - uint256 _value, - bytes calldata _data - ) - external - onlyWalletFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - doCallContract(_wallet, _contract, _value, _data); - LimitUtils.resetDailySpent(versionManager, limitStorage, _wallet); - } - - /** - * @notice Lets the owner do an ERC20 approve followed by a call to a contract. - * The address to approve may be different than the contract to call. - * We assume that the contract does not require ETH. - * @param _wallet The target wallet. - * @param _token The token to approve. - * @param _spender The address to approve. - * @param _amount The amount of ERC20 tokens to approve. - * @param _contract The contract to call. - * @param _data The encoded method data - */ - function approveTokenAndCallContract( - address _wallet, - address _token, - address _spender, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyWalletFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - doApproveTokenAndCallContract(_wallet, _token, _spender, _amount, _contract, _data); - LimitUtils.resetDailySpent(versionManager, limitStorage, _wallet); - } - - /** - * @notice Changes the daily limit. The change is immediate. - * @param _wallet The target wallet. - * @param _newLimit The new limit. - */ - function changeLimit(address _wallet, uint256 _newLimit) external onlyWalletFeature(_wallet) onlyWhenUnlocked(_wallet) { - uint128 targetLimit = LimitUtils.safe128(_newLimit); - ILimitStorage.Limit memory newLimit = ILimitStorage.Limit(targetLimit, targetLimit, LimitUtils.safe64(block.timestamp)); - ILimitStorage.DailySpent memory resetDailySpent = ILimitStorage.DailySpent(uint128(0), uint64(0)); - setLimitAndDailySpent(_wallet, newLimit, resetDailySpent); - emit LimitChanged(_wallet, _newLimit, newLimit.changeAfter); - } - - /** - * @notice Resets the daily spent amount. - * @param _wallet The target wallet. - */ - function resetDailySpent(address _wallet) external onlyWalletFeature(_wallet) onlyWhenUnlocked(_wallet) { - LimitUtils.resetDailySpent(versionManager, limitStorage, _wallet); - } - - /** - * @notice lets the owner wrap ETH into WETH, approve the WETH and call a contract. - * The address to approve may be different than the contract to call. - * We assume that the contract does not require ETH. - * @param _wallet The target wallet. - * @param _spender The address to approve. - * @param _amount The amount of ERC20 tokens to approve. - * @param _contract The contract to call. - * @param _data The encoded method data - */ - function approveWethAndCallContract( - address _wallet, - address _spender, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyWalletFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - doApproveWethAndCallContract(_wallet, _spender, _amount, _contract, _data); - LimitUtils.resetDailySpent(versionManager, limitStorage, _wallet); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata) external view override returns (uint256, OwnerSignature) { - // owner + [n/2] guardians - uint numberOfGuardians = Utils.ceil(guardianStorage.guardianCount(_wallet), 2); - require(numberOfGuardians > 0, "AT: no guardians set on wallet"); - uint numberOfSignatures = 1 + numberOfGuardians; - return (numberOfSignatures, OwnerSignature.Required); - } - - // *************** Internal Functions ********************* // - - function setLimitAndDailySpent( - address _wallet, - ILimitStorage.Limit memory _limit, - ILimitStorage.DailySpent memory _dailySpent - ) internal { - versionManager.invokeStorage( - _wallet, - address(limitStorage), - abi.encodeWithSelector(limitStorage.setLimitAndDailySpent.selector, _wallet, _limit, _dailySpent) - ); - } -} \ No newline at end of file diff --git a/contracts/modules/ArgentModule.sol b/contracts/modules/ArgentModule.sol new file mode 100644 index 000000000..ef609ef59 --- /dev/null +++ b/contracts/modules/ArgentModule.sol @@ -0,0 +1,128 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "./common/Utils.sol"; +import "./common/BaseModule.sol"; +import "./RelayerManager.sol"; +import "./SecurityManager.sol"; +import "./TransactionManager.sol"; + +/** + * @title ArgentModule + * @notice Single module for the Argent wallet. + * @author Julien Niset - + */ +contract ArgentModule is BaseModule, RelayerManager, SecurityManager, TransactionManager { + + bytes32 constant public NAME = "ArgentModule"; + + constructor ( + IModuleRegistry _registry, + IGuardianStorage _guardianStorage, + ITransferStorage _userWhitelist, + IAuthoriser _authoriser, + address _uniswapRouter, + uint256 _securityPeriod, + uint256 _securityWindow, + uint256 _recoveryPeriod, + uint256 _lockPeriod + ) + BaseModule(_registry, _guardianStorage, _userWhitelist, _authoriser, NAME) + SecurityManager(_recoveryPeriod, _securityPeriod, _securityWindow, _lockPeriod) + TransactionManager(_securityPeriod) + RelayerManager(_uniswapRouter) + { + + } + + /** + * @inheritdoc IModule + */ + function init(address _wallet) external override onlyWallet(_wallet) { + enableDefaultStaticCalls(_wallet); + } + + /** + * @inheritdoc IModule + */ + function addModule(address _wallet, address _module) external override onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + require(registry.isRegisteredModule(_module), "AM: module is not registered"); + IWallet(_wallet).authoriseModule(_module, true); + } + + /** + * @inheritdoc RelayerManager + */ + function getRequiredSignatures(address _wallet, bytes calldata _data) public view override returns (uint256, OwnerSignature) { + bytes4 methodId = Utils.functionPrefix(_data); + + if (methodId == TransactionManager.multiCall.selector || + methodId == TransactionManager.addToWhitelist.selector || + methodId == TransactionManager.removeFromWhitelist.selector || + methodId == TransactionManager.enableERC1155TokenReceiver.selector || + methodId == TransactionManager.clearSession.selector || + methodId == ArgentModule.addModule.selector || + methodId == SecurityManager.addGuardian.selector || + methodId == SecurityManager.revokeGuardian.selector || + methodId == SecurityManager.cancelGuardianAddition.selector || + methodId == SecurityManager.cancelGuardianRevokation.selector) + { + // owner + return (1, OwnerSignature.Required); + } + if (methodId == TransactionManager.multiCallWithSession.selector) { + return (1, OwnerSignature.Session); + } + if (methodId == SecurityManager.executeRecovery.selector) { + // majority of guardians + uint numberOfSignaturesRequired = _majorityOfGuardians(_wallet); + require(numberOfSignaturesRequired > 0, "AM: no guardians set on wallet"); + return (numberOfSignaturesRequired, OwnerSignature.Disallowed); + } + if (methodId == SecurityManager.cancelRecovery.selector) { + // majority of (owner + guardians) + uint numberOfSignaturesRequired = Utils.ceil(recoveryConfigs[_wallet].guardianCount + 1, 2); + return (numberOfSignaturesRequired, OwnerSignature.Optional); + } + if (methodId == TransactionManager.multiCallWithGuardians.selector || + methodId == TransactionManager.multiCallWithGuardiansAndStartSession.selector || + methodId == SecurityManager.transferOwnership.selector) + { + // owner + majority of guardians + uint majorityGuardians = _majorityOfGuardians(_wallet); + uint numberOfSignaturesRequired = majorityGuardians + 1; + return (numberOfSignaturesRequired, OwnerSignature.Required); + } + if (methodId == SecurityManager.finalizeRecovery.selector || + methodId == SecurityManager.confirmGuardianAddition.selector || + methodId == SecurityManager.confirmGuardianRevokation.selector) + { + // anyone + return (0, OwnerSignature.Anyone); + } + if (methodId == SecurityManager.lock.selector || methodId == SecurityManager.unlock.selector) { + // any guardian + return (1, OwnerSignature.Disallowed); + } + revert("SM: unknown method"); + } + + function _majorityOfGuardians(address _wallet) internal view returns (uint) { + return Utils.ceil(guardianStorage.guardianCount(_wallet), 2); + } +} \ No newline at end of file diff --git a/contracts/modules/CompoundManager.sol b/contracts/modules/CompoundManager.sol deleted file mode 100644 index 3eb0d66ab..000000000 --- a/contracts/modules/CompoundManager.sol +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/BaseFeature.sol"; -import "../infrastructure/ICompoundRegistry.sol"; - -interface IComptroller { - function enterMarkets(address[] calldata _cTokens) external returns (uint[] memory); - function exitMarket(address _cToken) external returns (uint); - function getAssetsIn(address _account) external view returns (address[] memory); - function getAccountLiquidity(address _account) external view returns (uint, uint, uint); - function checkMembership(address account, ICToken cToken) external view returns (bool); -} - -interface ICToken { - function comptroller() external view returns (address); - function underlying() external view returns (address); - function symbol() external view returns (string memory); - function exchangeRateCurrent() external returns (uint256); - function exchangeRateStored() external view returns (uint256); - function balanceOf(address _account) external view returns (uint256); - function borrowBalanceCurrent(address _account) external returns (uint256); - function borrowBalanceStored(address _account) external view returns (uint256); -} - -/** - * @title CompoundManager - * @notice Module to invest and borrow tokens with CompoundV2 - * @author Julien Niset - - */ -contract CompoundManager is BaseFeature{ - - bytes32 constant NAME = "CompoundManager"; - - // The Compound IComptroller contract - IComptroller public comptroller; - // The registry mapping underlying with cTokens - ICompoundRegistry public compoundRegistry; - - event InvestmentAdded(address indexed _wallet, address _token, uint256 _invested, uint256 _period); - event InvestmentRemoved(address indexed _wallet, address _token, uint256 _fraction); - event LoanOpened( - address indexed _wallet, - bytes32 indexed _loanId, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount); - event LoanClosed(address indexed _wallet, bytes32 indexed _loanId); - event CollateralAdded(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event CollateralRemoved(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event DebtAdded(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - event DebtRemoved(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - - using SafeMath for uint256; - - constructor( - ILockStorage _lockStorage, - IComptroller _comptroller, - ICompoundRegistry _compoundRegistry, - IVersionManager _versionManager - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - comptroller = _comptroller; - compoundRegistry = _compoundRegistry; - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - /* ********************************** Implementation of Loan ************************************* */ - - /** - * @notice Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of tokens borrowed. - * @return _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - */ - function openLoan( - address _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - address[] memory markets = new address[](2); - markets[0] = compoundRegistry.getCToken(_collateral); - markets[1] = compoundRegistry.getCToken(_debtToken); - invokeWallet(_wallet, address(comptroller), 0, abi.encodeWithSignature("enterMarkets(address[])", markets)); - mint(_wallet, markets[0], _collateral, _collateralAmount); - borrow(_wallet, _debtToken, markets[1], _debtAmount); - emit LoanOpened(_wallet, _loanId, _collateral, _collateralAmount, _debtToken, _debtAmount); - } - - /** - * @notice Closes the collateralized loan in all markets by repaying all debts (plus interest). Note that it does not redeem the collateral. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - */ - function closeLoan( - address _wallet, - bytes32 _loanId - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - address[] memory markets = comptroller.getAssetsIn(_wallet); - for (uint i = 0; i < markets.length; i++) { - address cToken = markets[i]; - uint debt = ICToken(cToken).borrowBalanceCurrent(_wallet); - if (debt > 0) { - repayBorrow(_wallet, cToken, debt); - uint collateral = ICToken(cToken).balanceOf(_wallet); - if (collateral == 0) { - invokeWallet( - _wallet, - address(comptroller), - 0, - abi.encodeWithSignature("exitMarket(address)", address(cToken)) - ); - } - } - } - emit LoanClosed(_wallet, _loanId); - } - - /** - * @notice Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - address _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - address cToken = compoundRegistry.getCToken(_collateral); - enterMarketIfNeeded(_wallet, cToken, address(comptroller)); - mint(_wallet, cToken, _collateral, _collateralAmount); - emit CollateralAdded(_wallet, _loanId, _collateral, _collateralAmount); - } - - /** - * @notice Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - address _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - address cToken = compoundRegistry.getCToken(_collateral); - redeemUnderlying(_wallet, cToken, _collateralAmount); - exitMarketIfNeeded(_wallet, cToken, address(comptroller)); - emit CollateralRemoved(_wallet, _loanId, _collateral, _collateralAmount); - } - - /** - * @notice Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _debtToken The token borrowed. - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - address _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - address dToken = compoundRegistry.getCToken(_debtToken); - enterMarketIfNeeded(_wallet, dToken, address(comptroller)); - borrow(_wallet, _debtToken, dToken, _debtAmount); - emit DebtAdded(_wallet, _loanId, _debtToken, _debtAmount); - } - - /** - * @notice Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId bytes32(0) as Compound does not allow the creation of multiple loans. - * @param _debtToken The token to repay. - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - address _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - address dToken = compoundRegistry.getCToken(_debtToken); - repayBorrow(_wallet, dToken, _debtAmount); - exitMarketIfNeeded(_wallet, dToken, address(comptroller)); - emit DebtRemoved(_wallet, _loanId, _debtToken, _debtAmount); - } - - /** - * @notice Gets information about the loan status on Compound. - * @param _wallet The target wallet. - * @return _status Status [0: no loan, 1: loan is safe, 2: loan is unsafe and can be liquidated] - * @return _ethValue Value (in ETH) representing the value that could still be borrowed when status = 1; or the value of the collateral - * that should be added to avoid liquidation when status = 2. - */ - function getLoan( - address _wallet, - bytes32 /* _loanId */ - ) - external - view - returns (uint8 _status, uint256 _ethValue) - { - (uint error, uint liquidity, uint shortfall) = comptroller.getAccountLiquidity(_wallet); - require(error == 0, "CM: failed to get account liquidity"); - if (liquidity > 0) { - return (1, liquidity); - } - if (shortfall > 0) { - return (2, shortfall); - } - return (0,0); - } - - /* ********************************** Implementation of Invest ************************************* */ - - /** - * @notice Invest tokens for a given period. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _amount The amount of tokens to invest. - * @param _period The period over which the tokens may be locked in the investment (optional). - * @return _invested The exact amount of tokens that have been invested. - */ - function addInvestment( - address _wallet, - address _token, - uint256 _amount, - uint256 _period - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - returns (uint256 _invested) - { - address cToken = compoundRegistry.getCToken(_token); - mint(_wallet, cToken, _token, _amount); - _invested = _amount; - emit InvestmentAdded(_wallet, _token, _amount, _period); - } - - /** - * @notice Exit invested postions. - * @param _wallet The target wallet. - * @param _token The token address. - * @param _fraction The fraction of invested tokens to exit in per 10000. - */ - function removeInvestment( - address _wallet, - address _token, - uint256 _fraction - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - require(_fraction <= 10000, "CM: invalid fraction value"); - address cToken = compoundRegistry.getCToken(_token); - uint shares = ICToken(cToken).balanceOf(_wallet); - redeem(_wallet, cToken, shares.mul(_fraction).div(10000)); - emit InvestmentRemoved(_wallet, _token, _fraction); - } - - /** - * @notice Get the amount of investment in a given token. - * @param _wallet The target wallet. - * @param _token The token address. - * @return _tokenValue The value in tokens of the investment (including interests). - * @return _periodEnd The time at which the investment can be removed. - */ - function getInvestment( - address _wallet, - address _token - ) - external - view - returns (uint256 _tokenValue, uint256 _periodEnd) - { - address cToken = compoundRegistry.getCToken(_token); - uint amount = ICToken(cToken).balanceOf(_wallet); - uint exchangeRateMantissa = ICToken(cToken).exchangeRateStored(); - _tokenValue = amount.mul(exchangeRateMantissa).div(10 ** 18); - _periodEnd = 0; - } - - /* ****************************************** Compound wrappers ******************************************* */ - - /** - * @notice Adds underlying tokens to a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _token The underlying token. - * @param _amount The amount of underlying token to add. - */ - function mint(address _wallet, address _cToken, address _token, uint256 _amount) internal { - require(_cToken != address(0), "CM: No market for target token"); - require(_amount > 0, "CM: amount cannot be 0"); - uint256 initialCTokenAmount = ERC20(_cToken).balanceOf(_wallet); - if (_token == ETH_TOKEN) { - invokeWallet(_wallet, _cToken, _amount, abi.encodeWithSignature("mint()")); - } else { - invokeWallet(_wallet, _token, 0, abi.encodeWithSignature("approve(address,uint256)", _cToken, _amount)); - invokeWallet(_wallet, _cToken, 0, abi.encodeWithSignature("mint(uint256)", _amount)); - } - require(ERC20(_cToken).balanceOf(_wallet) > initialCTokenAmount, "CM: mint failed"); - } - - /** - * @notice Redeems underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of cToken to redeem. - */ - function redeem(address _wallet, address _cToken, uint256 _amount) internal { - // The following commented `require()` is not necessary as `ICToken(cToken).balanceOf(_wallet)` in `removeInvestment()` - // would have reverted if `_cToken == address(0)` - // It is however left as a comment as a reminder to include it if `removeInvestment()` is changed to use amounts instead of fractions. - // require(_cToken != address(0), "CM: No market for target token"); - require(_amount > 0, "CM: amount cannot be 0"); - uint256 initialCTokenAmount = ERC20(_cToken).balanceOf(_wallet); - invokeWallet(_wallet, _cToken, 0, abi.encodeWithSignature("redeem(uint256)", _amount)); - require(ERC20(_cToken).balanceOf(_wallet) < initialCTokenAmount, "CM: redeem failed"); - } - - /** - * @notice Redeems underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying token to redeem. - */ - function redeemUnderlying(address _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "CM: No market for target token"); - require(_amount > 0, "CM: amount cannot be 0"); - uint256 initialCTokenAmount = ERC20(_cToken).balanceOf(_wallet); - invokeWallet(_wallet, _cToken, 0, abi.encodeWithSignature("redeemUnderlying(uint256)", _amount)); - require(ERC20(_cToken).balanceOf(_wallet) < initialCTokenAmount, "CM: redeemUnderlying failed"); - } - - /** - * @notice Borrows underlying tokens from a cToken contract. - * @param _wallet The target wallet. - * @param _token The token contract. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying tokens to borrow. - */ - function borrow(address _wallet, address _token, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "CM: No market for target token"); - require(_amount > 0, "CM: amount cannot be 0"); - uint256 initialTokenAmount = _token == ETH_TOKEN ? _wallet.balance : ERC20(_token).balanceOf(_wallet); - invokeWallet(_wallet, _cToken, 0, abi.encodeWithSignature("borrow(uint256)", _amount)); - uint256 finalTokenAmount = _token == ETH_TOKEN ? _wallet.balance : ERC20(_token).balanceOf(_wallet); - require(finalTokenAmount > initialTokenAmount, "CM: borrow failed"); - } - - /** - * @notice Repays some borrowed underlying tokens to a cToken contract. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _amount The amount of underlying to repay. - */ - function repayBorrow(address _wallet, address _cToken, uint256 _amount) internal { - require(_cToken != address(0), "CM: No market for target token"); - require(_amount > 0, "CM: amount cannot be 0"); - string memory symbol = ICToken(_cToken).symbol(); - uint256 initialTokenAmount; - uint256 finalTokenAmount; - if (keccak256(abi.encodePacked(symbol)) == keccak256(abi.encodePacked("cETH"))) { - initialTokenAmount = _wallet.balance; - invokeWallet(_wallet, _cToken, _amount, abi.encodeWithSignature("repayBorrow()")); - finalTokenAmount = _wallet.balance; - } else { - address token = ICToken(_cToken).underlying(); - initialTokenAmount = ERC20(token).balanceOf(_wallet); - invokeWallet(_wallet, token, 0, abi.encodeWithSignature("approve(address,uint256)", _cToken, _amount)); - invokeWallet(_wallet, _cToken, 0, abi.encodeWithSignature("repayBorrow(uint256)", _amount)); - finalTokenAmount = ERC20(token).balanceOf(_wallet); - } - require(finalTokenAmount < initialTokenAmount, "CM: repayBorrow failed"); - } - - /** - * @notice Enters a cToken market if it was not entered before. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _comptroller The comptroller contract. - */ - function enterMarketIfNeeded(address _wallet, address _cToken, address _comptroller) internal { - bool isEntered = IComptroller(_comptroller).checkMembership(_wallet, ICToken(_cToken)); - if (!isEntered) { - address[] memory market = new address[](1); - market[0] = _cToken; - invokeWallet(_wallet, _comptroller, 0, abi.encodeWithSignature("enterMarkets(address[])", market)); - } - } - - /** - * @notice Exits a cToken market if there is no more collateral and debt. - * @param _wallet The target wallet. - * @param _cToken The cToken contract. - * @param _comptroller The comptroller contract. - */ - function exitMarketIfNeeded(address _wallet, address _cToken, address _comptroller) internal { - uint collateral = ICToken(_cToken).balanceOf(_wallet); - uint debt = ICToken(_cToken).borrowBalanceStored(_wallet); - if (collateral == 0 && debt == 0) { - invokeWallet(_wallet, _comptroller, 0, abi.encodeWithSignature("exitMarket(address)", _cToken)); - } - } -} diff --git a/contracts/modules/GuardianManager.sol b/contracts/modules/GuardianManager.sol deleted file mode 100644 index 3285d79da..000000000 --- a/contracts/modules/GuardianManager.sol +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/Utils.sol"; -import "./common/GuardianUtils.sol"; -import "./common/BaseFeature.sol"; -import "../infrastructure/storage/IGuardianStorage.sol"; - -/** - * @title GuardianManager - * @notice Module to manage the guardians of wallets. - * Guardians are accounts (EOA or contracts) that are authorized to perform specific security operations on wallet - * such as toggle a safety lock, start a recovery procedure, or confirm transactions. - * Addition or revokation of guardians is initiated by the owner of a wallet and must be confirmed after a security period (e.g. 24 hours). - * The list of guardians for a wallet is stored on a separate contract to facilitate its use by other modules. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract GuardianManager is BaseFeature { - - bytes32 constant NAME = "GuardianManager"; - - bytes4 constant internal CONFIRM_ADDITION_PREFIX = bytes4(keccak256("confirmGuardianAddition(address,address)")); - bytes4 constant internal CONFIRM_REVOKATION_PREFIX = bytes4(keccak256("confirmGuardianRevokation(address,address)")); - - struct GuardianManagerConfig { - // The time at which a guardian addition or revokation will be confirmable by the owner - mapping (bytes32 => uint256) pending; - } - - // The wallet specific storage - mapping (address => GuardianManagerConfig) internal configs; - // The security period - uint256 public securityPeriod; - // The security window - uint256 public securityWindow; - // The guardian storage - IGuardianStorage public guardianStorage; - - // *************** Events *************************** // - - event GuardianAdditionRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); - event GuardianRevokationRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); - event GuardianAdditionCancelled(address indexed wallet, address indexed guardian); - event GuardianRevokationCancelled(address indexed wallet, address indexed guardian); - event GuardianAdded(address indexed wallet, address indexed guardian); - event GuardianRevoked(address indexed wallet, address indexed guardian); - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - IVersionManager _versionManager, - uint256 _securityPeriod, - uint256 _securityWindow - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - guardianStorage = _guardianStorage; - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - } - - // *************** External Functions ********************* // - - /** - * @notice Lets the owner add a guardian to its wallet. - * The first guardian is added immediately. All following additions must be confirmed - * by calling the confirmGuardianAddition() method. - * @param _wallet The target wallet. - * @param _guardian The guardian to add. - */ - function addGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - require(!isOwner(_wallet, _guardian), "GM: target guardian cannot be owner"); - require(!isGuardian(_wallet, _guardian), "GM: target is already a guardian"); - // Guardians must either be an EOA or a contract with an owner() - // method that returns an address with a 5000 gas stipend. - // Note that this test is not meant to be strict and can be bypassed by custom malicious contracts. - (bool success,) = _guardian.call{gas: 5000}(abi.encodeWithSignature("owner()")); - require(success, "GM: guardian must be EOA or implement owner()"); - if (guardianStorage.guardianCount(_wallet) == 0) { - doAddGuardian(_wallet, _guardian); - emit GuardianAdded(_wallet, _guardian); - } else { - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); - GuardianManagerConfig storage config = configs[_wallet]; - require( - config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow, - "GM: addition of target as guardian is already pending"); - config.pending[id] = block.timestamp + securityPeriod; - emit GuardianAdditionRequested(_wallet, _guardian, block.timestamp + securityPeriod); - } - } - - /** - * @notice Confirms the pending addition of a guardian to a wallet. - * The method must be called during the confirmation window and can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function confirmGuardianAddition(address _wallet, address _guardian) external onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); - GuardianManagerConfig storage config = configs[_wallet]; - require(config.pending[id] > 0, "GM: no pending addition as guardian for target"); - require(config.pending[id] < block.timestamp, "GM: Too early to confirm guardian addition"); - require(block.timestamp < config.pending[id] + securityWindow, "GM: Too late to confirm guardian addition"); - doAddGuardian(_wallet, _guardian); - emit GuardianAdded(_wallet, _guardian); - delete config.pending[id]; - } - - /** - * @notice Lets the owner cancel a pending guardian addition. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function cancelGuardianAddition(address _wallet, address _guardian) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); - GuardianManagerConfig storage config = configs[_wallet]; - require(config.pending[id] > 0, "GM: no pending addition as guardian for target"); - delete config.pending[id]; - emit GuardianAdditionCancelled(_wallet, _guardian); - } - - /** - * @notice Lets the owner revoke a guardian from its wallet. - * @dev Revokation must be confirmed by calling the confirmGuardianRevokation() method. - * @param _wallet The target wallet. - * @param _guardian The guardian to revoke. - */ - function revokeGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrFeature(_wallet) { - require(isGuardian(_wallet, _guardian), "GM: must be an existing guardian"); - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); - GuardianManagerConfig storage config = configs[_wallet]; - require( - config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow, - "GM: revokation of target as guardian is already pending"); // TODO need to allow if confirmation window passed - config.pending[id] = block.timestamp + securityPeriod; - emit GuardianRevokationRequested(_wallet, _guardian, block.timestamp + securityPeriod); - } - - /** - * @notice Confirms the pending revokation of a guardian to a wallet. - * The method must be called during the confirmation window and can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function confirmGuardianRevokation(address _wallet, address _guardian) external { - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); - GuardianManagerConfig storage config = configs[_wallet]; - require(config.pending[id] > 0, "GM: no pending guardian revokation for target"); - require(config.pending[id] < block.timestamp, "GM: Too early to confirm guardian revokation"); - require(block.timestamp < config.pending[id] + securityWindow, "GM: Too late to confirm guardian revokation"); - doRevokeGuardian(_wallet, _guardian); - emit GuardianRevoked(_wallet, _guardian); - delete config.pending[id]; - } - - /** - * @notice Lets the owner cancel a pending guardian revokation. - * @param _wallet The target wallet. - * @param _guardian The guardian. - */ - function cancelGuardianRevokation(address _wallet, address _guardian) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); - GuardianManagerConfig storage config = configs[_wallet]; - require(config.pending[id] > 0, "GM: no pending guardian revokation for target"); - delete config.pending[id]; - emit GuardianRevokationCancelled(_wallet, _guardian); - } - - /** - * @notice Checks if an address is a guardian for a wallet. - * @param _wallet The target wallet. - * @param _guardian The address to check. - * @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`. - */ - function isGuardian(address _wallet, address _guardian) public view returns (bool _isGuardian) { - _isGuardian = guardianStorage.isGuardian(_wallet, _guardian); - } - - /** - * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian. - * @param _wallet The target wallet. - * @param _guardian the address to test - * @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`. - */ - function isGuardianOrGuardianSigner(address _wallet, address _guardian) external view returns (bool _isGuardian) { - (_isGuardian, ) = GuardianUtils.isGuardianOrGuardianSigner(guardianStorage.getGuardians(_wallet), _guardian); - } - - /** - * @notice Counts the number of active guardians for a wallet. - * @param _wallet The target wallet. - * @return _count The number of active guardians for a wallet. - */ - function guardianCount(address _wallet) external view returns (uint256 _count) { - return guardianStorage.guardianCount(_wallet); - } - - /** - * @notice Get the active guardians for a wallet. - * @param _wallet The target wallet. - * @return _guardians the active guardians for a wallet. - */ - function getGuardians(address _wallet) external view returns (address[] memory _guardians) { - return guardianStorage.getGuardians(_wallet); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - bytes4 methodId = Utils.functionPrefix(_data); - if (methodId == CONFIRM_ADDITION_PREFIX || methodId == CONFIRM_REVOKATION_PREFIX) { - return (0, OwnerSignature.Anyone); - } else { - return (1, OwnerSignature.Required); - } - } - - - // *************** Internal Functions ********************* // - - function doAddGuardian(address _wallet, address _guardian) internal { - versionManager.invokeStorage( - _wallet, - address(guardianStorage), - abi.encodeWithSelector(guardianStorage.addGuardian.selector, _wallet, _guardian) - ); - } - - function doRevokeGuardian(address _wallet, address _guardian) internal { - versionManager.invokeStorage( - _wallet, - address(guardianStorage), - abi.encodeWithSelector(guardianStorage.revokeGuardian.selector, _wallet, _guardian) - ); - } -} \ No newline at end of file diff --git a/contracts/modules/LockManager.sol b/contracts/modules/LockManager.sol deleted file mode 100644 index 2d258cac4..000000000 --- a/contracts/modules/LockManager.sol +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/BaseFeature.sol"; -import "./common/GuardianUtils.sol"; -import "../infrastructure/storage/ILockStorage.sol"; -import "../infrastructure/storage/IGuardianStorage.sol"; - -/** - * @title LockManager - * @notice Feature to manage the state of a wallet's lock. - * Other features can use the state of the lock to determine if their operations - * should be authorised or blocked. Only the guardians of a wallet can lock and unlock it. - * The lock automatically unlocks after a given period. The lock state is stored on a separate - * contract to facilitate its use by other features. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract LockManager is BaseFeature { - - bytes32 constant NAME = "LockManager"; - - // The lock period - uint256 public lockPeriod; - // the guardian storage - IGuardianStorage public guardianStorage; - - // *************** Events *************************** // - - event Locked(address indexed wallet, uint64 releaseAfter); - event Unlocked(address indexed wallet); - - // *************** Modifiers ************************ // - - /** - * @notice Throws if the wallet is not locked. - */ - modifier onlyWhenLocked(address _wallet) { - require(lockStorage.isLocked(_wallet), "LM: wallet must be locked"); - _; - } - - /** - * @notice Throws if the caller is not a guardian for the wallet. - */ - modifier onlyGuardianOrFeature(address _wallet) { - bool isGuardian = guardianStorage.isGuardian(_wallet, msg.sender); - require(isFeatureAuthorisedInVersionManager(_wallet, msg.sender) || isGuardian, "LM: must be guardian or feature"); - _; - } - - // *************** Constructor ************************ // - - constructor( - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - IVersionManager _versionManager, - uint256 _lockPeriod - ) - BaseFeature(_lockStorage, _versionManager, NAME) public { - guardianStorage = _guardianStorage; - lockPeriod = _lockPeriod; - } - - // *************** External functions ************************ // - - /** - * @notice Lets a guardian lock a wallet. - * @param _wallet The target wallet. - */ - function lock(address _wallet) external onlyGuardianOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - setLock(_wallet, block.timestamp + lockPeriod); - emit Locked(_wallet, uint64(block.timestamp + lockPeriod)); - } - - /** - * @notice Lets a guardian unlock a locked wallet. - * @param _wallet The target wallet. - */ - function unlock(address _wallet) external onlyGuardianOrFeature(_wallet) onlyWhenLocked(_wallet) { - address locker = lockStorage.getLocker(_wallet); - require(locker == address(this), "LM: cannot unlock a wallet that was locked by another feature"); - setLock(_wallet, 0); - emit Unlocked(_wallet); - } - - /** - * @notice Returns the release time of a wallet lock or 0 if the wallet is unlocked. - * @param _wallet The target wallet. - * @return _releaseAfter The epoch time at which the lock will release (in seconds). - */ - function getLock(address _wallet) external view returns(uint64 _releaseAfter) { - uint256 lockEnd = lockStorage.getLock(_wallet); - if (lockEnd > block.timestamp) { - _releaseAfter = uint64(lockEnd); - } - } - - /** - * @notice Checks if a wallet is locked. - * @param _wallet The target wallet. - * @return _isLocked `true` if the wallet is locked otherwise `false`. - */ - function isLocked(address _wallet) external view returns (bool _isLocked) { - return lockStorage.isLocked(_wallet); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Disallowed); - } - - // *************** Internal Functions ********************* // - - function setLock(address _wallet, uint256 _releaseAfter) internal { - versionManager.invokeStorage( - _wallet, - address(lockStorage), - abi.encodeWithSelector(lockStorage.setLock.selector, _wallet, address(this), _releaseAfter) - ); - } -} \ No newline at end of file diff --git a/contracts/modules/NftTransfer.sol b/contracts/modules/NftTransfer.sol deleted file mode 100644 index 994bac9ba..000000000 --- a/contracts/modules/NftTransfer.sol +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/BaseFeature.sol"; -import "../infrastructure/ITokenPriceRegistry.sol"; - -/** - * @title NftTransfer - * @notice Module to transfer NFTs (ERC721), - * @author Olivier VDB - - */ -contract NftTransfer is BaseFeature{ - - bytes32 constant NAME = "NftTransfer"; - - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - bytes4 private constant ERC721_RECEIVED = 0x150b7a02; - - // The address of the CryptoKitties contract - address public ckAddress; - // The token price registry - ITokenPriceRegistry public tokenPriceRegistry; - - // *************** Events *************************** // - - event NonFungibleTransfer(address indexed wallet, address indexed nftContract, uint256 indexed tokenId, address to, bytes data); - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - ITokenPriceRegistry _tokenPriceRegistry, - IVersionManager _versionManager, - address _ckAddress - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - ckAddress = _ckAddress; - tokenPriceRegistry = _tokenPriceRegistry; - } - - // *************** External/Public Functions ********************* // - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - /** - * @inheritdoc IFeature - */ - function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) { - _sigs = new bytes4[](1); - _sigs[0] = ERC721_RECEIVED; - } - - /** - * @notice Handle the receipt of an NFT - * @notice An ERC721 smart contract calls this function on the recipient contract - * after a `safeTransfer`. If the recipient is a BaseWallet, the call to onERC721Received - * will be forwarded to the method onERC721Received of the present module. - * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - */ - function onERC721Received( - address /* operator */, - address /* from */, - uint256 /* tokenId */, - bytes calldata /* data*/ - ) - external - returns (bytes4) - { - return ERC721_RECEIVED; - } - - /** - * @notice Lets the owner transfer NFTs from a wallet. - * @param _wallet The target wallet. - * @param _nftContract The ERC721 address. - * @param _to The recipient. - * @param _tokenId The NFT id - * @param _safe Whether to execute a safe transfer or not - * @param _data The data to pass with the transfer. - */ - function transferNFT( - address _wallet, - address _nftContract, - address _to, - uint256 _tokenId, - bool _safe, - bytes calldata _data - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - bytes memory methodData; - if (_nftContract == ckAddress) { - methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _tokenId); - } else { - if (_safe) { - methodData = abi.encodeWithSignature( - "safeTransferFrom(address,address,uint256,bytes)", _wallet, _to, _tokenId, _data); - } else { - require(!coveredByDailyLimit(_nftContract), "NT: Forbidden ERC20 contract"); - methodData = abi.encodeWithSignature( - "transferFrom(address,address,uint256)", _wallet, _to, _tokenId); - } - } - invokeWallet(_wallet, _nftContract, 0, methodData); - emit NonFungibleTransfer(_wallet, _nftContract, _tokenId, _to, _data); - } - - // *************** Internal Functions ********************* // - - /** - * @notice Returns true if the contract is a supported ERC20. - * @param _contract The address of the contract. - */ - function coveredByDailyLimit(address _contract) internal view returns (bool) { - return tokenPriceRegistry.getTokenPrice(_contract) > 0; - } - -} \ No newline at end of file diff --git a/contracts/modules/RecoveryManager.sol b/contracts/modules/RecoveryManager.sol deleted file mode 100644 index 9a78e57ab..000000000 --- a/contracts/modules/RecoveryManager.sol +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/Utils.sol"; -import "./common/BaseFeature.sol"; -import "../infrastructure/storage/IGuardianStorage.sol"; - -/** - * @title RecoveryManager - * @notice Feature to manage the recovery of a wallet owner. - * Recovery is executed by a consensus of the wallet's guardians and takes 24 hours before it can be finalized. - * Once finalised the ownership of the wallet is transfered to a new address. - * @author Julien Niset - - * @author Olivier Van Den Biggelaar - - */ -contract RecoveryManager is BaseFeature { - - bytes32 constant NAME = "RecoveryManager"; - - bytes4 constant internal EXECUTE_RECOVERY_PREFIX = bytes4(keccak256("executeRecovery(address,address)")); - bytes4 constant internal FINALIZE_RECOVERY_PREFIX = bytes4(keccak256("finalizeRecovery(address)")); - bytes4 constant internal CANCEL_RECOVERY_PREFIX = bytes4(keccak256("cancelRecovery(address)")); - bytes4 constant internal TRANSFER_OWNERSHIP_PREFIX = bytes4(keccak256("transferOwnership(address,address)")); - - struct RecoveryConfig { - address recovery; - uint64 executeAfter; - uint32 guardianCount; - } - - // Wallet specific storage - mapping (address => RecoveryConfig) internal recoveryConfigs; - - // Recovery period - uint256 public recoveryPeriod; - // Lock period - uint256 public lockPeriod; - // Guardian Storage - IGuardianStorage public guardianStorage; - - // *************** Events *************************** // - - event RecoveryExecuted(address indexed wallet, address indexed _recovery, uint64 executeAfter); - event RecoveryFinalized(address indexed wallet, address indexed _recovery); - event RecoveryCanceled(address indexed wallet, address indexed _recovery); - event OwnershipTransfered(address indexed wallet, address indexed _newOwner); - - // *************** Modifiers ************************ // - - /** - * @notice Throws if there is no ongoing recovery procedure. - */ - modifier onlyWhenRecovery(address _wallet) { - require(recoveryConfigs[_wallet].executeAfter > 0, "RM: there must be an ongoing recovery"); - _; - } - - /** - * @notice Throws if there is an ongoing recovery procedure. - */ - modifier notWhenRecovery(address _wallet) { - require(recoveryConfigs[_wallet].executeAfter == 0, "RM: there cannot be an ongoing recovery"); - _; - } - - // *************** Constructor ************************ // - - constructor( - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - IVersionManager _versionManager, - uint256 _recoveryPeriod, - uint256 _lockPeriod - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - // For the wallet to be secure we must have recoveryPeriod >= securityPeriod + securityWindow - // where securityPeriod and securityWindow are the security parameters of adding/removing guardians - // and confirming large transfers. - require(_lockPeriod >= _recoveryPeriod, "RM: insecure security periods"); - recoveryPeriod = _recoveryPeriod; - lockPeriod = _lockPeriod; - guardianStorage = _guardianStorage; - } - - // *************** External functions ************************ // - - /** - * @notice Lets the guardians start the execution of the recovery procedure. - * Once triggered the recovery is pending for the security period before it can be finalised. - * Must be confirmed by N guardians, where N = ((Nb Guardian + 1) / 2). - * @param _wallet The target wallet. - * @param _recovery The address to which ownership should be transferred. - */ - function executeRecovery(address _wallet, address _recovery) external onlyWalletFeature(_wallet) notWhenRecovery(_wallet) { - validateNewOwner(_wallet, _recovery); - RecoveryConfig storage config = recoveryConfigs[_wallet]; - config.recovery = _recovery; - config.executeAfter = uint64(block.timestamp + recoveryPeriod); - config.guardianCount = uint32(guardianStorage.guardianCount(_wallet)); - setLock(_wallet, block.timestamp + lockPeriod); - emit RecoveryExecuted(_wallet, _recovery, config.executeAfter); - } - - /** - * @notice Finalizes an ongoing recovery procedure if the security period is over. - * The method is public and callable by anyone to enable orchestration. - * @param _wallet The target wallet. - */ - function finalizeRecovery(address _wallet) external onlyWhenRecovery(_wallet) { - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - require(uint64(block.timestamp) > config.executeAfter, "RM: the recovery period is not over yet"); - address recoveryOwner = config.recovery; - delete recoveryConfigs[_wallet]; - - versionManager.setOwner(_wallet, recoveryOwner); - setLock(_wallet, 0); - - emit RecoveryFinalized(_wallet, recoveryOwner); - } - - /** - * @notice Lets the owner cancel an ongoing recovery procedure. - * Must be confirmed by N guardians, where N = ((Nb Guardian + 1) / 2) - 1. - * @param _wallet The target wallet. - */ - function cancelRecovery(address _wallet) external onlyWalletFeature(_wallet) onlyWhenRecovery(_wallet) { - RecoveryConfig storage config = recoveryConfigs[address(_wallet)]; - address recoveryOwner = config.recovery; - delete recoveryConfigs[_wallet]; - setLock(_wallet, 0); - - emit RecoveryCanceled(_wallet, recoveryOwner); - } - - /** - * @notice Lets the owner transfer the wallet ownership. This is executed immediately. - * @param _wallet The target wallet. - * @param _newOwner The address to which ownership should be transferred. - */ - function transferOwnership(address _wallet, address _newOwner) external onlyWalletFeature(_wallet) onlyWhenUnlocked(_wallet) { - validateNewOwner(_wallet, _newOwner); - versionManager.setOwner(_wallet, _newOwner); - - emit OwnershipTransfered(_wallet, _newOwner); - } - - /** - * @notice Gets the details of the ongoing recovery procedure if any. - * @param _wallet The target wallet. - */ - function getRecovery(address _wallet) external view returns(address _address, uint64 _executeAfter, uint32 _guardianCount) { - RecoveryConfig storage config = recoveryConfigs[_wallet]; - return (config.recovery, config.executeAfter, config.guardianCount); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - bytes4 methodId = Utils.functionPrefix(_data); - if (methodId == EXECUTE_RECOVERY_PREFIX) { - uint walletGuardians = guardianStorage.guardianCount(_wallet); - require(walletGuardians > 0, "RM: no guardians set on wallet"); - uint numberOfSignaturesRequired = Utils.ceil(walletGuardians, 2); - return (numberOfSignaturesRequired, OwnerSignature.Disallowed); - } - if (methodId == FINALIZE_RECOVERY_PREFIX) { - return (0, OwnerSignature.Anyone); - } - if (methodId == CANCEL_RECOVERY_PREFIX) { - uint numberOfSignaturesRequired = Utils.ceil(recoveryConfigs[_wallet].guardianCount + 1, 2); - return (numberOfSignaturesRequired, OwnerSignature.Optional); - } - if (methodId == TRANSFER_OWNERSHIP_PREFIX) { - uint majorityGuardians = Utils.ceil(guardianStorage.guardianCount(_wallet), 2); - uint numberOfSignaturesRequired = SafeMath.add(majorityGuardians, 1); - return (numberOfSignaturesRequired, OwnerSignature.Required); - } - - revert("RM: unknown method"); - } - - // *************** Internal Functions ********************* // - - function validateNewOwner(address _wallet, address _newOwner) internal view { - require(_newOwner != address(0), "RM: new owner address cannot be null"); - require(!guardianStorage.isGuardian(_wallet, _newOwner), "RM: new owner address cannot be a guardian"); - } - - function setLock(address _wallet, uint256 _releaseAfter) internal { - versionManager.invokeStorage( - _wallet, - address(lockStorage), - abi.encodeWithSelector(lockStorage.setLock.selector, _wallet, address(this), _releaseAfter) - ); - } - -} \ No newline at end of file diff --git a/contracts/modules/RelayerManager.sol b/contracts/modules/RelayerManager.sol index f05b36e8e..4fd09832b 100644 --- a/contracts/modules/RelayerManager.sol +++ b/contracts/modules/RelayerManager.sol @@ -14,37 +14,24 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; +pragma solidity ^0.8.3; +import "@openzeppelin/contracts/utils/math/Math.sol"; import "./common/Utils.sol"; -import "./common/BaseFeature.sol"; -import "./common/GuardianUtils.sol"; -import "./common/LimitUtils.sol"; -import "../infrastructure/storage/ILimitStorage.sol"; -import "../infrastructure/ITokenPriceRegistry.sol"; +import "./common/BaseModule.sol"; +import "./common/SimpleOracle.sol"; import "../infrastructure/storage/IGuardianStorage.sol"; /** * @title RelayerManager - * @notice Feature to execute transactions signed by ETH-less accounts and sent by a relayer. + * @notice Abstract Module to execute transactions signed by ETH-less accounts and sent by a relayer. * @author Julien Niset , Olivier VDB */ -contract RelayerManager is BaseFeature { +abstract contract RelayerManager is BaseModule, SimpleOracle { - bytes32 constant NAME = "RelayerManager"; uint256 constant internal BLOCKBOUND = 10000; - using SafeMath for uint256; - - mapping (address => RelayerConfig) public relayer; - - // The storage of the limit - ILimitStorage public limitStorage; - // The Token price storage - ITokenPriceRegistry public tokenPriceRegistry; - // The Guardian storage - IGuardianStorage public guardianStorage; + mapping (address => RelayerConfig) internal relayer; struct RelayerConfig { uint256 nonce; @@ -63,38 +50,36 @@ contract RelayerManager is BaseFeature { event TransactionExecuted(address indexed wallet, bool indexed success, bytes returnData, bytes32 signedHash); event Refund(address indexed wallet, address indexed refundAddress, address refundToken, uint256 refundAmount); - /* ***************** External methods ************************* */ + // *************** Constructor ************************ // + + constructor(address _uniswapRouter) SimpleOracle(_uniswapRouter) { - constructor( - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - ILimitStorage _limitStorage, - ITokenPriceRegistry _tokenPriceRegistry, - IVersionManager _versionManager - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - limitStorage = _limitStorage; - tokenPriceRegistry = _tokenPriceRegistry; - guardianStorage = _guardianStorage; } + /* ***************** External methods ************************* */ + + /** + * @notice Gets the number of valid signatures that must be provided to execute a + * specific relayed transaction. + * @param _wallet The target wallet. + * @param _data The data of the relayed transaction. + * @return The number of required signatures and the wallet owner signature requirement. + */ + function getRequiredSignatures(address _wallet, bytes calldata _data) public view virtual returns (uint256, OwnerSignature); + /** * @notice Executes a relayed transaction. * @param _wallet The target wallet. - * @param _feature The target feature. * @param _data The data for the relayed transaction * @param _nonce The nonce used to prevent replay attacks. * @param _signatures The signatures as a concatenated byte array. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. + * @param _gasPrice The max gas price (in token) to use for the gas refund. + * @param _gasLimit The max gas limit to use for the gas refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. */ function execute( address _wallet, - address _feature, bytes calldata _data, uint256 _nonce, bytes calldata _signatures, @@ -106,17 +91,21 @@ contract RelayerManager is BaseFeature { external returns (bool) { - uint startGas = gasleft(); + // initial gas = 21k + non_zero_bytes * 16 + zero_bytes * 4 + // ~= 21k + calldata.length * [1/3 * 16 + 2/3 * 4] + uint256 startGas = gasleft() + 21000 + msg.data.length * 8; require(startGas >= _gasLimit, "RM: not enough gas provided"); require(verifyData(_wallet, _data), "RM: Target of _data != _wallet"); - require(isFeatureAuthorisedInVersionManager(_wallet, _feature), "RM: feature not authorised"); + + require(!_isLocked(_wallet) || _gasPrice == 0, "RM: Locked wallet refund"); + StackExtension memory stack; - (stack.requiredSignatures, stack.ownerSignatureRequirement) = IFeature(_feature).getRequiredSignatures(_wallet, _data); + (stack.requiredSignatures, stack.ownerSignatureRequirement) = getRequiredSignatures(_wallet, _data); + require(stack.requiredSignatures > 0 || stack.ownerSignatureRequirement == OwnerSignature.Anyone, "RM: Wrong signature requirement"); require(stack.requiredSignatures * 65 == _signatures.length, "RM: Wrong number of signatures"); stack.signHash = getSignHash( address(this), - _feature, 0, _data, _nonce, @@ -130,20 +119,22 @@ contract RelayerManager is BaseFeature { stack.signHash, stack.requiredSignatures, stack.ownerSignatureRequirement), "RM: Duplicate request"); - require(validateSignatures(_wallet, stack.signHash, _signatures, stack.ownerSignatureRequirement), "RM: Invalid signatures"); - - (stack.success, stack.returnData) = _feature.call(_data); - // only refund when approved by owner and positive gas price - if (_gasPrice > 0 && stack.ownerSignatureRequirement == OwnerSignature.Required) { - refund( - _wallet, - startGas, - _gasPrice, - _gasLimit, - _refundToken, - _refundAddress, - stack.requiredSignatures); + + if (stack.ownerSignatureRequirement == OwnerSignature.Session) { + require(validateSession(_wallet, stack.signHash, _signatures), "RM: Invalid session"); + } else { + require(validateSignatures(_wallet, stack.signHash, _signatures, stack.ownerSignatureRequirement), "RM: Invalid signatures"); } + (stack.success, stack.returnData) = address(this).call(_data); + refund( + _wallet, + startGas, + _gasPrice, + _gasLimit, + _refundToken, + _refundAddress, + stack.requiredSignatures, + stack.ownerSignatureRequirement); emit TransactionExecuted(_wallet, stack.success, stack.returnData, stack.signHash); return stack.success; } @@ -165,23 +156,29 @@ contract RelayerManager is BaseFeature { return relayer[_wallet].executedTx[_signHash]; } + /** + * @notice Gets the last stored session for a wallet. + * @param _wallet The target wallet. + */ + function getSession(address _wallet) external view returns (address key, uint64 expires) { + return (sessions[_wallet].key, sessions[_wallet].expires); + } + /* ***************** Internal & Private methods ************************* */ /** * @notice Generates the signed hash of a relayed transaction according to ERC 1077. * @param _from The starting address for the relayed transaction (should be the relayer module) - * @param _to The destination address for the relayed transaction (should be the target module) * @param _value The value for the relayed transaction. * @param _data The data for the relayed transaction which includes the wallet address. * @param _nonce The nonce used to prevent replay attacks. - * @param _gasPrice The gas price to use for the gas refund. - * @param _gasLimit The gas limit to use for the gas refund. + * @param _gasPrice The max gas price (in token) to use for the gas refund. + * @param _gasLimit The max gas limit to use for the gas refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. */ function getSignHash( address _from, - address _to, uint256 _value, bytes memory _data, uint256 _nonce, @@ -191,20 +188,19 @@ contract RelayerManager is BaseFeature { address _refundAddress ) internal - pure + view returns (bytes32) { return keccak256( abi.encodePacked( "\x19Ethereum Signed Message:\n32", keccak256(abi.encodePacked( - byte(0x19), - byte(0), + bytes1(0x19), + bytes1(0), _from, - _to, _value, _data, - getChainId(), + block.chainid, _nonce, _gasPrice, _gasLimit, @@ -215,7 +211,7 @@ contract RelayerManager is BaseFeature { /** * @notice Checks if the relayed transaction is unique. If yes the state is updated. - * For actions requiring 1 signature by the owner we use the incremental nonce. + * For actions requiring 1 signature by the owner or a session key we use the incremental nonce. * For all other actions we check/store the signHash in a mapping. * @param _wallet The target wallet. * @param _nonce The nonce. @@ -234,7 +230,8 @@ contract RelayerManager is BaseFeature { internal returns (bool) { - if (requiredSignatures == 1 && ownerSignatureRequirement == OwnerSignature.Required) { + if (requiredSignatures == 1 && + (ownerSignatureRequirement == OwnerSignature.Required || ownerSignatureRequirement == OwnerSignature.Session)) { // use the incremental nonce if (_nonce <= relayer[_wallet].nonce) { return false; @@ -257,22 +254,13 @@ contract RelayerManager is BaseFeature { /** * @notice Validates the signatures provided with a relayed transaction. - * The method MUST throw if one or more signatures are not valid. * @param _wallet The target wallet. * @param _signHash The signed hash representing the relayed transaction. - * @param _signatures The signatures as a concatenated byte array. - * @param _option An enum indicating whether the owner is required, optional or disallowed. + * @param _signatures The signatures as a concatenated bytes array. + * @param _option An OwnerSignature enum indicating whether the owner is required, optional or disallowed. * @return A boolean indicating whether the signatures are valid. */ - function validateSignatures( - address _wallet, - bytes32 _signHash, - bytes memory _signatures, - OwnerSignature _option - ) - internal - view - returns (bool) + function validateSignatures(address _wallet, bytes32 _signHash, bytes memory _signatures, OwnerSignature _option) internal view returns (bool) { if (_signatures.length == 0) { return true; @@ -290,13 +278,13 @@ contract RelayerManager is BaseFeature { if (i == 0) { if (_option == OwnerSignature.Required) { // First signer must be owner - if (isOwner(_wallet, signer)) { + if (_isOwner(_wallet, signer)) { continue; } return false; } else if (_option == OwnerSignature.Optional) { // First signer can be owner - if (isOwner(_wallet, signer)) { + if (_isOwner(_wallet, signer)) { continue; } } @@ -305,7 +293,7 @@ contract RelayerManager is BaseFeature { return false; // Signers must be different } lastSigner = signer; - (isGuardian, guardians) = GuardianUtils.isGuardianOrGuardianSigner(guardians, signer); + (isGuardian, guardians) = Utils.isGuardianOrGuardianSigner(guardians, signer); if (!isGuardian) { return false; } @@ -313,15 +301,29 @@ contract RelayerManager is BaseFeature { return true; } + /** + * @notice Validates the signature provided when a session key was used. + * @param _wallet The target wallet. + * @param _signHash The signed hash representing the relayed transaction. + * @param _signatures The signatures as a concatenated bytes array. + * @return A boolean indicating whether the signature is valid. + */ + function validateSession(address _wallet, bytes32 _signHash, bytes calldata _signatures) internal view returns (bool) { + Session memory session = sessions[_wallet]; + address signer = Utils.recoverSigner(_signHash, _signatures, 0); + return (signer == session.key && session.expires >= block.timestamp); + } + /** * @notice Refunds the gas used to the Relayer. * @param _wallet The target wallet. * @param _startGas The gas provided at the start of the execution. - * @param _gasPrice The gas price for the refund. - * @param _gasLimit The gas limit for the refund. + * @param _gasPrice The max gas price (in token) for the refund. + * @param _gasLimit The max gas limit for the refund. * @param _refundToken The token to use for the gas refund. * @param _refundAddress The address refunded to prevent front-running. * @param _requiredSignatures The number of signatures required. + * @param _option An OwnerSignature enum indicating the signature requirement. */ function refund( address _wallet, @@ -330,42 +332,50 @@ contract RelayerManager is BaseFeature { uint _gasLimit, address _refundToken, address _refundAddress, - uint256 _requiredSignatures + uint256 _requiredSignatures, + OwnerSignature _option ) internal { - address refundAddress = _refundAddress == address(0) ? msg.sender : _refundAddress; - uint256 refundAmount; - // skip daily limit when approved by guardians (and signed by owner) - if (_requiredSignatures > 1) { - uint256 gasConsumed = _startGas.sub(gasleft()).add(30000); - refundAmount = Utils.min(gasConsumed, _gasLimit).mul(_gasPrice); - } else { - uint256 gasConsumed = _startGas.sub(gasleft()).add(40000); - refundAmount = Utils.min(gasConsumed, _gasLimit).mul(_gasPrice); - uint256 ethAmount = (_refundToken == ETH_TOKEN) ? refundAmount : LimitUtils.getEtherValue(tokenPriceRegistry, refundAmount, _refundToken); - require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, ethAmount), "RM: refund is above daily limit"); - } - // refund in ETH or ERC20 - if (_refundToken == ETH_TOKEN) { - invokeWallet(_wallet, refundAddress, refundAmount, EMPTY_BYTES); - } else { - bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", refundAddress, refundAmount); - bytes memory transferSuccessBytes = invokeWallet(_wallet, _refundToken, 0, methodData); - // Check token refund is successful, when `transfer` returns a success bool result - if (transferSuccessBytes.length > 0) { - require(abi.decode(transferSuccessBytes, (bool)), "RM: Refund transfer failed"); + // Only refund when the owner is one of the signers or a session key was used + if (_gasPrice > 0 && (_option == OwnerSignature.Required || _option == OwnerSignature.Session)) { + address refundAddress = _refundAddress == address(0) ? msg.sender : _refundAddress; + if (_requiredSignatures == 1 && _option == OwnerSignature.Required) { + // refundAddress must be whitelisted/authorised + if (!authoriser.isAuthorised(_wallet, refundAddress, address(0), EMPTY_BYTES)) { + uint whitelistAfter = userWhitelist.getWhitelist(_wallet, refundAddress); + require(whitelistAfter > 0 && whitelistAfter < block.timestamp, "RM: refund not authorised"); + } } + uint256 refundAmount; + if (_refundToken == ETH_TOKEN) { + // 23k as an upper bound to cover the rest of refund logic + uint256 gasConsumed = _startGas - gasleft() + 23000; + refundAmount = Math.min(gasConsumed, _gasLimit) * (Math.min(_gasPrice, tx.gasprice)); + invokeWallet(_wallet, refundAddress, refundAmount, EMPTY_BYTES); + } else { + // 37.5k as an upper bound to cover the rest of refund logic + uint256 gasConsumed = _startGas - gasleft() + 37500; + uint256 tokenGasPrice = inToken(_refundToken, tx.gasprice); + refundAmount = Math.min(gasConsumed, _gasLimit) * (Math.min(_gasPrice, tokenGasPrice)); + bytes memory methodData = abi.encodeWithSelector(ERC20.transfer.selector, refundAddress, refundAmount); + bytes memory transferSuccessBytes = invokeWallet(_wallet, _refundToken, 0, methodData); + // Check token refund is successful, when `transfer` returns a success bool result + if (transferSuccessBytes.length > 0) { + require(abi.decode(transferSuccessBytes, (bool)), "RM: Refund transfer failed"); + } + } + emit Refund(_wallet, refundAddress, _refundToken, refundAmount); } - emit Refund(_wallet, refundAddress, _refundToken, refundAmount); } - /** - * @notice Returns the current chainId - * @return chainId the chainId + /** + * @notice Checks that the wallet address provided as the first parameter of _data matches _wallet + * @return false if the addresses are different. */ - function getChainId() private pure returns (uint256 chainId) { - // solhint-disable-next-line no-inline-assembly - assembly { chainId := chainid() } + function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) { + require(_data.length >= 36, "RM: Invalid dataWallet"); + address dataWallet = abi.decode(_data[4:], (address)); + return dataWallet == _wallet; } } \ No newline at end of file diff --git a/contracts/modules/SecurityManager.sol b/contracts/modules/SecurityManager.sol new file mode 100644 index 000000000..7f1ce68a2 --- /dev/null +++ b/contracts/modules/SecurityManager.sol @@ -0,0 +1,379 @@ +// Copyright (C) 2018 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "./common/Utils.sol"; +import "./common/BaseModule.sol"; +import "../wallet/IWallet.sol"; + +/** + * @title SecurityManager + * @notice Abstract module implementing the key security features of the wallet: guardians, lock and recovery. + * @author Julien Niset - + * @author Olivier Van Den Biggelaar - + */ +abstract contract SecurityManager is BaseModule { + + struct RecoveryConfig { + address recovery; + uint64 executeAfter; + uint32 guardianCount; + } + + struct GuardianManagerConfig { + // The time at which a guardian addition or revokation will be confirmable by the owner + mapping (bytes32 => uint256) pending; + } + + // Wallet specific storage for recovery + mapping (address => RecoveryConfig) internal recoveryConfigs; + // Wallet specific storage for pending guardian addition/revokation + mapping (address => GuardianManagerConfig) internal guardianConfigs; + + + // Recovery period + uint256 internal immutable recoveryPeriod; + // Lock period + uint256 internal immutable lockPeriod; + // The security period to add/remove guardians + uint256 internal immutable securityPeriod; + // The security window + uint256 internal immutable securityWindow; + + // *************** Events *************************** // + + event RecoveryExecuted(address indexed wallet, address indexed _recovery, uint64 executeAfter); + event RecoveryFinalized(address indexed wallet, address indexed _recovery); + event RecoveryCanceled(address indexed wallet, address indexed _recovery); + event OwnershipTransfered(address indexed wallet, address indexed _newOwner); + event Locked(address indexed wallet, uint64 releaseAfter); + event Unlocked(address indexed wallet); + event GuardianAdditionRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); + event GuardianRevokationRequested(address indexed wallet, address indexed guardian, uint256 executeAfter); + event GuardianAdditionCancelled(address indexed wallet, address indexed guardian); + event GuardianRevokationCancelled(address indexed wallet, address indexed guardian); + event GuardianAdded(address indexed wallet, address indexed guardian); + event GuardianRevoked(address indexed wallet, address indexed guardian); + // *************** Modifiers ************************ // + + /** + * @notice Throws if there is no ongoing recovery procedure. + */ + modifier onlyWhenRecovery(address _wallet) { + require(recoveryConfigs[_wallet].executeAfter > 0, "SM: no ongoing recovery"); + _; + } + + /** + * @notice Throws if there is an ongoing recovery procedure. + */ + modifier notWhenRecovery(address _wallet) { + require(recoveryConfigs[_wallet].executeAfter == 0, "SM: ongoing recovery"); + _; + } + + /** + * @notice Throws if the caller is not a guardian for the wallet or the module itself. + */ + modifier onlyGuardianOrSelf(address _wallet) { + require(_isSelf(msg.sender) || isGuardian(_wallet, msg.sender), "SM: must be guardian/self"); + _; + } + + // *************** Constructor ************************ // + + constructor( + uint256 _recoveryPeriod, + uint256 _securityPeriod, + uint256 _securityWindow, + uint256 _lockPeriod + ) { + // For the wallet to be secure we must have recoveryPeriod >= securityPeriod + securityWindow + // where securityPeriod and securityWindow are the security parameters of adding/removing guardians. + require(_lockPeriod >= _recoveryPeriod, "SM: insecure lock period"); + require(_recoveryPeriod >= _securityPeriod + _securityWindow, "SM: insecure security periods"); + recoveryPeriod = _recoveryPeriod; + lockPeriod = _lockPeriod; + securityWindow = _securityWindow; + securityPeriod = _securityPeriod; + } + + // *************** External functions ************************ // + + // *************** Recovery functions ************************ // + + /** + * @notice Lets the guardians start the execution of the recovery procedure. + * Once triggered the recovery is pending for the security period before it can be finalised. + * Must be confirmed by N guardians, where N = ceil(Nb Guardians / 2). + * @param _wallet The target wallet. + * @param _recovery The address to which ownership should be transferred. + */ + function executeRecovery(address _wallet, address _recovery) external onlySelf() notWhenRecovery(_wallet) { + validateNewOwner(_wallet, _recovery); + uint64 executeAfter = uint64(block.timestamp + recoveryPeriod); + recoveryConfigs[_wallet] = RecoveryConfig(_recovery, executeAfter, uint32(guardianStorage.guardianCount(_wallet))); + _setLock(_wallet, block.timestamp + lockPeriod, SecurityManager.executeRecovery.selector); + emit RecoveryExecuted(_wallet, _recovery, executeAfter); + } + + /** + * @notice Finalizes an ongoing recovery procedure if the security period is over. + * The method is public and callable by anyone to enable orchestration. + * @param _wallet The target wallet. + */ + function finalizeRecovery(address _wallet) external onlyWhenRecovery(_wallet) { + RecoveryConfig storage config = recoveryConfigs[_wallet]; + require(uint64(block.timestamp) > config.executeAfter, "SM: ongoing recovery period"); + address recoveryOwner = config.recovery; + delete recoveryConfigs[_wallet]; + + _clearSession(_wallet); + + IWallet(_wallet).setOwner(recoveryOwner); + _setLock(_wallet, 0, bytes4(0)); + + emit RecoveryFinalized(_wallet, recoveryOwner); + } + + /** + * @notice Lets the owner cancel an ongoing recovery procedure. + * Must be confirmed by N guardians, where N = ceil(Nb Guardian at executeRecovery + 1) / 2) - 1. + * @param _wallet The target wallet. + */ + function cancelRecovery(address _wallet) external onlySelf() onlyWhenRecovery(_wallet) { + address recoveryOwner = recoveryConfigs[_wallet].recovery; + delete recoveryConfigs[_wallet]; + _setLock(_wallet, 0, bytes4(0)); + + emit RecoveryCanceled(_wallet, recoveryOwner); + } + + /** + * @notice Lets the owner transfer the wallet ownership. This is executed immediately. + * @param _wallet The target wallet. + * @param _newOwner The address to which ownership should be transferred. + */ + function transferOwnership(address _wallet, address _newOwner) external onlySelf() onlyWhenUnlocked(_wallet) { + validateNewOwner(_wallet, _newOwner); + IWallet(_wallet).setOwner(_newOwner); + + emit OwnershipTransfered(_wallet, _newOwner); + } + + /** + * @notice Gets the details of the ongoing recovery procedure if any. + * @param _wallet The target wallet. + */ + function getRecovery(address _wallet) external view returns(address _address, uint64 _executeAfter, uint32 _guardianCount) { + RecoveryConfig storage config = recoveryConfigs[_wallet]; + return (config.recovery, config.executeAfter, config.guardianCount); + } + + // *************** Lock functions ************************ // + + /** + * @notice Lets a guardian lock a wallet. + * @param _wallet The target wallet. + */ + function lock(address _wallet) external onlyGuardianOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + _setLock(_wallet, block.timestamp + lockPeriod, SecurityManager.lock.selector); + emit Locked(_wallet, uint64(block.timestamp + lockPeriod)); + } + + /** + * @notice Lets a guardian unlock a locked wallet. + * @param _wallet The target wallet. + */ + function unlock(address _wallet) external onlyGuardianOrSelf(_wallet) onlyWhenLocked(_wallet) { + require(locks[_wallet].locker == SecurityManager.lock.selector, "SM: cannot unlock"); + _setLock(_wallet, 0, bytes4(0)); + emit Unlocked(_wallet); + } + + /** + * @notice Returns the release time of a wallet lock or 0 if the wallet is unlocked. + * @param _wallet The target wallet. + * @return _releaseAfter The epoch time at which the lock will release (in seconds). + */ + function getLock(address _wallet) external view returns(uint64 _releaseAfter) { + return _isLocked(_wallet) ? locks[_wallet].release : 0; + } + + /** + * @notice Checks if a wallet is locked. + * @param _wallet The target wallet. + * @return _isLocked `true` if the wallet is locked otherwise `false`. + */ + function isLocked(address _wallet) external view returns (bool) { + return _isLocked(_wallet); + } + + // *************** Guardian functions ************************ // + + /** + * @notice Lets the owner add a guardian to its wallet. + * The first guardian is added immediately. All following additions must be confirmed + * by calling the confirmGuardianAddition() method. + * @param _wallet The target wallet. + * @param _guardian The guardian to add. + */ + function addGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + require(!_isOwner(_wallet, _guardian), "SM: guardian cannot be owner"); + require(!isGuardian(_wallet, _guardian), "SM: duplicate guardian"); + // Guardians must either be an EOA or a contract with an owner() + // method that returns an address with a 25000 gas stipend. + // Note that this test is not meant to be strict and can be bypassed by custom malicious contracts. + (bool success,) = _guardian.call{gas: 25000}(abi.encodeWithSignature("owner()")); + require(success, "SM: must be EOA/Argent wallet"); + + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require( + config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow, + "SM: duplicate pending addition"); + config.pending[id] = block.timestamp + securityPeriod; + emit GuardianAdditionRequested(_wallet, _guardian, block.timestamp + securityPeriod); + } + + /** + * @notice Confirms the pending addition of a guardian to a wallet. + * The method must be called during the confirmation window and can be called by anyone to enable orchestration. + * @param _wallet The target wallet. + * @param _guardian The guardian. + */ + function confirmGuardianAddition(address _wallet, address _guardian) external onlyWhenUnlocked(_wallet) { + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require(config.pending[id] > 0, "SM: unknown pending addition"); + require(config.pending[id] < block.timestamp, "SM: pending addition not over"); + require(block.timestamp < config.pending[id] + securityWindow, "SM: pending addition expired"); + guardianStorage.addGuardian(_wallet, _guardian); + emit GuardianAdded(_wallet, _guardian); + delete config.pending[id]; + } + + /** + * @notice Lets the owner cancel a pending guardian addition. + * @param _wallet The target wallet. + * @param _guardian The guardian. + */ + function cancelGuardianAddition(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "addition")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require(config.pending[id] > 0, "SM: unknown pending addition"); + delete config.pending[id]; + emit GuardianAdditionCancelled(_wallet, _guardian); + } + + /** + * @notice Lets the owner revoke a guardian from its wallet. + * @dev Revokation must be confirmed by calling the confirmGuardianRevokation() method. + * @param _wallet The target wallet. + * @param _guardian The guardian to revoke. + */ + function revokeGuardian(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) { + require(isGuardian(_wallet, _guardian), "SM: must be existing guardian"); + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require( + config.pending[id] == 0 || block.timestamp > config.pending[id] + securityWindow, + "SM: duplicate pending revoke"); // TODO need to allow if confirmation window passed + config.pending[id] = block.timestamp + securityPeriod; + emit GuardianRevokationRequested(_wallet, _guardian, block.timestamp + securityPeriod); + } + + /** + * @notice Confirms the pending revokation of a guardian to a wallet. + * The method must be called during the confirmation window and can be called by anyone to enable orchestration. + * @param _wallet The target wallet. + * @param _guardian The guardian. + */ + function confirmGuardianRevokation(address _wallet, address _guardian) external { + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require(config.pending[id] > 0, "SM: unknown pending revoke"); + require(config.pending[id] < block.timestamp, "SM: pending revoke not over"); + require(block.timestamp < config.pending[id] + securityWindow, "SM: pending revoke expired"); + guardianStorage.revokeGuardian(_wallet, _guardian); + emit GuardianRevoked(_wallet, _guardian); + delete config.pending[id]; + } + + /** + * @notice Lets the owner cancel a pending guardian revokation. + * @param _wallet The target wallet. + * @param _guardian The guardian. + */ + function cancelGuardianRevokation(address _wallet, address _guardian) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + bytes32 id = keccak256(abi.encodePacked(_wallet, _guardian, "revokation")); + GuardianManagerConfig storage config = guardianConfigs[_wallet]; + require(config.pending[id] > 0, "SM: unknown pending revoke"); + delete config.pending[id]; + emit GuardianRevokationCancelled(_wallet, _guardian); + } + + /** + * @notice Checks if an address is a guardian for a wallet. + * @param _wallet The target wallet. + * @param _guardian The address to check. + * @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`. + */ + function isGuardian(address _wallet, address _guardian) public view returns (bool _isGuardian) { + return guardianStorage.isGuardian(_wallet, _guardian); + } + + /** + * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian. + * @param _wallet The target wallet. + * @param _guardian the address to test + * @return _isGuardian `true` if the address is a guardian for the wallet otherwise `false`. + */ + function isGuardianOrGuardianSigner(address _wallet, address _guardian) external view returns (bool _isGuardian) { + (_isGuardian, ) = Utils.isGuardianOrGuardianSigner(guardianStorage.getGuardians(_wallet), _guardian); + } + + /** + * @notice Counts the number of active guardians for a wallet. + * @param _wallet The target wallet. + * @return _count The number of active guardians for a wallet. + */ + function guardianCount(address _wallet) external view returns (uint256 _count) { + return guardianStorage.guardianCount(_wallet); + } + + /** + * @notice Get the active guardians for a wallet. + * @param _wallet The target wallet. + * @return _guardians the active guardians for a wallet. + */ + function getGuardians(address _wallet) external view returns (address[] memory _guardians) { + return guardianStorage.getGuardians(_wallet); + } + + // *************** Internal Functions ********************* // + + function validateNewOwner(address _wallet, address _newOwner) internal view { + require(_newOwner != address(0), "SM: new owner cannot be null"); + require(!isGuardian(_wallet, _newOwner), "SM: new owner cannot be guardian"); + } + + function _setLock(address _wallet, uint256 _releaseAfter, bytes4 _locker) internal { + locks[_wallet] = Lock(SafeCast.toUint64(_releaseAfter), _locker); + } +} \ No newline at end of file diff --git a/contracts/modules/SimpleUpgrader.sol b/contracts/modules/SimpleUpgrader.sol index c88652885..699882ceb 100644 --- a/contracts/modules/SimpleUpgrader.sol +++ b/contracts/modules/SimpleUpgrader.sol @@ -14,11 +14,10 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "./common/IModule.sol"; import "../infrastructure/IModuleRegistry.sol"; -import "../infrastructure/storage/ILockStorage.sol"; import "../wallet/IWallet.sol"; /** @@ -29,7 +28,6 @@ import "../wallet/IWallet.sol"; contract SimpleUpgrader is IModule { IModuleRegistry private registry; - ILockStorage private lockStorage; address[] public toDisable; address[] public toEnable; @@ -37,14 +35,11 @@ contract SimpleUpgrader is IModule { constructor( IModuleRegistry _registry, - ILockStorage _lockStorage, address[] memory _toDisable, address[] memory _toEnable ) - public { registry = _registry; - lockStorage = _lockStorage; toDisable = _toDisable; toEnable = _toEnable; } @@ -57,8 +52,7 @@ contract SimpleUpgrader is IModule { */ function init(address _wallet) external override { require(msg.sender == _wallet, "SU: only wallet can call init"); - require(!lockStorage.isLocked(_wallet), "SU: wallet locked"); - require(registry.isRegisteredModule(toEnable), "SU: Not all modules are registered"); + require(registry.isRegisteredModule(toEnable), "SU: module not registered"); uint256 i = 0; //add new modules @@ -76,5 +70,14 @@ contract SimpleUpgrader is IModule { /** * @inheritdoc IModule */ - function addModule(address _wallet, address _module) external override {} + function addModule(address /*_wallet*/, address /*_module*/) external pure override { + revert("SU: method not implemented"); + } + + /** + * @inheritdoc IModule + */ + function supportsStaticCall(bytes4 /*_methodId*/) external pure override returns (bool _isSupported) { + return false; + } } \ No newline at end of file diff --git a/contracts/modules/TokenExchanger.sol b/contracts/modules/TokenExchanger.sol deleted file mode 100644 index 402c37337..000000000 --- a/contracts/modules/TokenExchanger.sol +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./common/BaseFeature.sol"; -import "../../lib/other/ERC20.sol"; -import "../../lib/paraswap/IAugustusSwapper.sol"; -import "../infrastructure/ITokenPriceRegistry.sol"; -import "../infrastructure/IDexRegistry.sol"; - -/** - * @title TokenExchanger - * @notice Module to trade tokens (ETH or ERC20) using ParaSwap. - * @author Olivier VDB - - */ -contract TokenExchanger is BaseFeature { - - bytes32 constant NAME = "TokenExchanger"; - - using SafeMath for uint256; - - // Mock token address for ETH - address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - // Signatures of Paraswap's trade methods - // solhint-disable-next-line max-line-length - bytes4 constant internal MULTISWAP = 0xcbd1603e; // bytes4(keccak256("multiSwap(address,address,uint256,uint256,uint256,(address,uint256,(address,address,uint256,bytes,uint256)[])[],uint256,address,uint256,string)")) - // solhint-disable-next-line max-line-length - bytes4 constant internal BUY = 0xbb2a349b; // bytes4(keccak256("buy(address,address,uint256,uint256,uint256,(address,address,uint256,uint256,bytes,uint256)[],uint256,address,uint256,string)")) - - // The address of the Paraswap Proxy contract - address public paraswapProxy; - // The address of the Paraswap contract - address public paraswapSwapper; - // The label of the referrer - string public referrer; - // Registry of authorised exchanges - IDexRegistry public dexRegistry; - // The token price registry - ITokenPriceRegistry public tokenPriceRegistry; - - event TokenExchanged(address indexed wallet, address srcToken, uint srcAmount, address destToken, uint destAmount); - - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - ITokenPriceRegistry _tokenPriceRegistry, - IVersionManager _versionManager, - IDexRegistry _dexRegistry, - address _paraswap, - string memory _referrer - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - tokenPriceRegistry = _tokenPriceRegistry; - dexRegistry = _dexRegistry; - paraswapSwapper = _paraswap; - paraswapProxy = IAugustusSwapper(_paraswap).getTokenTransferProxy(); - referrer = _referrer; - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - /** - * @notice Lets the owner of the wallet execute a "sell" trade (fixed source amount, variable destination amount). - * @param _wallet The target wallet - * @param _srcToken The address of the source token. - * @param _destToken The address of the destination token. - * @param _srcAmount The exact amount of source tokens to sell. - * @param _minDestAmount The minimum amount of destination tokens required for the trade. - * @param _expectedDestAmount The expected amount of destination tokens (used only in ParaSwap's Swapped event). - * @param _path Sequence of sets of weighted ParaSwap routes. Each route specifies an exchange to use to convert a given (exact) amount of - * a given source token into a given (minimum) amount of a given destination token. The path is a sequence of sets of weighted routes where - * the destination token of a set of weighted routes matches the source token of the next set of weighted routes in the path. - * @param _mintPrice gasPrice (in wei) at the time the gas tokens were minted by ParaSwap. 0 means gas token will not be used by ParaSwap - */ - function sell( - address _wallet, - address _srcToken, - address _destToken, - uint256 _srcAmount, - uint256 _minDestAmount, - uint256 _expectedDestAmount, - IAugustusSwapper.Path[] calldata _path, - uint256 _mintPrice - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - // Verify that the destination token is tradable - verifyTradable(_destToken); - // Verify that the exchange adapters used have been authorised - verifyExchangeAdapters(_path); - // Approve source amount if required - uint previousAllowance = approveToken(_wallet, _srcToken, _srcAmount); - // Perform trade and emit event - doSell( - _wallet, - _srcToken, - _destToken, - _srcAmount, - _minDestAmount, - _expectedDestAmount, - _path, - _mintPrice); - // Restore the previous allowance if needed. This should only be needed when the previous allowance - // was infinite. In other cases, paraswap.multiSwap() should have used exactly the additional allowance - // granted to it and therefore the previous allowance should have been restored. - restoreAllowance(_wallet, _srcToken, previousAllowance); - } - - /** - * @notice Lets the owner of the wallet execute a "buy" trade (fixed destination amount, variable source amount). - * @param _wallet The target wallet - * @param _srcToken The address of the source token. - * @param _destToken The address of the destination token. - * @param _maxSrcAmount The maximum amount of source tokens to use for the trade. - * @param _destAmount The exact amount of destination tokens to buy. - * @param _expectedSrcAmount The expected amount of source tokens (used only in ParaSwap's Bought event). - * @param _routes Set of weighted ParaSwap routes. Each route specifies an exchange to use to convert a given (maximum) amount of a given - * source token into a given (exact) amount of a given destination token. - * @param _mintPrice gasPrice (in wei) at the time the gas tokens were minted by ParaSwap. 0 means gas token will not be used by ParaSwap - */ - function buy( - address _wallet, - address _srcToken, - address _destToken, - uint256 _maxSrcAmount, - uint256 _destAmount, - uint256 _expectedSrcAmount, - IAugustusSwapper.BuyRoute[] calldata _routes, - uint256 _mintPrice - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - // Verify that the destination token is tradable - verifyTradable(_destToken); - // Verify that the exchange adapters used have been authorised - verifyExchangeAdapters(_routes); - // Approve source amount if required - uint previousAllowance = approveToken(_wallet, _srcToken, _maxSrcAmount); - // Perform trade and emit event - doBuy( - _wallet, - _srcToken, - _destToken, - _maxSrcAmount, - _destAmount, - _expectedSrcAmount, - _routes, - _mintPrice); - // Restore the previous allowance if needed (paraswap.buy() may not have used exactly the additional allowance granted to it) - restoreAllowance(_wallet, _srcToken, previousAllowance); - } - - // Internal & Private Methods - - function verifyTradable(address _token) internal view { - require((_token == ETH_TOKEN_ADDRESS) || tokenPriceRegistry.isTokenTradable(_token), "TE: Token not tradable"); - } - - function verifyExchangeAdapters(IAugustusSwapper.Path[] calldata _path) internal view { - dexRegistry.verifyExchangeAdapters(_path); - } - - function verifyExchangeAdapters(IAugustusSwapper.BuyRoute[] calldata _routes) internal view { - dexRegistry.verifyExchangeAdapters(_routes); - } - - function approveToken(address _wallet, address _token, uint _amount) internal returns (uint256 _existingAllowance) { - // TODO: Use a "safe approve" logic similar to the one implemented below in other modules - if (_token != ETH_TOKEN_ADDRESS) { - _existingAllowance = ERC20(_token).allowance(_wallet, paraswapProxy); - if (_existingAllowance < uint256(-1)) { - if (_existingAllowance > 0) { - // Clear the existing allowance to avoid issues with tokens like USDT that do not allow changing a non-zero allowance - invokeWallet(_wallet, _token, 0, abi.encodeWithSignature("approve(address,uint256)", paraswapProxy, 0)); - } - // Increase the allowance to include the required amount - uint256 newAllowance = SafeMath.add(_existingAllowance, _amount); - invokeWallet( - _wallet, - _token, - 0, - abi.encodeWithSignature("approve(address,uint256)", paraswapProxy, newAllowance) - ); - } - } - } - - function restoreAllowance(address _wallet, address _token, uint _previousAllowance) internal { - if (_token != ETH_TOKEN_ADDRESS) { - uint allowance = ERC20(_token).allowance(_wallet, paraswapProxy); - if (allowance != _previousAllowance) { - invokeWallet( - _wallet, - _token, - 0, - abi.encodeWithSignature("approve(address,uint256)", paraswapProxy, _previousAllowance) - ); - } - } - } - - function doTradeAndEmitEvent( - address _wallet, - address _srcToken, - address _destToken, - uint256 _srcAmount, - uint256 _destAmount, - bytes memory tradeData - ) - internal - { - // Perform the trade - bytes memory swapRes = invokeWallet( - _wallet, - paraswapSwapper, - _srcToken == ETH_TOKEN_ADDRESS ? _srcAmount : 0, tradeData - ); - - // Emit event with best possible estimate of destination amount - uint256 estimatedDestAmount; - if (swapRes.length > 0) { - (estimatedDestAmount) = abi.decode(swapRes, (uint256)); - } else { - estimatedDestAmount = _destAmount; - } - emit TokenExchanged(_wallet, _srcToken, _srcAmount, _destToken, estimatedDestAmount); - } - - function doSell( - address _wallet, - address _srcToken, - address _destToken, - uint256 _srcAmount, - uint256 _minDestAmount, - uint256 _expectedDestAmount, - IAugustusSwapper.Path[] calldata _path, - uint256 _mintPrice - ) - internal - { - // Build the calldata - string memory ref = referrer; - bytes memory tradeData = abi.encodeWithSelector(MULTISWAP, - _srcToken, _destToken, _srcAmount, _minDestAmount, _expectedDestAmount, _path, _mintPrice, address(0), 0, ref); - - // Perform the trade - doTradeAndEmitEvent(_wallet, _srcToken, _destToken, _srcAmount, _minDestAmount, tradeData); - } - - function doBuy( - address _wallet, - address _srcToken, - address _destToken, - uint256 _maxSrcAmount, - uint256 _destAmount, - uint256 _expectedSrcAmount, - IAugustusSwapper.BuyRoute[] calldata _routes, - uint256 _mintPrice - ) - internal - { - // Build the calldata - string memory ref = referrer; - bytes memory tradeData = abi.encodeWithSelector(BUY, - _srcToken, _destToken, _maxSrcAmount, _destAmount, _expectedSrcAmount, _routes, _mintPrice, address(0), 0, ref); - - // Perform the trade - doTradeAndEmitEvent(_wallet, _srcToken, _destToken, _maxSrcAmount, _destAmount, tradeData); - } - -} \ No newline at end of file diff --git a/contracts/modules/TransactionManager.sol b/contracts/modules/TransactionManager.sol new file mode 100644 index 000000000..a4b239fad --- /dev/null +++ b/contracts/modules/TransactionManager.sol @@ -0,0 +1,279 @@ +// Copyright (C) 2018 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "./common/Utils.sol"; +import "./common/BaseModule.sol"; +import "../../lib_0.5/other/ERC20.sol"; + +/** + * @title TransactionManager + * @notice Module to execute transactions in sequence to e.g. transfer tokens (ETH, ERC20, ERC721, ERC1155) or call third-party contracts. + * @author Julien Niset - + */ +abstract contract TransactionManager is BaseModule { + + // Static calls + bytes4 private constant ERC1271_IS_VALID_SIGNATURE = bytes4(keccak256("isValidSignature(bytes32,bytes)")); + bytes4 private constant ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); + bytes4 private constant ERC1155_RECEIVED = bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")); + bytes4 private constant ERC1155_BATCH_RECEIVED = bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")); + bytes4 private constant ERC165_INTERFACE = bytes4(keccak256("supportsInterface(bytes4)")); + + struct Call { + address to; + uint256 value; + bytes data; + } + + // The time delay for adding a trusted contact + uint256 internal immutable whitelistPeriod; + + // *************** Events *************************** // + + event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter); + event RemovedFromWhitelist(address indexed wallet, address indexed target); + event SessionCreated(address indexed wallet, address sessionKey, uint64 expires); + event SessionCleared(address indexed wallet, address sessionKey); + // *************** Constructor ************************ // + + constructor(uint256 _whitelistPeriod) { + whitelistPeriod = _whitelistPeriod; + } + + // *************** External functions ************************ // + + /** + * @notice Makes the target wallet execute a sequence of transactions authorised by the wallet owner. + * The method reverts if any of the inner transactions reverts. + * The method reverts if any of the inner transaction is not to a trusted contact or an authorised dapp. + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function multiCall( + address _wallet, + Call[] calldata _transactions + ) + external + onlySelf() + onlyWhenUnlocked(_wallet) + returns (bytes[] memory) + { + bytes[] memory results = new bytes[](_transactions.length); + for(uint i = 0; i < _transactions.length; i++) { + address spender = Utils.recoverSpender(_transactions[i].to, _transactions[i].data); + require( + (_transactions[i].value == 0 || spender == _transactions[i].to) && + (isWhitelisted(_wallet, spender) || authoriser.isAuthorised(_wallet, spender, _transactions[i].to, _transactions[i].data)), + "TM: call not authorised"); + results[i] = invokeWallet(_wallet, _transactions[i].to, _transactions[i].value, _transactions[i].data); + } + return results; + } + + /** + * @notice Makes the target wallet execute a sequence of transactions authorised by a session key. + * The method reverts if any of the inner transactions reverts. + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function multiCallWithSession( + address _wallet, + Call[] calldata _transactions + ) + external + onlySelf() + onlyWhenUnlocked(_wallet) + returns (bytes[] memory) + { + return multiCallWithApproval(_wallet, _transactions); + } + + /** + * @notice Makes the target wallet execute a sequence of transactions approved by a majority of guardians. + * The method reverts if any of the inner transactions reverts. + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function multiCallWithGuardians( + address _wallet, + Call[] calldata _transactions + ) + external + onlySelf() + onlyWhenUnlocked(_wallet) + returns (bytes[] memory) + { + return multiCallWithApproval(_wallet, _transactions); + } + + /** + * @notice Makes the target wallet execute a sequence of transactions approved by a majority of guardians. + * The method reverts if any of the inner transactions reverts. + * Upon success a new session is started. + * @param _wallet The target wallet. + * @param _transactions The sequence of transactions. + */ + function multiCallWithGuardiansAndStartSession( + address _wallet, + Call[] calldata _transactions, + address _sessionUser, + uint64 _duration + ) + external + onlySelf() + onlyWhenUnlocked(_wallet) + returns (bytes[] memory) + { + startSession(_wallet, _sessionUser, _duration); + return multiCallWithApproval(_wallet, _transactions); + } + + /** + * @notice Clears the active session of a wallet if any. + * @param _wallet The target wallet. + */ + function clearSession(address _wallet) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + emit SessionCleared(_wallet, sessions[_wallet].key); + _clearSession(_wallet); + } + + /** + * @notice Adds an address to the list of trusted contacts. + * @param _wallet The target wallet. + * @param _target The address to add. + */ + function addToWhitelist(address _wallet, address _target) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + require(_target != _wallet, "TM: Cannot whitelist wallet"); + require(!registry.isRegisteredModule(_target), "TM: Cannot whitelist module"); + require(!isWhitelisted(_wallet, _target), "TM: target already whitelisted"); + + uint256 whitelistAfter = block.timestamp + whitelistPeriod; + setWhitelist(_wallet, _target, whitelistAfter); + emit AddedToWhitelist(_wallet, _target, uint64(whitelistAfter)); + } + + /** + * @notice Removes an address from the list of trusted contacts. + * @param _wallet The target wallet. + * @param _target The address to remove. + */ + function removeFromWhitelist(address _wallet, address _target) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + setWhitelist(_wallet, _target, 0); + emit RemovedFromWhitelist(_wallet, _target); + } + + /** + * @notice Checks if an address is a trusted contact for a wallet. + * @param _wallet The target wallet. + * @param _target The address. + * @return _isWhitelisted true if the address is a trusted contact. + */ + function isWhitelisted(address _wallet, address _target) public view returns (bool _isWhitelisted) { + uint whitelistAfter = userWhitelist.getWhitelist(_wallet, _target); + return whitelistAfter > 0 && whitelistAfter < block.timestamp; + } + + /* + * @notice Enable the static calls required to make the wallet compatible with the ERC1155TokenReceiver + * interface (see https://eips.ethereum.org/EIPS/eip-1155#erc-1155-token-receiver). This method only + * needs to be called for wallets deployed in version lower or equal to 2.4.0 as the ERC1155 static calls + * are not available by default for these versions of BaseWallet + * @param _wallet The target wallet. + */ + function enableERC1155TokenReceiver(address _wallet) external onlyWalletOwnerOrSelf(_wallet) onlyWhenUnlocked(_wallet) { + IWallet(_wallet).enableStaticCall(address(this), ERC165_INTERFACE); + IWallet(_wallet).enableStaticCall(address(this), ERC1155_RECEIVED); + IWallet(_wallet).enableStaticCall(address(this), ERC1155_BATCH_RECEIVED); + } + + /** + * @inheritdoc IModule + */ + function supportsStaticCall(bytes4 _methodId) external pure override returns (bool _isSupported) { + return _methodId == ERC1271_IS_VALID_SIGNATURE || + _methodId == ERC721_RECEIVED || + _methodId == ERC165_INTERFACE || + _methodId == ERC1155_RECEIVED || + _methodId == ERC1155_BATCH_RECEIVED; + } + + /** ******************* Callbacks ************************** */ + + /** + * @notice Returns true if this contract implements the interface defined by + * `interfaceId` (see https://eips.ethereum.org/EIPS/eip-165). + */ + function supportsInterface(bytes4 _interfaceID) external pure returns (bool) { + return _interfaceID == ERC165_INTERFACE || _interfaceID == (ERC1155_RECEIVED ^ ERC1155_BATCH_RECEIVED); + } + + /** + * @notice Implementation of EIP 1271. + * Should return whether the signature provided is valid for the provided data. + * @param _msgHash Hash of a message signed on the behalf of address(this) + * @param _signature Signature byte array associated with _msgHash + */ + function isValidSignature(bytes32 _msgHash, bytes memory _signature) external view returns (bytes4) { + require(_signature.length == 65, "TM: invalid signature length"); + address signer = Utils.recoverSigner(_msgHash, _signature, 0); + require(_isOwner(msg.sender, signer), "TM: Invalid signer"); + return ERC1271_IS_VALID_SIGNATURE; + } + + + fallback() external { + bytes4 methodId = Utils.functionPrefix(msg.data); + if(methodId == ERC721_RECEIVED || methodId == ERC1155_RECEIVED || methodId == ERC1155_BATCH_RECEIVED) { + // solhint-disable-next-line no-inline-assembly + assembly { + calldatacopy(0, 0, 0x04) + return (0, 0x20) + } + } + } + + // *************** Internal Functions ********************* // + + function enableDefaultStaticCalls(address _wallet) internal { + // setup the static calls that are available for free for all wallets + IWallet(_wallet).enableStaticCall(address(this), ERC1271_IS_VALID_SIGNATURE); + IWallet(_wallet).enableStaticCall(address(this), ERC721_RECEIVED); + } + + function multiCallWithApproval(address _wallet, Call[] calldata _transactions) internal returns (bytes[] memory) { + bytes[] memory results = new bytes[](_transactions.length); + for(uint i = 0; i < _transactions.length; i++) { + results[i] = invokeWallet(_wallet, _transactions[i].to, _transactions[i].value, _transactions[i].data); + } + return results; + } + + function startSession(address _wallet, address _sessionUser, uint64 _duration) internal { + require(_sessionUser != address(0), "TM: Invalid session user"); + require(_duration > 0, "TM: Invalid session duration"); + + uint64 expiry = SafeCast.toUint64(block.timestamp + _duration); + sessions[_wallet] = Session(_sessionUser, expiry); + emit SessionCreated(_wallet, _sessionUser, expiry); + } + + function setWhitelist(address _wallet, address _target, uint256 _whitelistAfter) internal { + userWhitelist.setWhitelist(_wallet, _target, _whitelistAfter); + } +} \ No newline at end of file diff --git a/contracts/modules/TransferManager.sol b/contracts/modules/TransferManager.sol deleted file mode 100644 index 1b209e9de..000000000 --- a/contracts/modules/TransferManager.sol +++ /dev/null @@ -1,597 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./common/Utils.sol"; -import "./common/BaseTransfer.sol"; -import "./common/LimitUtils.sol"; -import "../infrastructure/storage/ILimitStorage.sol"; -import "../infrastructure/storage/ITransferStorage.sol"; -import "../infrastructure/ITokenPriceRegistry.sol"; -import "../../lib/other/ERC20.sol"; - -/** - * @title TransferManager - * @notice Feature to transfer and approve tokens (ETH or ERC20) or data (contract call) based on a security context (daily limit, whitelist, etc). - * @author Julien Niset - - */ -contract TransferManager is BaseTransfer { - - bytes32 constant NAME = "TransferManager"; - - bytes4 private constant ERC1271_ISVALIDSIGNATURE_BYTES32 = bytes4(keccak256("isValidSignature(bytes32,bytes)")); - - enum ActionType { Transfer } - - using SafeMath for uint256; - - struct TokenManagerConfig { - // Mapping between pending action hash and their timestamp - mapping (bytes32 => uint256) pendingActions; - } - - // wallet specific storage - mapping (address => TokenManagerConfig) internal configs; - - // The security period - uint256 public securityPeriod; - // The execution window - uint256 public securityWindow; - // The default limit - uint128 public defaultLimit; - // The Token storage - ITransferStorage public transferStorage; - // The previous limit manager needed to migrate the limits - TransferManager public oldTransferManager; - // The limit storage - ILimitStorage public limitStorage; - // The token price storage - ITokenPriceRegistry public tokenPriceRegistry; - - // *************** Events *************************** // - - event AddedToWhitelist(address indexed wallet, address indexed target, uint64 whitelistAfter); - event RemovedFromWhitelist(address indexed wallet, address indexed target); - event PendingTransferCreated(address indexed wallet, bytes32 indexed id, uint256 indexed executeAfter, - address token, address to, uint256 amount, bytes data); - event PendingTransferExecuted(address indexed wallet, bytes32 indexed id); - event PendingTransferCanceled(address indexed wallet, bytes32 indexed id); - event DailyLimitMigrated(address indexed wallet, uint256 currentDailyLimit, uint256 pendingDailyLimit, uint256 changeDailyLimitAfter); - event DailyLimitDisabled(address indexed wallet, uint256 securityPeriod); - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - ITransferStorage _transferStorage, - ILimitStorage _limitStorage, - ITokenPriceRegistry _tokenPriceRegistry, - IVersionManager _versionManager, - uint256 _securityPeriod, - uint256 _securityWindow, - uint256 _defaultLimit, - address _wethToken, - TransferManager _oldTransferManager - ) - BaseFeature(_lockStorage, _versionManager, NAME) - BaseTransfer(_wethToken) - public - { - transferStorage = _transferStorage; - limitStorage = _limitStorage; - tokenPriceRegistry = _tokenPriceRegistry; - securityPeriod = _securityPeriod; - securityWindow = _securityWindow; - defaultLimit = LimitUtils.safe128(_defaultLimit); - oldTransferManager = _oldTransferManager; - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } - - /** - * @inheritdoc IFeature - */ - function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) { - _sigs = new bytes4[](1); - _sigs[0] = ERC1271_ISVALIDSIGNATURE_BYTES32; - } - - - /** - * @notice Inits the feature for a wallet by setting up the isValidSignature (EIP 1271) - * static call redirection from the wallet to the feature and copying all the parameters - * of the daily limit from the previous implementation of the LimitManager module. - * @param _wallet The target wallet. - */ - function init(address _wallet) external override(BaseFeature) onlyVersionManager { - - if (address(oldTransferManager) == address(0)) { - setLimit(_wallet, ILimitStorage.Limit(defaultLimit, 0, 0)); - } else { - uint256 current = oldTransferManager.getCurrentLimit(_wallet); - (uint256 pending, uint64 changeAfter) = oldTransferManager.getPendingLimit(_wallet); - if (current == 0 && changeAfter == 0) { - // new wallet: we setup the default limit - setLimit(_wallet, ILimitStorage.Limit(defaultLimit, 0, 0)); - } else { - // migrate limit and daily spent (if we are in a rolling period) - (uint256 unspent, uint64 periodEnd) = oldTransferManager.getDailyUnspent(_wallet); - - if (periodEnd < block.timestamp) { - setLimit(_wallet, ILimitStorage.Limit(LimitUtils.safe128(current), LimitUtils.safe128(pending), changeAfter)); - } else { - setLimitAndDailySpent( - _wallet, - ILimitStorage.Limit(LimitUtils.safe128(current), LimitUtils.safe128(pending), changeAfter), - ILimitStorage.DailySpent(LimitUtils.safe128(current.sub(unspent)), periodEnd) - ); - } - - emit DailyLimitMigrated(_wallet, current, pending, changeAfter); - } - } - } - - // *************** External/Public Functions ********************* // - - /** - * @notice Lets the owner transfer tokens (ETH or ERC20) from a wallet. - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _to The destination address - * @param _amount The amoutn of token to transfer - * @param _data The data for the transaction - */ - function transferToken( - address _wallet, - address _token, - address _to, - uint256 _amount, - bytes calldata _data - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _to)) { - // transfer to whitelist - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - uint256 etherAmount = (_token == ETH_TOKEN) ? _amount : LimitUtils.getEtherValue(tokenPriceRegistry, _amount, _token); - if (LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, etherAmount)) { - // transfer under the limit - doTransfer(_wallet, _token, _to, _amount, _data); - } else { - // transfer above the limit - (bytes32 id, uint256 executeAfter) = addPendingAction(ActionType.Transfer, _wallet, _token, _to, _amount, _data); - emit PendingTransferCreated(_wallet, id, executeAfter, _token, _to, _amount, _data); - } - } - } - - /** - * @notice Lets the owner approve an allowance of ERC20 tokens for a spender (dApp). - * @param _wallet The target wallet. - * @param _token The address of the token to transfer. - * @param _spender The address of the spender - * @param _amount The amount of tokens to approve - */ - function approveToken( - address _wallet, - address _token, - address _spender, - uint256 _amount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - if (isWhitelisted(_wallet, _spender)) { - // approve to whitelist - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // get current alowance - uint256 currentAllowance = ERC20(_token).allowance(_wallet, _spender); - if (_amount <= currentAllowance) { - // approve if we reduce the allowance - doApproveToken(_wallet, _token, _spender, _amount); - } else { - // check if delta is under the limit - uint delta = _amount - currentAllowance; - uint256 deltaInEth = LimitUtils.getEtherValue(tokenPriceRegistry, delta, _token); - require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, deltaInEth), "TM: Approve above daily limit"); - // approve if under the limit - doApproveToken(_wallet, _token, _spender, _amount); - } - } - } - - /** - * @notice Lets the owner call a contract. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - * @param _value The amount of ETH to transfer as part of call - * @param _data The encoded method data - */ - function callContract( - address _wallet, - address _contract, - uint256 _value, - bytes calldata _data - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - checkAndUpdateDailySpentIfNeeded(_wallet, ETH_TOKEN, _value, _contract); - doCallContract(_wallet, _contract, _value, _data); - } - - /** - * @notice Lets the owner do an ERC20 approve followed by a call to a contract. - * We assume that the contract will pull the tokens and does not require ETH. - * @param _wallet The target wallet. - * @param _token The token to approve. - * @param _proxy The address to approve, which may be different from the contract being called. - * @param _amount The amount of ERC20 tokens to approve. - * @param _contract The address of the contract. - * @param _data The encoded method data - */ - function approveTokenAndCallContract( - address _wallet, - address _token, - address _proxy, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - checkAndUpdateDailySpentIfNeeded(_wallet, _token, _amount, _contract); - doApproveTokenAndCallContract(_wallet, _token, _proxy, _amount, _contract, _data); - } - - /** - * @notice Lets the owner wrap ETH into WETH, approve the WETH and call a contract. - * We assume that the contract will pull the tokens and does not require ETH. - * @param _wallet The target wallet. - * @param _proxy The address to approve, which may be different from the contract being called. - * @param _amount The amount of ETH to wrap and approve. - * @param _contract The address of the contract. - * @param _data The encoded method data - */ - function approveWethAndCallContract( - address _wallet, - address _proxy, - uint256 _amount, - address _contract, - bytes calldata _data - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - onlyAuthorisedContractCall(_wallet, _contract) - { - checkAndUpdateDailySpentIfNeeded(_wallet, wethToken, _amount, _contract); - doApproveWethAndCallContract(_wallet, _proxy, _amount, _contract, _data); - } - - /** - * @notice Adds an address to the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to add. - */ - function addToWhitelist( - address _wallet, - address _target - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - require(!isWhitelisted(_wallet, _target), "TT: target already whitelisted"); - - uint256 whitelistAfter = block.timestamp.add(securityPeriod); - setWhitelist(_wallet, _target, whitelistAfter); - emit AddedToWhitelist(_wallet, _target, uint64(whitelistAfter)); - } - - /** - * @notice Removes an address from the whitelist of a wallet. - * @param _wallet The target wallet. - * @param _target The address to remove. - */ - function removeFromWhitelist( - address _wallet, - address _target - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - setWhitelist(_wallet, _target, 0); - emit RemovedFromWhitelist(_wallet, _target); - } - - /** - * @notice Executes a pending transfer for a wallet. - * The method can be called by anyone to enable orchestration. - * @param _wallet The target wallet. - * @param _token The token of the pending transfer. - * @param _to The destination address of the pending transfer. - * @param _amount The amount of token to transfer of the pending transfer. - * @param _data The data associated to the pending transfer. - * @param _block The block at which the pending transfer was created. - */ - function executePendingTransfer( - address _wallet, - address _token, - address _to, - uint _amount, - bytes calldata _data, - uint _block - ) - external - onlyWhenUnlocked(_wallet) - { - bytes32 id = keccak256(abi.encodePacked(ActionType.Transfer, _token, _to, _amount, _data, _block)); - uint executeAfter = configs[_wallet].pendingActions[id]; - require(executeAfter > 0, "TT: unknown pending transfer"); - uint executeBefore = executeAfter.add(securityWindow); - - require(executeAfter <= block.timestamp && block.timestamp <= executeBefore, "TT: transfer outside of the execution window"); - delete configs[_wallet].pendingActions[id]; - doTransfer(_wallet, _token, _to, _amount, _data); - emit PendingTransferExecuted(_wallet, id); - } - - function cancelPendingTransfer( - address _wallet, - bytes32 _id - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - require(configs[_wallet].pendingActions[_id] > 0, "TT: unknown pending action"); - delete configs[_wallet].pendingActions[_id]; - emit PendingTransferCanceled(_wallet, _id); - } - - /** - * @notice Lets the owner of a wallet change its daily limit. - * The limit is expressed in ETH. Changes to the limit take 24 hours. - * @param _wallet The target wallet. - * @param _newLimit The new limit. - */ - function changeLimit(address _wallet, uint256 _newLimit) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - ILimitStorage.Limit memory limit = LimitUtils.changeLimit(limitStorage, versionManager, _wallet, _newLimit, securityPeriod); - emit LimitChanged(_wallet, _newLimit, limit.changeAfter); - } - - /** - * @notice Convenience method to disable the limit - * The limit is disabled by setting it to an arbitrary large value. - * @param _wallet The target wallet. - */ - function disableLimit(address _wallet) external onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - LimitUtils.disableLimit(limitStorage, versionManager, _wallet, securityPeriod); - emit DailyLimitDisabled(_wallet, securityPeriod); - } - - /** - * @notice Gets the current daily limit for a wallet. - * @param _wallet The target wallet. - * @return _currentLimit The current limit expressed in ETH. - */ - function getCurrentLimit(address _wallet) external view returns (uint256 _currentLimit) { - ILimitStorage.Limit memory limit = limitStorage.getLimit(_wallet); - return LimitUtils.currentLimit(limit); - } - - /** - * @notice Returns whether the daily limit is disabled for a wallet. - * @param _wallet The target wallet. - * @return _limitDisabled true if the daily limit is disabled, false otherwise. - */ - function isLimitDisabled(address _wallet) public view returns (bool _limitDisabled) { - return LimitUtils.isLimitDisabled(limitStorage, _wallet); - } - - /** - * @notice Gets a pending limit for a wallet if any. - * @param _wallet The target wallet. - * @return _pendingLimit The pending limit (in ETH). - * @return _changeAfter The time at which the pending limit will become effective. - */ - function getPendingLimit(address _wallet) external view returns (uint256 _pendingLimit, uint64 _changeAfter) { - ILimitStorage.Limit memory limit = limitStorage.getLimit(_wallet); - - return ((block.timestamp < limit.changeAfter)? (limit.pending, uint64(limit.changeAfter)) : (0,0)); - } - - /** - * @notice Gets the amount of tokens that has not yet been spent during the current period. - * @param _wallet The target wallet. - * @return _unspent The amount of tokens (in ETH) that has not been spent yet. - * @return _periodEnd The end of the daily period. - */ - function getDailyUnspent(address _wallet) external view returns (uint256 _unspent, uint64 _periodEnd) { - ( - ILimitStorage.Limit memory limit, - ILimitStorage.DailySpent memory dailySpent - ) = limitStorage.getLimitAndDailySpent(_wallet); - uint256 currentLimit = LimitUtils.currentLimit(limit); - - if (block.timestamp > dailySpent.periodEnd) { - return (currentLimit, uint64(block.timestamp.add(24 hours))); - } else if (dailySpent.alreadySpent < currentLimit) { - return (currentLimit.sub(dailySpent.alreadySpent), dailySpent.periodEnd); - } else { - return (0, dailySpent.periodEnd); - } - } - - /** - * @notice Checks if an address is whitelisted for a wallet. - * @param _wallet The target wallet. - * @param _target The address. - * @return _isWhitelisted true if the address is whitelisted. - */ - function isWhitelisted(address _wallet, address _target) public view returns (bool _isWhitelisted) { - uint whitelistAfter = transferStorage.getWhitelist(_wallet, _target); - - return whitelistAfter > 0 && whitelistAfter < block.timestamp; - } - - /** - * @notice Gets the info of a pending transfer for a wallet. - * @param _wallet The target wallet. - * @param _id The pending transfer ID. - * @return _executeAfter The epoch time at which the pending transfer can be executed. - */ - function getPendingTransfer(address _wallet, bytes32 _id) external view returns (uint64 _executeAfter) { - _executeAfter = uint64(configs[address(_wallet)].pendingActions[_id]); - } - - /** - * @notice Implementation of EIP 1271. - * Should return whether the signature provided is valid for the provided data. - * @param _msgHash Hash of a message signed on the behalf of address(this) - * @param _signature Signature byte array associated with _msgHash - */ - function isValidSignature(bytes32 _msgHash, bytes memory _signature) public view returns (bytes4) { - require(_signature.length == 65, "TM: invalid signature length"); - address signer = Utils.recoverSigner(_msgHash, _signature, 0); - require(isOwner(msg.sender, signer), "TM: Invalid signer"); - return ERC1271_ISVALIDSIGNATURE_BYTES32; - } - - // *************** Internal Functions ********************* // - - /** - * @notice Creates a new pending action for a wallet. - * @param _action The target action. - * @param _wallet The target wallet. - * @param _token The target token for the action. - * @param _to The recipient of the action. - * @param _amount The amount of token associated to the action. - * @param _data The data associated to the action. - * @return id The identifier for the new pending action. - * @return executeAfter The time when the action can be executed - */ - function addPendingAction( - ActionType _action, - address _wallet, - address _token, - address _to, - uint _amount, - bytes memory _data - ) - internal - returns (bytes32 id, uint256 executeAfter) - { - id = keccak256(abi.encodePacked(_action, _token, _to, _amount, _data, block.number)); - require(configs[_wallet].pendingActions[id] == 0, "TM: duplicate pending action"); - - executeAfter = block.timestamp.add(securityPeriod); - configs[_wallet].pendingActions[id] = executeAfter; - } - - /** - * @notice Make sure a contract call is not trying to call a supported ERC20. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - */ - function coveredByDailyLimit(address _wallet, address _contract) internal view returns (bool) { - return (tokenPriceRegistry.getTokenPrice(_contract) > 0 && !isLimitDisabled(_wallet)); - } - - /** - * @notice Verify and update the daily spent if the spender is not whitelisted. - * Reverts if the daily spent is insufficient or if the contract to call is - * protected by the daily limit (i.e. is a token contract). - * @param _wallet The target wallet. - * @param _token The token that the spender will spend. - * @param _amount The amount of ERC20 or ETH that the spender will spend. - * @param _contract The address of the contract called by the wallet for the spend to occur. - */ - - function checkAndUpdateDailySpentIfNeeded( - address _wallet, - address _token, - uint256 _amount, - address _contract - ) - internal - { - if (!isWhitelisted(_wallet, _contract)) { - // Make sure we don't call a supported ERC20 that's not whitelisted - require(!coveredByDailyLimit(_wallet, _contract), "TM: Forbidden contract"); - - // Check if the amount is under the daily limit. - // Check the entire amount because the currently approved amount will be restored and should still count towards the daily limit - uint256 valueInEth; - if (_token == ETH_TOKEN || _token == wethToken) { - valueInEth = _amount; - } else { - valueInEth = LimitUtils.getEtherValue(tokenPriceRegistry, _amount, _token); - } - require(LimitUtils.checkAndUpdateDailySpent(limitStorage, versionManager, _wallet, valueInEth), "TM: Approve above daily limit"); - } - } - - // *************** Internal Functions ********************* // - - function setWhitelist(address _wallet, address _target, uint256 _whitelistAfter) internal { - versionManager.invokeStorage( - _wallet, - address(transferStorage), - abi.encodeWithSelector(transferStorage.setWhitelist.selector, _wallet, _target, _whitelistAfter) - ); - } - - function setLimit(address _wallet, ILimitStorage.Limit memory _limit) internal { - versionManager.invokeStorage( - _wallet, - address(limitStorage), - abi.encodeWithSelector(limitStorage.setLimit.selector, _wallet, _limit) - ); - } - - function setLimitAndDailySpent( - address _wallet, - ILimitStorage.Limit memory _limit, - ILimitStorage.DailySpent memory _dailySpent - ) internal { - versionManager.invokeStorage( - _wallet, - address(limitStorage), - abi.encodeWithSelector(limitStorage.setLimitAndDailySpent.selector, _wallet, _limit, _dailySpent) - ); - } -} diff --git a/contracts/modules/UpgraderToVersionManager.sol b/contracts/modules/UpgraderToVersionManager.sol deleted file mode 100644 index 47375b7c9..000000000 --- a/contracts/modules/UpgraderToVersionManager.sol +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./common/IModule.sol"; -import "./common/IVersionManager.sol"; -import "../infrastructure/IModuleRegistry.sol"; -import "../infrastructure/storage/ILockStorage.sol"; -import "../wallet/IWallet.sol"; - -/** - * @title UpgraderToVersionManager - * @notice Temporary module used to add the VersionManager and remove other modules. - * @author Olivier VDB - , Julien Niset - - */ -contract UpgraderToVersionManager is IModule { - - IModuleRegistry private registry; - ILockStorage private lockStorage; - address[] public toDisable; - address public versionManager; - - // *************** Constructor ********************** // - - constructor( - IModuleRegistry _registry, - ILockStorage _lockStorage, - address[] memory _toDisable, - address _versionManager - ) - public - { - registry = _registry; - lockStorage = _lockStorage; - toDisable = _toDisable; - versionManager = _versionManager; - } - - // *************** External/Public Functions ********************* // - - /** - * @notice Perform the upgrade for a wallet. This method gets called when UpgradeToVersionManager is temporarily added as a module. - * @param _wallet The target wallet. - */ - function init(address _wallet) public override { - require(msg.sender == _wallet, "SU: only wallet can call init"); - require(!lockStorage.isLocked(_wallet), "SU: wallet locked"); - require(registry.isRegisteredModule(versionManager), "SU: VersionManager not registered"); - - // add VersionManager - IWallet(_wallet).authoriseModule(versionManager, true); - - // upgrade wallet from version 0 to version 1 - IVersionManager(versionManager).upgradeWallet(_wallet, 1); - - // remove old modules - for (uint256 i = 0; i < toDisable.length; i++) { - IWallet(_wallet).authoriseModule(toDisable[i], false); - } - // SimpleUpgrader did its job, we no longer need it as a module - IWallet(_wallet).authoriseModule(address(this), false); - } - - /** - * @inheritdoc IModule - */ - function addModule(address _wallet, address _module) external override {} -} \ No newline at end of file diff --git a/contracts/modules/VersionManager.sol b/contracts/modules/VersionManager.sol deleted file mode 100644 index 2f1a77026..000000000 --- a/contracts/modules/VersionManager.sol +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "./common/Utils.sol"; -import "../infrastructure/base/Owned.sol"; -import "../infrastructure/storage/ITransferStorage.sol"; -import "../infrastructure/storage/IGuardianStorage.sol"; -import "./common/IModule.sol"; -import "./common/BaseFeature.sol"; - -/** - * @title VersionManager - * @notice Intermediate contract between features and wallets. VersionManager checks that a calling feature is - * authorised for the wallet and if so, forwards the call to it. Note that VersionManager is meant to be the only - * module authorised on a wallet and because some of its methods need to be called by the RelayerManager feature, - * the VersionManager is both a module AND a feature. - * @author Olivier VDB - */ -contract VersionManager is IVersionManager, IModule, BaseFeature, Owned { - - bytes32 constant NAME = "VersionManager"; - - bytes4 constant internal ADD_MODULE_PREFIX = bytes4(keccak256("addModule(address,address)")); - bytes4 constant internal UPGRADE_WALLET_PREFIX = bytes4(keccak256("upgradeWallet(address,uint256)")); - - // Last bundle version - uint256 public lastVersion; - // Minimum allowed version - uint256 public minVersion = 1; - // Current bundle version for a wallet - mapping(address => uint256) public walletVersions; // [wallet] => [version] - // Features per version - mapping(address => mapping(uint256 => bool)) public isFeatureInVersion; // [feature][version] => bool - // Features requiring initialization for a wallet - mapping(uint256 => address[]) public featuresToInit; // [version] => [features] - - // Supported static call signatures - mapping(uint256 => bytes4[]) public staticCallSignatures; // [version] => [sigs] - // Features executing static calls - mapping(uint256 => mapping(bytes4 => address)) public staticCallExecutors; // [version][sig] => [feature] - - // Authorised Storages - mapping(address => bool) public isStorage; // [storage] => bool - - event VersionAdded(uint256 _version, address[] _features); - event WalletUpgraded(address indexed _wallet, uint256 _version); - - // The Module Registry - IModuleRegistry private registry; - - /* ***************** Constructor ************************* */ - - constructor( - IModuleRegistry _registry, - ILockStorage _lockStorage, - IGuardianStorage _guardianStorage, - ITransferStorage _transferStorage, - ILimitStorage _limitStorage - ) - BaseFeature(_lockStorage, IVersionManager(address(this)), NAME) - public - { - registry = _registry; - - // Add initial storages - if(address(_lockStorage) != address(0)) { - addStorage(address(_lockStorage)); - } - if(address(_guardianStorage) != address(0)) { - addStorage(address(_guardianStorage)); - } - if(address(_transferStorage) != address(0)) { - addStorage(address(_transferStorage)); - } - if(address(_limitStorage) != address(0)) { - addStorage(address(_limitStorage)); - } - } - - /* ***************** onlyOwner ************************* */ - - /** - * @inheritdoc IFeature - */ - function recoverToken(address _token) external override onlyOwner { - uint total = ERC20(_token).balanceOf(address(this)); - _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, msg.sender, total)); - } - - /** - * @notice Lets the owner change the minimum allowed version - * @param _minVersion the minimum allowed version - */ - function setMinVersion(uint256 _minVersion) external onlyOwner { - require(_minVersion > 0 && _minVersion <= lastVersion, "VM: invalid _minVersion"); - minVersion = _minVersion; - } - - /** - * @notice Lets the owner add a new version, i.e. a new bundle of features. - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * WARNING: if a feature was added to a version and later on removed from a subsequent version, - * the feature may no longer be used in any future version without first being redeployed. - * Otherwise, the feature could be initialized more than once. - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * @param _features the list of features included in the new version - * @param _featuresToInit the subset of features that need to be initialized for a wallet - */ - function addVersion(address[] calldata _features, address[] calldata _featuresToInit) external onlyOwner { - uint256 newVersion = ++lastVersion; - for(uint256 i = 0; i < _features.length; i++) { - isFeatureInVersion[_features[i]][newVersion] = true; - - // Store static call information to optimise its use by wallets - bytes4[] memory sigs = IFeature(_features[i]).getStaticCallSignatures(); - for(uint256 j = 0; j < sigs.length; j++) { - staticCallSignatures[newVersion].push(sigs[j]); - staticCallExecutors[newVersion][sigs[j]] = _features[i]; - } - } - - // Sanity check - for(uint256 i = 0; i < _featuresToInit.length; i++) { - require(isFeatureInVersion[_featuresToInit[i]][newVersion], "VM: invalid _featuresToInit"); - } - - featuresToInit[newVersion] = _featuresToInit; - - emit VersionAdded(newVersion, _features); - } - - /** - * @notice Lets the owner add a storage contract - * @param _storage the storage contract to add - */ - function addStorage(address _storage) public onlyOwner { - require(!isStorage[_storage], "VM: storage already added"); - isStorage[_storage] = true; - } - - /* ***************** View Methods ************************* */ - - /** - * @inheritdoc IVersionManager - */ - function isFeatureAuthorised(address _wallet, address _feature) external view override returns (bool) { - // Note that the VersionManager is the only feature that isn't stored in isFeatureInVersion - return _isFeatureAuthorisedForWallet(_wallet, _feature) || _feature == address(this); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address /* _wallet */, bytes calldata _data) external view override returns (uint256, OwnerSignature) { - bytes4 methodId = Utils.functionPrefix(_data); - // This require ensures that the RelayerManager cannot be used to call a featureOnly VersionManager method - // that calls a Storage or the BaseWallet for backward-compatibility reason - require(methodId == UPGRADE_WALLET_PREFIX || methodId == ADD_MODULE_PREFIX, "VM: unknown method"); - return (1, OwnerSignature.Required); - } - - /* ***************** Static Call Delegation ************************* */ - - /** - * @notice This method is used by the VersionManager's fallback (via an internal call) to determine whether - * the current transaction is a staticcall or not. The method succeeds if the current transaction is a static call, - * and reverts otherwise. - * @dev The use of an if/else allows to encapsulate the whole logic in a single function. - */ - function verifyStaticCall() public { - if(msg.sender != address(this)) { // first entry in the method (via an internal call) - (bool success,) = address(this).call{gas: 3000}(abi.encodeWithSelector(VersionManager(0).verifyStaticCall.selector)); - require(!success, "VM: not in a staticcall"); - } else { // second entry in the method (via an external call) - // solhint-disable-next-line no-inline-assembly - assembly { log0(0, 0) } - } - } - - /** - * @notice This method delegates the static call to a target feature - */ - fallback() external { - uint256 version = walletVersions[msg.sender]; - address feature = staticCallExecutors[version][msg.sig]; - require(feature != address(0), "VM: static call not supported for wallet version"); - verifyStaticCall(); - - // solhint-disable-next-line no-inline-assembly - assembly { - calldatacopy(0, 0, calldatasize()) - let result := delegatecall(gas(), feature, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } - } - - /* ***************** Wallet Upgrade ************************* */ - - /** - * @inheritdoc IFeature - */ - function init(address _wallet) public override(IModule, BaseFeature) {} - - /** - * @inheritdoc IVersionManager - */ - function upgradeWallet(address _wallet, uint256 _toVersion) external override onlyWhenUnlocked(_wallet) { - require( - // Upgrade triggered by the RelayerManager (from version v>=1 to version v'>v) - _isFeatureAuthorisedForWallet(_wallet, msg.sender) || - // Upgrade triggered by WalletFactory or UpgraderToVersionManager (from version v=0 to version v'>0) - IWallet(_wallet).authorised(msg.sender) || - // Upgrade triggered directly by the owner (from version v>=1 to version v'>v) - isOwner(_wallet, msg.sender), - "VM: sender may not upgrade wallet" - ); - uint256 fromVersion = walletVersions[_wallet]; - uint256 minVersion_ = minVersion; - uint256 toVersion; - - if(_toVersion < minVersion_ && fromVersion == 0 && IWallet(_wallet).modules() == 2) { - // When the caller is the WalletFactory, we automatically change toVersion to minVersion if needed. - // Note that when fromVersion == 0, the caller could be the WalletFactory or the UpgraderToVersionManager. - // The WalletFactory will be the only possible caller when the wallet has only 2 authorised modules - // (that number would be >= 3 for a call from the UpgraderToVersionManager) - toVersion = minVersion_; - } else { - toVersion = _toVersion; - } - require(toVersion >= minVersion_ && toVersion <= lastVersion, "VM: invalid _toVersion"); - require(fromVersion < toVersion, "VM: already on new version"); - walletVersions[_wallet] = toVersion; - - // Setup static call redirection - bytes4[] storage sigs = staticCallSignatures[toVersion]; - for(uint256 i = 0; i < sigs.length; i++) { - bytes4 sig = sigs[i]; - if(IWallet(_wallet).enabled(sig) != address(this)) { - IWallet(_wallet).enableStaticCall(address(this), sig); - } - } - - // Init features - address[] storage featuresToInitInToVersion = featuresToInit[toVersion]; - for(uint256 i = 0; i < featuresToInitInToVersion.length; i++) { - address feature = featuresToInitInToVersion[i]; - // We only initialize a feature that was not already initialized in the previous version - if(fromVersion == 0 || !isFeatureInVersion[feature][fromVersion]) { - IFeature(feature).init(_wallet); - } - } - - emit WalletUpgraded(_wallet, toVersion); - - } - - /** - * @inheritdoc IModule - */ - function addModule(address _wallet, address _module) external override onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) { - require(registry.isRegisteredModule(_module), "VM: module is not registered"); - IWallet(_wallet).authoriseModule(_module, true); - } - - /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ - - /** - * @inheritdoc IVersionManager - */ - function checkAuthorisedFeatureAndInvokeWallet( - address _wallet, - address _to, - uint256 _value, - bytes memory _data - ) - external - override - returns (bytes memory _res) - { - require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke wallet"); - bool success; - (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data)); - if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values - (_res) = abi.decode(_res, (bytes)); - } else if (_res.length > 0) { - // solhint-disable-next-line no-inline-assembly - assembly { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - } else if (!success) { - revert("VM: wallet invoke reverted"); - } - } - - /** - * @inheritdoc IVersionManager - */ - function invokeStorage(address _wallet, address _storage, bytes calldata _data) external override { - require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke storage"); - require(verifyData(_wallet, _data), "VM: target of _data != _wallet"); - require(isStorage[_storage], "VM: invalid storage invoked"); - (bool success,) = _storage.call(_data); - require(success, "VM: _storage failed"); - } - - /** - * @inheritdoc IVersionManager - */ - function setOwner(address _wallet, address _newOwner) external override { - require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender should be authorized feature"); - IWallet(_wallet).setOwner(_newOwner); - } - - /* ***************** Internal Methods ************************* */ - - function _isFeatureAuthorisedForWallet(address _wallet, address _feature) private view returns (bool) { - return isFeatureInVersion[_feature][walletVersions[_wallet]]; - } -} \ No newline at end of file diff --git a/contracts/modules/common/BaseFeature.sol b/contracts/modules/common/BaseFeature.sol deleted file mode 100644 index 9b7f7eb95..000000000 --- a/contracts/modules/common/BaseFeature.sol +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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.s - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "../../wallet/IWallet.sol"; -import "../../infrastructure/IModuleRegistry.sol"; -import "../../infrastructure/storage/ILockStorage.sol"; -import "./IFeature.sol"; -import "../../../lib/other/ERC20.sol"; -import "./IVersionManager.sol"; - -/** - * @title BaseFeature - * @notice Base Feature contract that contains methods common to all Feature contracts. - * @author Julien Niset - , Olivier VDB - - */ -contract BaseFeature is IFeature { - - // Empty calldata - bytes constant internal EMPTY_BYTES = ""; - // Mock token address for ETH - address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - // The address of the Lock storage - ILockStorage internal lockStorage; - // The address of the Version Manager - IVersionManager internal versionManager; - - event FeatureCreated(bytes32 name); - - /** - * @notice Throws if the wallet is locked. - */ - modifier onlyWhenUnlocked(address _wallet) { - require(!lockStorage.isLocked(_wallet), "BF: wallet locked"); - _; - } - - /** - * @notice Throws if the sender is not the VersionManager. - */ - modifier onlyVersionManager() { - require(msg.sender == address(versionManager), "BF: caller must be VersionManager"); - _; - } - - /** - * @notice Throws if the sender is not the owner of the target wallet. - */ - modifier onlyWalletOwner(address _wallet) { - require(isOwner(_wallet, msg.sender), "BF: must be wallet owner"); - _; - } - - /** - * @notice Throws if the sender is not an authorised feature of the target wallet. - */ - modifier onlyWalletFeature(address _wallet) { - require(versionManager.isFeatureAuthorised(_wallet, msg.sender), "BF: must be a wallet feature"); - _; - } - - /** - * @notice Throws if the sender is not the owner of the target wallet or the feature itself. - */ - modifier onlyWalletOwnerOrFeature(address _wallet) { - // Wrapping in an internal method reduces deployment cost by avoiding duplication of inlined code - verifyOwnerOrAuthorisedFeature(_wallet, msg.sender); - _; - } - - constructor( - ILockStorage _lockStorage, - IVersionManager _versionManager, - bytes32 _name - ) public { - lockStorage = _lockStorage; - versionManager = _versionManager; - emit FeatureCreated(_name); - } - - /** - * @inheritdoc IFeature - */ - function recoverToken(address _token) external virtual override { - uint total = ERC20(_token).balanceOf(address(this)); - _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, address(versionManager), total)); - } - - /** - * @notice Inits the feature for a wallet by doing nothing. - * @dev !! Overriding methods need make sure `init()` can only be called by the VersionManager !! - * @param _wallet The wallet. - */ - function init(address _wallet) external virtual override {} - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external virtual view override returns (uint256, OwnerSignature) { - revert("BF: disabled method"); - } - - /** - * @inheritdoc IFeature - */ - function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) {} - - /** - * @inheritdoc IFeature - */ - function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) public override view returns (bool) { - return versionManager.isFeatureAuthorised(_wallet, _feature); - } - - /** - * @notice Checks that the wallet address provided as the first parameter of _data matches _wallet - * @return false if the addresses are different. - */ - function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) { - require(_data.length >= 36, "RM: Invalid dataWallet"); - address dataWallet = abi.decode(_data[4:], (address)); - return dataWallet == _wallet; - } - - /** - * @notice Helper method to check if an address is the owner of a target wallet. - * @param _wallet The target wallet. - * @param _addr The address. - */ - function isOwner(address _wallet, address _addr) internal view returns (bool) { - return IWallet(_wallet).owner() == _addr; - } - - /** - * @notice Verify that the caller is an authorised feature or the wallet owner. - * @param _wallet The target wallet. - * @param _sender The caller. - */ - function verifyOwnerOrAuthorisedFeature(address _wallet, address _sender) internal view { - require(isFeatureAuthorisedInVersionManager(_wallet, _sender) || isOwner(_wallet, _sender), "BF: must be owner or feature"); - } - - /** - * @notice Helper method to invoke a wallet. - * @param _wallet The target wallet. - * @param _to The target address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) - internal - returns (bytes memory _res) - { - _res = versionManager.checkAuthorisedFeatureAndInvokeWallet(_wallet, _to, _value, _data); - } - -} \ No newline at end of file diff --git a/contracts/modules/common/BaseModule.sol b/contracts/modules/common/BaseModule.sol new file mode 100644 index 000000000..1b8be035a --- /dev/null +++ b/contracts/modules/common/BaseModule.sol @@ -0,0 +1,190 @@ +// Copyright (C) 2018 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "../../wallet/IWallet.sol"; +import "../../infrastructure/IModuleRegistry.sol"; +import "../../infrastructure/storage/IGuardianStorage.sol"; +import "../../infrastructure/IAuthoriser.sol"; +import "../../infrastructure/storage/ITransferStorage.sol"; +import "./IModule.sol"; +import "../../../lib_0.5/other/ERC20.sol"; + +/** + * @title BaseModule + * @notice Base Module contract that contains methods common to all Modules. + * @author Julien Niset - , Olivier VDB - + */ +abstract contract BaseModule is IModule { + + // Empty calldata + bytes constant internal EMPTY_BYTES = ""; + // Mock token address for ETH + address constant internal ETH_TOKEN = address(0); + + // The module registry + IModuleRegistry internal immutable registry; + // The guardians storage + IGuardianStorage internal immutable guardianStorage; + // The trusted contacts storage + ITransferStorage internal immutable userWhitelist; + // The authoriser + IAuthoriser internal immutable authoriser; + + event ModuleCreated(bytes32 name); + + enum OwnerSignature { + Anyone, // Anyone + Required, // Owner required + Optional, // Owner and/or guardians + Disallowed, // Guardians only + Session // Session only + } + + struct Session { + address key; + uint64 expires; + } + + // Maps wallet to session + mapping (address => Session) internal sessions; + + struct Lock { + // the lock's release timestamp + uint64 release; + // the signature of the method that set the last lock + bytes4 locker; + } + + // Wallet specific lock storage + mapping (address => Lock) internal locks; + + /** + * @notice Throws if the wallet is not locked. + */ + modifier onlyWhenLocked(address _wallet) { + require(_isLocked(_wallet), "BM: wallet must be locked"); + _; + } + + /** + * @notice Throws if the wallet is locked. + */ + modifier onlyWhenUnlocked(address _wallet) { + require(!_isLocked(_wallet), "BM: wallet locked"); + _; + } + + /** + * @notice Throws if the sender is not the module itself. + */ + modifier onlySelf() { + require(_isSelf(msg.sender), "BM: must be module"); + _; + } + + /** + * @notice Throws if the sender is not the module itself or the owner of the target wallet. + */ + modifier onlyWalletOwnerOrSelf(address _wallet) { + require(_isSelf(msg.sender) || _isOwner(_wallet, msg.sender), "BM: must be wallet owner/self"); + _; + } + + /** + * @dev Throws if the sender is not the target wallet of the call. + */ + modifier onlyWallet(address _wallet) { + require(msg.sender == _wallet, "BM: caller must be wallet"); + _; + } + + constructor( + IModuleRegistry _registry, + IGuardianStorage _guardianStorage, + ITransferStorage _userWhitelist, + IAuthoriser _authoriser, + bytes32 _name + ) { + registry = _registry; + guardianStorage = _guardianStorage; + userWhitelist = _userWhitelist; + authoriser = _authoriser; + emit ModuleCreated(_name); + } + + /** + * @notice Moves tokens that have been sent to the module by mistake. + * @param _token The target token. + */ + function recoverToken(address _token) external { + uint total = ERC20(_token).balanceOf(address(this)); + ERC20(_token).transfer(address(registry), total); + } + + function _clearSession(address _wallet) internal { + delete sessions[_wallet]; + } + + /** + * @notice Helper method to check if an address is the owner of a target wallet. + * @param _wallet The target wallet. + * @param _addr The address. + */ + function _isOwner(address _wallet, address _addr) internal view returns (bool) { + return IWallet(_wallet).owner() == _addr; + } + + /** + * @notice Helper method to check if a wallet is locked. + * @param _wallet The target wallet. + */ + function _isLocked(address _wallet) internal view returns (bool) { + return locks[_wallet].release > uint64(block.timestamp); + } + + /** + * @notice Helper method to check if an address is the module itself. + * @param _addr The target address. + */ + function _isSelf(address _addr) internal view returns (bool) { + return _addr == address(this); + } + + /** + * @notice Helper method to invoke a wallet. + * @param _wallet The target wallet. + * @param _to The target address for the transaction. + * @param _value The value of the transaction. + * @param _data The data of the transaction. + */ + function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data) internal returns (bytes memory _res) { + bool success; + (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data)); + if (success && _res.length > 0) { //_res is empty if _wallet is an "old" BaseWallet that can't return output values + (_res) = abi.decode(_res, (bytes)); + } else if (_res.length > 0) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } else if (!success) { + revert("BM: wallet invoke reverted"); + } + } +} \ No newline at end of file diff --git a/contracts/modules/common/BaseTransfer.sol b/contracts/modules/common/BaseTransfer.sol deleted file mode 100644 index 6d1a000cd..000000000 --- a/contracts/modules/common/BaseTransfer.sol +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./BaseFeature.sol"; -import "./LimitUtils.sol"; - -/** - * @title BaseTransfer - * @notice Contains common methods to transfer tokens or call third-party contracts. - * @author Olivier VDB - - */ -abstract contract BaseTransfer is BaseFeature { - - // The address of the WETH token - address public wethToken; - - // *************** Events *************************** // - - event Transfer(address indexed wallet, address indexed token, uint256 indexed amount, address to, bytes data); - event Approved(address indexed wallet, address indexed token, uint256 amount, address spender); - event CalledContract(address indexed wallet, address indexed to, uint256 amount, bytes data); - event ApprovedAndCalledContract( - address indexed wallet, - address indexed to, - address spender, - address indexed token, - uint256 amountApproved, - uint256 amountSpent, - bytes data - ); - event LimitChanged(address indexed wallet, uint indexed newLimit, uint64 indexed startAfter); - - - // *************** Constructor ********************** // - - constructor(address _wethToken) public { - wethToken = _wethToken; - } - - - // *************** Internal Functions ********************* // - /** - * @notice Make sure a contract call is not trying to call a module, a feature, or the wallet itself. - * @param _wallet The target wallet. - * @param _contract The address of the contract. - */ - modifier onlyAuthorisedContractCall(address _wallet, address _contract) { - require( - _contract != _wallet && // not calling the wallet - !IWallet(_wallet).authorised(_contract) && // not calling an authorised module - !versionManager.isFeatureAuthorised(_wallet, _contract), // not calling an authorised feature - "BT: Forbidden contract" - ); - _; - } - - /** - * @notice Helper method to transfer ETH or ERC20 for a wallet. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _to The recipient. - * @param _value The amount of ETH to transfer - * @param _data The data to *log* with the transfer. - */ - function doTransfer(address _wallet, address _token, address _to, uint256 _value, bytes memory _data) internal { - if (_token == ETH_TOKEN) { - invokeWallet(_wallet, _to, _value, EMPTY_BYTES); - } else { - bytes memory methodData = abi.encodeWithSignature("transfer(address,uint256)", _to, _value); - bytes memory transferSuccessBytes = invokeWallet(_wallet, _token, 0, methodData); - // Check transfer is successful, when `transfer` returns a success bool result - if (transferSuccessBytes.length > 0) { - require(abi.decode(transferSuccessBytes, (bool)), "RM: Transfer failed"); - } - } - emit Transfer(_wallet, _token, _value, _to, _data); - } - - /** - * @notice Helper method to approve spending the ERC20 of a wallet. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _spender The spender address. - * @param _value The amount of token to transfer. - */ - function doApproveToken(address _wallet, address _token, address _spender, uint256 _value) internal { - bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _spender, _value); - invokeWallet(_wallet, _token, 0, methodData); - emit Approved(_wallet, _token, _value, _spender); - } - - /** - * @notice Helper method to call an external contract. - * @param _wallet The target wallet. - * @param _contract The contract address. - * @param _value The ETH value to transfer. - * @param _data The method data. - */ - function doCallContract(address _wallet, address _contract, uint256 _value, bytes memory _data) internal { - invokeWallet(_wallet, _contract, _value, _data); - emit CalledContract(_wallet, _contract, _value, _data); - } - - /** - * @notice Helper method to approve a certain amount of token and call an external contract. - * The address that spends the _token and the address that is called with _data can be different. - * @param _wallet The target wallet. - * @param _token The ERC20 address. - * @param _proxy The address to approve. - * @param _amount The amount of tokens to transfer. - * @param _contract The contract address. - * @param _data The method data. - */ - function doApproveTokenAndCallContract( - address _wallet, - address _token, - address _proxy, - uint256 _amount, - address _contract, - bytes memory _data - ) - internal - { - // Ensure there is sufficient balance of token before we approve - uint256 balance = ERC20(_token).balanceOf(_wallet); - require(balance >= _amount, "BT: insufficient balance"); - - uint256 existingAllowance = ERC20(_token).allowance(_wallet, _proxy); - uint256 totalAllowance = SafeMath.add(existingAllowance, _amount); - // Approve the desired amount plus existing amount. This logic allows for potential gas saving later - // when restoring the original approved amount, in cases where the _proxy uses the exact approved _amount. - bytes memory methodData = abi.encodeWithSignature("approve(address,uint256)", _proxy, totalAllowance); - - invokeWallet(_wallet, _token, 0, methodData); - invokeWallet(_wallet, _contract, 0, _data); - - // Calculate the approved amount that was spent after the call - uint256 unusedAllowance = ERC20(_token).allowance(_wallet, _proxy); - uint256 usedAllowance = SafeMath.sub(totalAllowance, unusedAllowance); - // Ensure the amount spent does not exceed the amount approved for this call - require(usedAllowance <= _amount, "BT: insufficient amount for call"); - - if (unusedAllowance != existingAllowance) { - // Restore the original allowance amount if the amount spent was different (can be lower). - methodData = abi.encodeWithSignature("approve(address,uint256)", _proxy, existingAllowance); - invokeWallet(_wallet, _token, 0, methodData); - } - - emit ApprovedAndCalledContract( - _wallet, - _contract, - _proxy, - _token, - _amount, - usedAllowance, - _data); - } - - /** - * @notice Helper method to wrap ETH into WETH, approve a certain amount of WETH and call an external contract. - * The address that spends the WETH and the address that is called with _data can be different. - * @param _wallet The target wallet. - * @param _proxy The address to approves. - * @param _amount The amount of tokens to transfer. - * @param _contract The contract address. - * @param _data The method data. - */ - function doApproveWethAndCallContract( - address _wallet, - address _proxy, - uint256 _amount, - address _contract, - bytes memory _data - ) - internal - { - uint256 wethBalance = ERC20(wethToken).balanceOf(_wallet); - if (wethBalance < _amount) { - // Wrap ETH into WETH - invokeWallet(_wallet, wethToken, _amount - wethBalance, abi.encodeWithSignature("deposit()")); - } - - doApproveTokenAndCallContract(_wallet, wethToken, _proxy, _amount, _contract, _data); - } -} diff --git a/contracts/modules/common/GuardianUtils.sol b/contracts/modules/common/GuardianUtils.sol deleted file mode 100644 index d4111ee99..000000000 --- a/contracts/modules/common/GuardianUtils.sol +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -/** - * @title GuardianUtils - * @notice Bundles guardian read logic. - */ -library GuardianUtils { - - /** - * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian - * given a list of guardians. - * @param _guardians the list of guardians - * @param _guardian the address to test - * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found. - */ - function isGuardianOrGuardianSigner(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) { - if (_guardians.length == 0 || _guardian == address(0)) { - return (false, _guardians); - } - bool isFound = false; - address[] memory updatedGuardians = new address[](_guardians.length - 1); - uint256 index = 0; - for (uint256 i = 0; i < _guardians.length; i++) { - if (!isFound) { - // check if _guardian is an account guardian - if (_guardian == _guardians[i]) { - isFound = true; - continue; - } - // check if _guardian is the owner of a smart contract guardian - if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) { - isFound = true; - continue; - } - } - if (index < updatedGuardians.length) { - updatedGuardians[index] = _guardians[i]; - index++; - } - } - return isFound ? (true, updatedGuardians) : (false, _guardians); - } - - /** - * @notice Checks if an address is a contract. - * @param _addr The address. - */ - function isContract(address _addr) internal view returns (bool) { - uint32 size; - // solhint-disable-next-line no-inline-assembly - assembly { - size := extcodesize(_addr) - } - return (size > 0); - } - - /** - * @notice Checks if an address is the owner of a guardian contract. - * The method does not revert if the call to the owner() method consumes more then 5000 gas. - * @param _guardian The guardian contract - * @param _owner The owner to verify. - */ - function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) { - address owner = address(0); - bytes4 sig = bytes4(keccak256("owner()")); - - // solhint-disable-next-line no-inline-assembly - assembly { - let ptr := mload(0x40) - mstore(ptr,sig) - let result := staticcall(5000, _guardian, ptr, 0x20, ptr, 0x20) - if eq(result, 1) { - owner := mload(ptr) - } - } - return owner == _owner; - } -} diff --git a/contracts/modules/common/IFeature.sol b/contracts/modules/common/IFeature.sol deleted file mode 100644 index a6c4413e9..000000000 --- a/contracts/modules/common/IFeature.sol +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; - -/** - * @title IFeature - * @notice Interface for a Feature. - * @author Julien Niset - , Olivier VDB - - */ -interface IFeature { - - enum OwnerSignature { - Anyone, // Anyone - Required, // Owner required - Optional, // Owner and/or guardians - Disallowed // guardians only - } - - /** - * @notice Utility method to recover any ERC20 token that was sent to the Feature by mistake. - * @param _token The token to recover. - */ - function recoverToken(address _token) external; - - /** - * @notice Inits a Feature for a wallet by e.g. setting some wallet specific parameters in storage. - * @param _wallet The wallet. - */ - function init(address _wallet) external; - - /** - * @notice Helper method to check if an address is an authorised feature of a target wallet. - * @param _wallet The target wallet. - * @param _feature The address. - */ - function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) external view returns (bool); - - /** - * @notice Gets the number of valid signatures that must be provided to execute a - * specific relayed transaction. - * @param _wallet The target wallet. - * @param _data The data of the relayed transaction. - * @return The number of required signatures and the wallet owner signature requirement. - */ - function getRequiredSignatures(address _wallet, bytes calldata _data) external view returns (uint256, OwnerSignature); - - /** - * @notice Gets the list of static call signatures that this feature responds to on behalf of wallets - */ - function getStaticCallSignatures() external view returns (bytes4[] memory); -} \ No newline at end of file diff --git a/contracts/modules/common/IModule.sol b/contracts/modules/common/IModule.sol index 367816e46..248461bc8 100644 --- a/contracts/modules/common/IModule.sol +++ b/contracts/modules/common/IModule.sol @@ -14,21 +14,14 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity ^0.8.3; /** * @title IModule - * @notice Interface for a module. - * A module MUST implement the addModule() method to ensure that a wallet with at least one module - * can never end up in a "frozen" state. - * @author Julien Niset - + * @notice Interface for a Module. + * @author Julien Niset - , Olivier VDB - */ interface IModule { - /** - * @notice Inits a module for a wallet by e.g. setting some wallet specific parameters in storage. - * @param _wallet The wallet. - */ - function init(address _wallet) external; /** * @notice Adds a module to a wallet. Cannot execute when wallet is locked (or under recovery) @@ -36,4 +29,17 @@ interface IModule { * @param _module The modules to authorise. */ function addModule(address _wallet, address _module) external; + + /** + * @notice Inits a Module for a wallet by e.g. setting some wallet specific parameters in storage. + * @param _wallet The wallet. + */ + function init(address _wallet) external; + + + /** + * @notice Returns whether the module implements a callback for a given static call method. + * @param _methodId The method id. + */ + function supportsStaticCall(bytes4 _methodId) external view returns (bool _isSupported); } \ No newline at end of file diff --git a/contracts/modules/common/IVersionManager.sol b/contracts/modules/common/IVersionManager.sol deleted file mode 100644 index 6296e2e90..000000000 --- a/contracts/modules/common/IVersionManager.sol +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; -pragma experimental ABIEncoderV2; - -import "../../infrastructure/storage/ILimitStorage.sol"; - -/** - * @title IVersionManager - * @notice Interface for the VersionManager module. - * @author Olivier VDB - - */ -interface IVersionManager { - /** - * @notice Returns true if the feature is authorised for the wallet - * @param _wallet The target wallet. - * @param _feature The feature. - */ - function isFeatureAuthorised(address _wallet, address _feature) external view returns (bool); - - /** - * @notice Lets a feature (caller) invoke a wallet. - * @param _wallet The target wallet. - * @param _to The target address for the transaction. - * @param _value The value of the transaction. - * @param _data The data of the transaction. - */ - function checkAuthorisedFeatureAndInvokeWallet( - address _wallet, - address _to, - uint256 _value, - bytes calldata _data - ) external returns (bytes memory _res); - - /* ******* Backward Compatibility with old Storages and BaseWallet *************** */ - - /** - * @notice Sets a new owner for the wallet. - * @param _newOwner The new owner. - */ - function setOwner(address _wallet, address _newOwner) external; - - /** - * @notice Lets a feature write data to a storage contract. - * @param _wallet The target wallet. - * @param _storage The storage contract. - * @param _data The data of the call - */ - function invokeStorage(address _wallet, address _storage, bytes calldata _data) external; - - /** - * @notice Upgrade a wallet to a new version. - * @param _wallet the wallet to upgrade - * @param _toVersion the new version - */ - function upgradeWallet(address _wallet, uint256 _toVersion) external; - -} \ No newline at end of file diff --git a/contracts/modules/common/LimitUtils.sol b/contracts/modules/common/LimitUtils.sol deleted file mode 100644 index 1751e5710..000000000 --- a/contracts/modules/common/LimitUtils.sol +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (C) 2018 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; -pragma experimental ABIEncoderV2; - -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "../../infrastructure/storage/ILimitStorage.sol"; -import "../../infrastructure/ITokenPriceRegistry.sol"; -import "./IVersionManager.sol"; - -/** - * @title LimitUtils - * @notice Helper library to manage the daily limit and interact with a contract implementing the ILimitStorage interface. - * @author Julien Niset - - */ -library LimitUtils { - - // large limit when the limit can be considered disabled - uint128 constant internal LIMIT_DISABLED = uint128(-1); - - using SafeMath for uint256; - - // *************** Internal Functions ********************* // - - /** - * @notice Changes the daily limit (expressed in ETH). - * Decreasing the limit is immediate while increasing the limit is pending for the security period. - * @param _lStorage The storage contract. - * @param _versionManager The version manager. - * @param _wallet The target wallet. - * @param _targetLimit The target limit. - * @param _securityPeriod The security period. - */ - function changeLimit( - ILimitStorage _lStorage, - IVersionManager _versionManager, - address _wallet, - uint256 _targetLimit, - uint256 _securityPeriod - ) - internal - returns (ILimitStorage.Limit memory) - { - ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); - uint256 currentLimit = currentLimit(limit); - ILimitStorage.Limit memory newLimit; - if (_targetLimit <= currentLimit) { - uint128 targetLimit = safe128(_targetLimit); - newLimit = ILimitStorage.Limit(targetLimit, targetLimit, safe64(block.timestamp)); - } else { - newLimit = ILimitStorage.Limit(safe128(currentLimit), safe128(_targetLimit), safe64(block.timestamp.add(_securityPeriod))); - } - setLimit(_versionManager, _lStorage, _wallet, newLimit); - return newLimit; - } - - /** - * @notice Disable the daily limit. - * The change is pending for the security period. - * @param _lStorage The storage contract. - * @param _versionManager The version manager. - * @param _wallet The target wallet. - * @param _securityPeriod The security period. - */ - function disableLimit( - ILimitStorage _lStorage, - IVersionManager _versionManager, - address _wallet, - uint256 _securityPeriod - ) - internal - { - changeLimit(_lStorage, _versionManager, _wallet, LIMIT_DISABLED, _securityPeriod); - } - - /** - * @notice Returns whether the daily limit is disabled for a wallet. - * @param _wallet The target wallet. - * @return _limitDisabled true if the daily limit is disabled, false otherwise. - */ - function isLimitDisabled(ILimitStorage _lStorage, address _wallet) internal view returns (bool) { - ILimitStorage.Limit memory limit = _lStorage.getLimit(_wallet); - uint256 currentLimit = currentLimit(limit); - return (currentLimit == LIMIT_DISABLED); - } - - /** - * @notice Checks if a transfer is within the limit. If yes the daily spent is updated. - * @param _lStorage The storage contract. - * @param _versionManager The Version Manager. - * @param _wallet The target wallet. - * @param _amount The amount for the transfer - * @return true if the transfer is withing the daily limit. - */ - function checkAndUpdateDailySpent( - ILimitStorage _lStorage, - IVersionManager _versionManager, - address _wallet, - uint256 _amount - ) - internal - returns (bool) - { - (ILimitStorage.Limit memory limit, ILimitStorage.DailySpent memory dailySpent) = _lStorage.getLimitAndDailySpent(_wallet); - uint256 currentLimit = currentLimit(limit); - if (_amount == 0 || currentLimit == LIMIT_DISABLED) { - return true; - } - ILimitStorage.DailySpent memory newDailySpent; - if (dailySpent.periodEnd <= block.timestamp && _amount <= currentLimit) { - newDailySpent = ILimitStorage.DailySpent(safe128(_amount), safe64(block.timestamp + 24 hours)); - setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); - return true; - } else if (dailySpent.periodEnd > block.timestamp && _amount.add(dailySpent.alreadySpent) <= currentLimit) { - newDailySpent = ILimitStorage.DailySpent(safe128(_amount.add(dailySpent.alreadySpent)), safe64(dailySpent.periodEnd)); - setDailySpent(_versionManager, _lStorage, _wallet, newDailySpent); - return true; - } - return false; - } - - /** - * @notice Helper method to Reset the daily consumption. - * @param _versionManager The Version Manager. - * @param _wallet The target wallet. - */ - function resetDailySpent(IVersionManager _versionManager, ILimitStorage limitStorage, address _wallet) internal { - setDailySpent(_versionManager, limitStorage, _wallet, ILimitStorage.DailySpent(uint128(0), uint64(0))); - } - - /** - * @notice Helper method to get the ether value equivalent of a token amount. - * @notice For low value amounts of tokens we accept this to return zero as these are small enough to disregard. - * Note that the price stored for tokens = price for 1 token (in ETH wei) * 10^(18-token decimals). - * @param _amount The token amount. - * @param _token The address of the token. - * @return The ether value for _amount of _token. - */ - function getEtherValue(ITokenPriceRegistry _priceRegistry, uint256 _amount, address _token) internal view returns (uint256) { - uint256 price = _priceRegistry.getTokenPrice(_token); - uint256 etherValue = price.mul(_amount).div(10**18); - return etherValue; - } - - /** - * @notice Helper method to get the current limit from a Limit struct. - * @param _limit The limit struct - */ - function currentLimit(ILimitStorage.Limit memory _limit) internal view returns (uint256) { - if (_limit.changeAfter > 0 && _limit.changeAfter < block.timestamp) { - return _limit.pending; - } - return _limit.current; - } - - function safe128(uint256 _num) internal pure returns (uint128) { - require(_num < 2**128, "LU: more then 128 bits"); - return uint128(_num); - } - - function safe64(uint256 _num) internal pure returns (uint64) { - require(_num < 2**64, "LU: more then 64 bits"); - return uint64(_num); - } - - // *************** Storage invocations in VersionManager ********************* // - - function setLimit( - IVersionManager _versionManager, - ILimitStorage _lStorage, - address _wallet, - ILimitStorage.Limit memory _limit - ) internal { - _versionManager.invokeStorage( - _wallet, - address(_lStorage), - abi.encodeWithSelector(_lStorage.setLimit.selector, _wallet, _limit) - ); - } - - function setDailySpent( - IVersionManager _versionManager, - ILimitStorage _lStorage, - address _wallet, - ILimitStorage.DailySpent memory _dailySpent - ) private { - _versionManager.invokeStorage( - _wallet, - address(_lStorage), - abi.encodeWithSelector(_lStorage.setDailySpent.selector, _wallet, _dailySpent) - ); - } - -} \ No newline at end of file diff --git a/contracts/modules/common/SimpleOracle.sol b/contracts/modules/common/SimpleOracle.sol new file mode 100644 index 000000000..6d65a0163 --- /dev/null +++ b/contracts/modules/common/SimpleOracle.sol @@ -0,0 +1,56 @@ +// Copyright (C) 2021 Argent Labs Ltd. + +// 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 . + +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; + +contract SimpleOracle { + + address internal immutable weth; + address internal immutable uniswapV2Factory; + + constructor(address _uniswapRouter) { + weth = IUniswapV2Router01(_uniswapRouter).WETH(); + uniswapV2Factory = IUniswapV2Router01(_uniswapRouter).factory(); + } + + function inToken(address _token, uint256 _ethAmount) internal view returns (uint256) { + (uint256 wethReserve, uint256 tokenReserve) = getReservesForTokenPool(_token); + return _ethAmount * tokenReserve / wethReserve; + } + + function getReservesForTokenPool(address _token) internal view returns (uint256 wethReserve, uint256 tokenReserve) { + if (weth < _token) { + address pair = getPairForSorted(weth, _token); + (wethReserve, tokenReserve,) = IUniswapV2Pair(pair).getReserves(); + } else { + address pair = getPairForSorted(_token, weth); + (tokenReserve, wethReserve,) = IUniswapV2Pair(pair).getReserves(); + } + require(wethReserve != 0 && tokenReserve != 0, "SO: no liquidity"); + } + + function getPairForSorted(address tokenA, address tokenB) internal virtual view returns (address pair) { + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + uniswapV2Factory, + keccak256(abi.encodePacked(tokenA, tokenB)), + hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' + ))))); + } +} \ No newline at end of file diff --git a/contracts/modules/common/Utils.sol b/contracts/modules/common/Utils.sol index 7ce585b5a..0ae08cb09 100644 --- a/contracts/modules/common/Utils.sol +++ b/contracts/modules/common/Utils.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; /** * @title Utils @@ -22,6 +22,16 @@ pragma solidity ^0.6.12; */ library Utils { + // ERC20, ERC721 & ERC1155 transfers & approvals + bytes4 private constant ERC20_TRANSFER = bytes4(keccak256("transfer(address,uint256)")); + bytes4 private constant ERC20_APPROVE = bytes4(keccak256("approve(address,uint256)")); + bytes4 private constant ERC721_SET_APPROVAL_FOR_ALL = bytes4(keccak256("setApprovalForAll(address,bool)")); + bytes4 private constant ERC721_TRANSFER_FROM = bytes4(keccak256("transferFrom(address,address,uint256)")); + bytes4 private constant ERC721_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256)")); + bytes4 private constant ERC721_SAFE_TRANSFER_FROM_BYTES = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")); + bytes4 private constant ERC1155_SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom(address,address,uint256,uint256,bytes)")); + + bytes4 private constant OWNER_SIG = 0x8da5cb5b; /** * @notice Helper method to recover the signer at a given position from a list of concatenated signatures. * @param _signedHash The signed hash @@ -41,24 +51,135 @@ library Utils { s := mload(add(_signatures, add(0x40,mul(0x41,_index)))) v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff) } - require(v == 27 || v == 28); + require(v == 27 || v == 28, "Utils: bad v value in signature"); address recoveredAddress = ecrecover(_signedHash, v, r, s); require(recoveredAddress != address(0), "Utils: ecrecover returned 0"); return recoveredAddress; } + /** + * @notice Helper method to recover the spender from a contract call. + * The method returns the contract unless the call is to a standard method of a ERC20/ERC721/ERC1155 token + * in which case the spender is recovered from the data. + * @param _to The target contract. + * @param _data The data payload. + */ + function recoverSpender(address _to, bytes memory _data) internal pure returns (address spender) { + if(_data.length >= 68) { + bytes4 methodId; + // solhint-disable-next-line no-inline-assembly + assembly { + methodId := mload(add(_data, 0x20)) + } + if( + methodId == ERC20_TRANSFER || + methodId == ERC20_APPROVE || + methodId == ERC721_SET_APPROVAL_FOR_ALL) + { + // solhint-disable-next-line no-inline-assembly + assembly { + spender := mload(add(_data, 0x24)) + } + return spender; + } + if( + methodId == ERC721_TRANSFER_FROM || + methodId == ERC721_SAFE_TRANSFER_FROM || + methodId == ERC721_SAFE_TRANSFER_FROM_BYTES || + methodId == ERC1155_SAFE_TRANSFER_FROM) + { + // solhint-disable-next-line no-inline-assembly + assembly { + spender := mload(add(_data, 0x44)) + } + return spender; + } + } + + spender = _to; + } + /** * @notice Helper method to parse data and extract the method signature. */ function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) { - require(_data.length >= 4, "RM: Invalid functionPrefix"); + require(_data.length >= 4, "Utils: Invalid functionPrefix"); // solhint-disable-next-line no-inline-assembly assembly { prefix := mload(add(_data, 0x20)) } } + /** + * @notice Checks if an address is a contract. + * @param _addr The address. + */ + function isContract(address _addr) internal view returns (bool) { + uint32 size; + // solhint-disable-next-line no-inline-assembly + assembly { + size := extcodesize(_addr) + } + return (size > 0); + } + + /** + * @notice Checks if an address is a guardian or an account authorised to sign on behalf of a smart-contract guardian + * given a list of guardians. + * @param _guardians the list of guardians + * @param _guardian the address to test + * @return true and the list of guardians minus the found guardian upon success, false and the original list of guardians if not found. + */ + function isGuardianOrGuardianSigner(address[] memory _guardians, address _guardian) internal view returns (bool, address[] memory) { + if (_guardians.length == 0 || _guardian == address(0)) { + return (false, _guardians); + } + bool isFound = false; + address[] memory updatedGuardians = new address[](_guardians.length - 1); + uint256 index = 0; + for (uint256 i = 0; i < _guardians.length; i++) { + if (!isFound) { + // check if _guardian is an account guardian + if (_guardian == _guardians[i]) { + isFound = true; + continue; + } + // check if _guardian is the owner of a smart contract guardian + if (isContract(_guardians[i]) && isGuardianOwner(_guardians[i], _guardian)) { + isFound = true; + continue; + } + } + if (index < updatedGuardians.length) { + updatedGuardians[index] = _guardians[i]; + index++; + } + } + return isFound ? (true, updatedGuardians) : (false, _guardians); + } + + /** + * @notice Checks if an address is the owner of a guardian contract. + * The method does not revert if the call to the owner() method consumes more then 25000 gas. + * @param _guardian The guardian contract + * @param _owner The owner to verify. + */ + function isGuardianOwner(address _guardian, address _owner) internal view returns (bool) { + address owner = address(0); + + // solhint-disable-next-line no-inline-assembly + assembly { + let ptr := mload(0x40) + mstore(ptr,OWNER_SIG) + let result := staticcall(25000, _guardian, ptr, 0x20, ptr, 0x20) + if eq(result, 1) { + owner := mload(ptr) + } + } + return owner == _owner; + } + /** * @notice Returns ceil(a / b). */ @@ -70,11 +191,4 @@ library Utils { return c + 1; } } - - function min(uint256 a, uint256 b) internal pure returns (uint256) { - if (a < b) { - return a; - } - return b; - } } diff --git a/contracts/modules/maker/IUniswapExchange.sol b/contracts/modules/maker/IUniswapExchange.sol deleted file mode 100644 index 0cac2db94..000000000 --- a/contracts/modules/maker/IUniswapExchange.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -interface IUniswapExchange { - function getEthToTokenOutputPrice(uint256 _tokensBought) external view returns (uint256); - function getEthToTokenInputPrice(uint256 _ethSold) external view returns (uint256); - function getTokenToEthOutputPrice(uint256 _ethBought) external view returns (uint256); - function getTokenToEthInputPrice(uint256 _tokensSold) external view returns (uint256); -} \ No newline at end of file diff --git a/contracts/modules/maker/IUniswapFactory.sol b/contracts/modules/maker/IUniswapFactory.sol deleted file mode 100644 index 67ff5b4a6..000000000 --- a/contracts/modules/maker/IUniswapFactory.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -interface IUniswapFactory { - function getExchange(address _token) external view returns(address); -} \ No newline at end of file diff --git a/contracts/modules/maker/MakerV2Base.sol b/contracts/modules/maker/MakerV2Base.sol deleted file mode 100644 index e749b6305..000000000 --- a/contracts/modules/maker/MakerV2Base.sol +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "../common/BaseFeature.sol"; -import "../../infrastructure/IMakerRegistry.sol"; -import "../../../lib/maker/MakerInterfaces.sol"; -import "../../../lib/maker/DS/DSMath.sol"; - -/** - * @title MakerV2Base - * @notice Common base to MakerV2Invest and MakerV2Loan. - * @author Olivier VDB - - */ -abstract contract MakerV2Base is DSMath, BaseFeature { - - bytes32 constant private NAME = "MakerV2Manager"; - - // The address of the (MCD) DAI token - GemLike internal daiToken; - // The address of the SAI <-> DAI migration contract - address internal scdMcdMigration; - // The address of the Dai Adapter - JoinLike internal daiJoin; - // The address of the Vat - VatLike internal vat; - - using SafeMath for uint256; - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - ScdMcdMigrationLike _scdMcdMigration, - IVersionManager _versionManager - ) - BaseFeature(_lockStorage, _versionManager, NAME) - public - { - scdMcdMigration = address(_scdMcdMigration); - daiJoin = _scdMcdMigration.daiJoin(); - daiToken = daiJoin.dai(); - vat = daiJoin.vat(); - } - - /** - * @inheritdoc IFeature - */ - function getRequiredSignatures(address, bytes calldata) external view override returns (uint256, OwnerSignature) { - return (1, OwnerSignature.Required); - } -} \ No newline at end of file diff --git a/contracts/modules/maker/MakerV2Invest.sol b/contracts/modules/maker/MakerV2Invest.sol deleted file mode 100644 index 8304b8a03..000000000 --- a/contracts/modules/maker/MakerV2Invest.sol +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./MakerV2Base.sol"; - -/** - * @title MakerV2Invest - * @notice Module to lock/unlock MCD DAI into/from Maker's Pot - * @author Olivier VDB - - */ -abstract contract MakerV2Invest is MakerV2Base { - - // The address of the Pot - PotLike internal pot; - - // *************** Events ********************** // - - // WARNING: in a previous version of this module, the third parameter of `InvestmentRemoved` - // represented the *fraction* (out of 10000) of the investment withdrawn, not the absolute amount withdrawn - event InvestmentRemoved(address indexed _wallet, address _token, uint256 _amount); - event InvestmentAdded(address indexed _wallet, address _token, uint256 _amount, uint256 _period); - - // *************** Constructor ********************** // - - constructor(PotLike _pot) public { - pot = _pot; - } - - // *************** External/Public Functions ********************* // - - /** - * @notice Lets the wallet owner deposit MCD DAI into the DSR Pot. - * @param _wallet The target wallet. - * @param _amount The amount of DAI to deposit - */ - function joinDsr( - address _wallet, - uint256 _amount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to get the chi rate updated to rho == block.timestamp, otherwise join will fail - pot.drip(); - // Approve DAI adapter to take the DAI amount - invokeWallet( - _wallet, - address(daiToken), - 0, - abi.encodeWithSignature("approve(address,uint256)", address(daiJoin), _amount) - ); - // Join DAI into the vat (_amount of external DAI is burned and the vat transfers _amount of internal DAI from the adapter to the _wallet) - invokeWallet( - _wallet, - address(daiJoin), - 0, - abi.encodeWithSignature("join(address,uint256)", address(_wallet), _amount) - ); - // Approve the pot to take out (internal) DAI from the wallet's balance in the vat - grantVatAccess(_wallet, address(pot)); - // Compute the pie value in the pot - uint256 pie = _amount.mul(RAY) / pot.chi(); - // Join the pie value to the pot - invokeWallet(_wallet, address(pot), 0, abi.encodeWithSignature("join(uint256)", pie)); - // Emitting event - emit InvestmentAdded(_wallet, address(daiToken), _amount, 0); - } - - /** - * @notice Lets the wallet owner withdraw MCD DAI from the DSR pot. - * @param _wallet The target wallet. - * @param _amount The amount of DAI to withdraw. - */ - function exitDsr( - address _wallet, - uint256 _amount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to count the savings accumulated until this moment - pot.drip(); - // Calculates the pie value in the pot equivalent to the DAI wad amount - uint256 pie = _amount.mul(RAY) / pot.chi(); - // Exit DAI from the pot - invokeWallet(_wallet, address(pot), 0, abi.encodeWithSignature("exit(uint256)", pie) - ); - // Allow adapter to access the _wallet's DAI balance in the vat - grantVatAccess(_wallet, address(daiJoin)); - // Check the actual balance of DAI in the vat after the pot exit - uint bal = vat.dai(_wallet); - // It is necessary to check if due to rounding the exact _amount can be exited by the adapter. - // Otherwise it will do the maximum DAI balance in the vat - uint256 withdrawn = bal >= _amount.mul(RAY) ? _amount : bal / RAY; - invokeWallet( - _wallet, - address(daiJoin), - 0, - abi.encodeWithSignature("exit(address,uint256)", address(_wallet), withdrawn) - ); - // Emitting event - emit InvestmentRemoved(_wallet, address(daiToken), withdrawn); - } - - /** - * @notice Lets the wallet owner withdraw their entire MCD DAI balance from the DSR pot. - * @param _wallet The target wallet. - */ - function exitAllDsr( - address _wallet - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - // Execute drip to count the savings accumulated until this moment - pot.drip(); - // Gets the total pie belonging to the _wallet - uint256 pie = pot.pie(_wallet); - // Exit DAI from the pot - invokeWallet(_wallet, address(pot), 0, abi.encodeWithSignature("exit(uint256)", pie)); - // Allow adapter to access the _wallet's DAI balance in the vat - grantVatAccess(_wallet, address(daiJoin)); - // Exits the DAI amount corresponding to the value of pie - uint256 withdrawn = pot.chi().mul(pie) / RAY; - invokeWallet( - _wallet, - address(daiJoin), - 0, - abi.encodeWithSignature("exit(address,uint256)", address(_wallet), withdrawn) - ); - // Emitting event - emit InvestmentRemoved(_wallet, address(daiToken), withdrawn); - } - - /** - * @notice Returns the amount of DAI currently held in the DSR pot. - * @param _wallet The target wallet. - * @return _balance The DSR balance. - */ - function dsrBalance(address _wallet) external view returns (uint256 _balance) { - return pot.chi().mul(pot.pie(_wallet)) / RAY; - } - - /* ****************************************** Internal method ******************************************* */ - - /** - * @notice Grant access to the wallet's internal DAI balance in the VAT to an operator. - * @param _wallet The target wallet. - * @param _operator The grantee of the access - */ - function grantVatAccess(address _wallet, address _operator) internal { - if (vat.can(_wallet, _operator) == 0) { - invokeWallet(_wallet, address(vat), 0, abi.encodeWithSignature("hope(address)", _operator)); - } - } -} \ No newline at end of file diff --git a/contracts/modules/maker/MakerV2Loan.sol b/contracts/modules/maker/MakerV2Loan.sol deleted file mode 100644 index dfe3b26e9..000000000 --- a/contracts/modules/maker/MakerV2Loan.sol +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./MakerV2Base.sol"; -import "./IUniswapExchange.sol"; -import "./IUniswapFactory.sol"; - -/** - * @title MakerV2Loan - * @notice Module to migrate old CDPs and open and manage new vaults. The vaults managed by - * this module are directly owned by the module. This is to prevent a compromised wallet owner - * from being able to use `TransferManager.callContract()` to transfer ownership of a vault - * (a type of asset NOT protected by a wallet's daily limit) to another account. - * @author Olivier VDB - - */ -abstract contract MakerV2Loan is MakerV2Base { - - bytes4 private constant IS_NEW_VERSION = bytes4(keccak256("isNewVersion(address)")); - - // The address of the MKR token - GemLike internal mkrToken; - // The address of the WETH token - GemLike internal wethToken; - // The address of the WETH Adapter - JoinLike internal wethJoin; - // The address of the Jug - JugLike internal jug; - // The address of the Vault Manager (referred to as 'CdpManager' to match Maker's naming) - ManagerLike internal cdpManager; - // The address of the SCD Tub - SaiTubLike internal tub; - // The Maker Registry in which all supported collateral tokens and their adapters are stored - IMakerRegistry internal makerRegistry; - // The Uniswap Exchange contract for DAI - IUniswapExchange internal daiUniswap; - // The Uniswap Exchange contract for MKR - IUniswapExchange internal mkrUniswap; - // Mapping [wallet][ilk] -> loanId, that keeps track of cdp owners - // while also enforcing a maximum of one loan per token (ilk) and per wallet - // (which will make future upgrades of the module easier) - mapping(address => mapping(bytes32 => bytes32)) public loanIds; - // Lock used by nonReentrant() - bool private _notEntered = true; - - // ****************** Events *************************** // - - // Vault management events - event LoanOpened( - address indexed _wallet, - bytes32 indexed _loanId, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ); - event LoanAcquired(address indexed _wallet, bytes32 indexed _loanId); - event LoanClosed(address indexed _wallet, bytes32 indexed _loanId); - event CollateralAdded(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event CollateralRemoved(address indexed _wallet, bytes32 indexed _loanId, address _collateral, uint256 _collateralAmount); - event DebtAdded(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - event DebtRemoved(address indexed _wallet, bytes32 indexed _loanId, address _debtToken, uint256 _debtAmount); - - - // *************** Modifiers *************************** // - - /** - * @notice Prevents call reentrancy - */ - modifier nonReentrant() { - require(_notEntered, "MV2: reentrant call"); - _notEntered = false; - _; - _notEntered = true; - } - - modifier onlyNewVersion() { - (bool success, bytes memory res) = msg.sender.call(abi.encodeWithSignature("isNewVersion(address)", address(this))); - require(success && abi.decode(res, (bytes4)) == IS_NEW_VERSION , "MV2: not a new version"); - _; - } - - // *************** Constructor ********************** // - - constructor( - JugLike _jug, - IMakerRegistry _makerRegistry, - IUniswapFactory _uniswapFactory - ) - public - { - cdpManager = ScdMcdMigrationLike(scdMcdMigration).cdpManager(); - tub = ScdMcdMigrationLike(scdMcdMigration).tub(); - wethJoin = ScdMcdMigrationLike(scdMcdMigration).wethJoin(); - wethToken = wethJoin.gem(); - mkrToken = tub.gov(); - jug = _jug; - makerRegistry = _makerRegistry; - daiUniswap = IUniswapExchange(_uniswapFactory.getExchange(address(daiToken))); - mkrUniswap = IUniswapExchange(_uniswapFactory.getExchange(address(mkrToken))); - // Authorize daiJoin to exit DAI from the module's internal balance in the vat - vat.hope(address(daiJoin)); - } - - // *************** External/Public Functions ********************* // - - /* ********************************** Implementation of Loan ************************************* */ - - /** - * @notice Opens a collateralized loan. - * @param _wallet The target wallet. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral token provided. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of tokens borrowed. - * @return _loanId The ID of the created vault. - */ - function openLoan( - address _wallet, - address _collateral, - uint256 _collateralAmount, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - returns (bytes32 _loanId) - { - verifySupportedCollateral(_collateral); - require(_debtToken == address(daiToken), "MV2: debt token not DAI"); - _loanId = bytes32(openVault(_wallet, _collateral, _collateralAmount, _debtAmount)); - emit LoanOpened(_wallet, _loanId, _collateral, _collateralAmount, _debtToken, _debtAmount); - } - - /** - * @notice Adds collateral to a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to add. - */ - function addCollateral( - address _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - addCollateral(_wallet, uint256(_loanId), _collateralAmount); - emit CollateralAdded(_wallet, _loanId, _collateral, _collateralAmount); - } - - /** - * @notice Removes collateral from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _collateral The token used as a collateral. - * @param _collateralAmount The amount of collateral to remove. - */ - function removeCollateral( - address _wallet, - bytes32 _loanId, - address _collateral, - uint256 _collateralAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - removeCollateral(_wallet, uint256(_loanId), _collateralAmount); - emit CollateralRemoved(_wallet, _loanId, _collateral, _collateralAmount); - } - - /** - * @notice Increases the debt by borrowing more token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _debtToken The token borrowed (must be the address of the DAI contract). - * @param _debtAmount The amount of token to borrow. - */ - function addDebt( - address _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - addDebt(_wallet, uint256(_loanId), _debtAmount); - emit DebtAdded(_wallet, _loanId, _debtToken, _debtAmount); - } - - /** - * @notice Decreases the debt by repaying some token from a loan identified by its ID. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - * @param _debtToken The token to repay (must be the address of the DAI contract). - * @param _debtAmount The amount of token to repay. - */ - function removeDebt( - address _wallet, - bytes32 _loanId, - address _debtToken, - uint256 _debtAmount - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - updateStabilityFee(uint256(_loanId)); - removeDebt(_wallet, uint256(_loanId), _debtAmount); - emit DebtRemoved(_wallet, _loanId, _debtToken, _debtAmount); - } - - /** - * @notice Closes a collateralized loan by repaying all debts (plus interest) and redeeming all collateral. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function closeLoan( - address _wallet, - bytes32 _loanId - ) - external - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - updateStabilityFee(uint256(_loanId)); - closeVault(_wallet, uint256(_loanId)); - emit LoanClosed(_wallet, _loanId); - } - - /* *************************************** Other vault methods ***************************************** */ - - /** - * @notice Lets a vault owner transfer their vault from their wallet to the present module so the vault - * can be managed by the module. - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function acquireLoan( - address _wallet, - bytes32 _loanId - ) - external - nonReentrant - onlyWalletOwnerOrFeature(_wallet) - onlyWhenUnlocked(_wallet) - { - require(cdpManager.owns(uint256(_loanId)) == _wallet, "MV2: wrong vault owner"); - // Transfer the vault from the wallet to the module - invokeWallet( - _wallet, - address(cdpManager), - 0, - abi.encodeWithSignature("give(uint256,address)", uint256(_loanId), address(this)) - ); - require(cdpManager.owns(uint256(_loanId)) == address(this), "MV2: failed give"); - // Mark the incoming vault as belonging to the wallet (or merge it into the existing vault if there is one) - assignLoanToWallet(_wallet, _loanId); - emit LoanAcquired(_wallet, _loanId); - } - - /** - * @notice Lets a future upgrade of this module transfer a vault to itself - * @param _wallet The target wallet. - * @param _loanId The ID of the target vault. - */ - function giveVault( - address _wallet, - bytes32 _loanId - ) - external - onlyWalletFeature(_wallet) - onlyNewVersion - onlyWhenUnlocked(_wallet) - { - verifyLoanOwner(_wallet, _loanId); - cdpManager.give(uint256(_loanId), msg.sender); - clearLoanOwner(_wallet, _loanId); - } - - /* ************************************** Internal Functions ************************************** */ - - function toInt(uint256 _x) internal pure returns (int _y) { - _y = int(_x); - require(_y >= 0, "MV2: int overflow"); - } - - function assignLoanToWallet(address _wallet, bytes32 _loanId) internal returns (bytes32 _assignedLoanId) { - bytes32 ilk = cdpManager.ilks(uint256(_loanId)); - // Check if the user already holds a vault in the MakerV2Manager - bytes32 existingLoanId = loanIds[_wallet][ilk]; - if (existingLoanId > 0) { - // Merge the new loan into the existing loan - cdpManager.shift(uint256(_loanId), uint256(existingLoanId)); - return existingLoanId; - } - // Record the new vault as belonging to the wallet - loanIds[_wallet][ilk] = _loanId; - return _loanId; - } - - function clearLoanOwner(address _wallet, bytes32 _loanId) internal { - delete loanIds[_wallet][cdpManager.ilks(uint256(_loanId))]; - } - - function verifyLoanOwner(address _wallet, bytes32 _loanId) internal view { - require(loanIds[_wallet][cdpManager.ilks(uint256(_loanId))] == _loanId, "MV2: unauthorized loanId"); - } - - function verifySupportedCollateral(address _collateral) internal view { - if (_collateral != ETH_TOKEN) { - (bool collateralSupported,,,) = makerRegistry.collaterals(_collateral); - require(collateralSupported, "MV2: unsupported collateral"); - } - } - - function joinCollateral( - address _wallet, - uint256 _cdpId, - uint256 _collateralAmount, - bytes32 _ilk - ) - internal - { - // Get the adapter and collateral token for the vault - (JoinLike gemJoin, GemLike collateral) = makerRegistry.getCollateral(_ilk); - // Convert ETH to WETH if needed - if (gemJoin == wethJoin) { - invokeWallet(_wallet, address(wethToken), _collateralAmount, abi.encodeWithSignature("deposit()")); - } - // Send the collateral to the module - invokeWallet( - _wallet, - address(collateral), - 0, - abi.encodeWithSignature("transfer(address,uint256)", address(this), _collateralAmount) - ); - // Approve the adapter to pull the collateral from the module - collateral.approve(address(gemJoin), _collateralAmount); - // Join collateral to the adapter. The first argument to `join` is the address that *technically* owns the vault - gemJoin.join(cdpManager.urns(_cdpId), _collateralAmount); - } - - function joinDebt( - address _wallet, - uint256 _cdpId, - uint256 _debtAmount // art.mul(rate).div(RAY) === [wad]*[ray]/[ray]=[wad] - ) - internal - { - // Send the DAI to the module - invokeWallet( - _wallet, - address(daiToken), - 0, - abi.encodeWithSignature("transfer(address,uint256)", address(this), _debtAmount) - ); - // Approve the DAI adapter to burn DAI from the module - daiToken.approve(address(daiJoin), _debtAmount); - // Join DAI to the adapter. The first argument to `join` is the address that *technically* owns the vault - // To avoid rounding issues, we substract one wei to the amount joined - daiJoin.join(cdpManager.urns(_cdpId), _debtAmount.sub(1)); - } - - function drawAndExitDebt( - address _wallet, - uint256 _cdpId, - uint256 _debtAmount, - uint256 _collateralAmount, - bytes32 _ilk - ) - internal - { - // Get the accumulated rate for the collateral type - (, uint rate,,,) = vat.ilks(_ilk); - // Express the debt in the RAD units used internally by the vat - uint daiDebtInRad = _debtAmount.mul(RAY); - // Lock the collateral and draw the debt. To avoid rounding issues we add an extra wei of debt - cdpManager.frob(_cdpId, toInt(_collateralAmount), toInt(daiDebtInRad.div(rate) + 1)); - // Transfer the (internal) DAI debt from the cdp's urn to the module. - cdpManager.move(_cdpId, address(this), daiDebtInRad); - // Mint the DAI token and exit it to the user's wallet - daiJoin.exit(_wallet, _debtAmount); - } - - function updateStabilityFee( - uint256 _cdpId - ) - internal - { - jug.drip(cdpManager.ilks(_cdpId)); - } - - function debt( - uint256 _cdpId - ) - internal - view - returns (uint256 _fullRepayment, uint256 _maxNonFullRepayment) - { - bytes32 ilk = cdpManager.ilks(_cdpId); - (, uint256 art) = vat.urns(ilk, cdpManager.urns(_cdpId)); - if (art > 0) { - (, uint rate,,, uint dust) = vat.ilks(ilk); - _maxNonFullRepayment = art.mul(rate).sub(dust).div(RAY); - _fullRepayment = art.mul(rate).div(RAY) - .add(1) // the amount approved is 1 wei more than the amount repaid, to avoid rounding issues - .add(art-art.mul(rate).div(RAY).mul(RAY).div(rate)); // adding 1 extra wei if further rounding issues are expected - } - } - - function collateral( - uint256 _cdpId - ) - internal - view - returns (uint256 _collateralAmount) - { - (_collateralAmount,) = vat.urns(cdpManager.ilks(_cdpId), cdpManager.urns(_cdpId)); - } - - function verifyValidRepayment( - uint256 _cdpId, - uint256 _debtAmount - ) - internal - view - { - (uint256 fullRepayment, uint256 maxRepayment) = debt(_cdpId); - require(_debtAmount <= maxRepayment || _debtAmount == fullRepayment, "MV2: repay less or full"); - } - - /** - * @notice Lets the owner of a wallet open a new vault. The owner must have enough collateral - * in their wallet. - * @param _wallet The target wallet - * @param _collateral The token to use as collateral in the vault. - * @param _collateralAmount The amount of collateral to lock in the vault. - * @param _debtAmount The amount of DAI to draw from the vault - * @return _cdpId The id of the created vault. - */ - function openVault( - address _wallet, - address _collateral, - uint256 _collateralAmount, - uint256 _debtAmount - ) - internal - returns (uint256 _cdpId) - { - // Continue with WETH as collateral instead of ETH if needed - if (_collateral == ETH_TOKEN) { - _collateral = address(wethToken); - } - // Get the ilk for the collateral - bytes32 ilk = makerRegistry.getIlk(_collateral); - // Open a vault if there isn't already one for the collateral type (the vault owner will effectively be the module) - _cdpId = uint256(loanIds[_wallet][ilk]); - if (_cdpId == 0) { - _cdpId = cdpManager.open(ilk, address(this)); - // Mark the vault as belonging to the wallet - loanIds[_wallet][ilk] = bytes32(_cdpId); - } - // Move the collateral from the wallet to the vat - joinCollateral(_wallet, _cdpId, _collateralAmount, ilk); - // Draw the debt and exit it to the wallet - if (_debtAmount > 0) { - drawAndExitDebt(_wallet, _cdpId, _debtAmount, _collateralAmount, ilk); - } - } - - /** - * @notice Lets the owner of a vault add more collateral to their vault. The owner must have enough of the - * collateral token in their wallet. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _collateralAmount The amount of collateral to add to the vault. - */ - function addCollateral( - address _wallet, - uint256 _cdpId, - uint256 _collateralAmount - ) - internal - { - // Move the collateral from the wallet to the vat - joinCollateral(_wallet, _cdpId, _collateralAmount, cdpManager.ilks(_cdpId)); - // Lock the collateral - cdpManager.frob(_cdpId, toInt(_collateralAmount), 0); - } - - /** - * @notice Lets the owner of a vault remove some collateral from their vault - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _collateralAmount The amount of collateral to remove from the vault. - */ - function removeCollateral( - address _wallet, - uint256 _cdpId, - uint256 _collateralAmount - ) - internal - { - // Unlock the collateral - cdpManager.frob(_cdpId, -toInt(_collateralAmount), 0); - // Transfer the (internal) collateral from the cdp's urn to the module. - cdpManager.flux(_cdpId, address(this), _collateralAmount); - // Get the adapter for the collateral - (JoinLike gemJoin,) = makerRegistry.getCollateral(cdpManager.ilks(_cdpId)); - // Exit the collateral from the adapter. - gemJoin.exit(_wallet, _collateralAmount); - // Convert WETH to ETH if needed - if (gemJoin == wethJoin) { - invokeWallet(_wallet, address(wethToken), 0, abi.encodeWithSignature("withdraw(uint256)", _collateralAmount)); - } - } - - /** - * @notice Lets the owner of a vault draw more DAI from their vault. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _amount The amount of additional DAI to draw from the vault. - */ - function addDebt( - address _wallet, - uint256 _cdpId, - uint256 _amount - ) - internal - { - // Draw and exit the debt to the wallet - drawAndExitDebt(_wallet, _cdpId, _amount, 0, cdpManager.ilks(_cdpId)); - } - - /** - * @notice Lets the owner of a vault partially repay their debt. The repayment is made up of - * the outstanding DAI debt plus the DAI stability fee. - * The method will use the user's DAI tokens in priority and will, if needed, convert the required - * amount of ETH to cover for any missing DAI tokens. - * @param _wallet The target wallet - * @param _cdpId The id of the vault. - * @param _amount The amount of DAI debt to repay. - */ - function removeDebt( - address _wallet, - uint256 _cdpId, - uint256 _amount - ) - internal - { - verifyValidRepayment(_cdpId, _amount); - // Move the DAI from the wallet to the vat. - joinDebt(_wallet, _cdpId, _amount); - // Get the accumulated rate for the collateral type - (, uint rate,,,) = vat.ilks(cdpManager.ilks(_cdpId)); - // Repay the debt. To avoid rounding issues we reduce the repayment by one wei - cdpManager.frob(_cdpId, 0, -toInt(_amount.sub(1).mul(RAY).div(rate))); - } - - /** - * @notice Lets the owner of a vault close their vault. The method will: - * 1) repay all debt and fee - * 2) free all collateral - * @param _wallet The target wallet - * @param _cdpId The id of the CDP. - */ - function closeVault( - address _wallet, - uint256 _cdpId - ) - internal - { - (uint256 fullRepayment,) = debt(_cdpId); - // Repay the debt - if (fullRepayment > 0) { - removeDebt(_wallet, _cdpId, fullRepayment); - } - // Remove the collateral - uint256 ink = collateral(_cdpId); - if (ink > 0) { - removeCollateral(_wallet, _cdpId, ink); - } - } - -} \ No newline at end of file diff --git a/contracts/modules/maker/MakerV2Manager.sol b/contracts/modules/maker/MakerV2Manager.sol deleted file mode 100644 index d2f5d0eda..000000000 --- a/contracts/modules/maker/MakerV2Manager.sol +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2019 Argent Labs Ltd. - -// 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 . - -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; - -import "./MakerV2Base.sol"; -import "./MakerV2Invest.sol"; -import "./MakerV2Loan.sol"; - -/** - * @title MakerV2Manager - * @notice Module to lock/unlock MCD DAI into/from Maker's Pot, - * migrate old CDPs and open and manage new CDPs. - * @author Olivier VDB - - */ -contract MakerV2Manager is MakerV2Base, MakerV2Invest, MakerV2Loan { - - // *************** Constructor ********************** // - - constructor( - ILockStorage _lockStorage, - ScdMcdMigrationLike _scdMcdMigration, - PotLike _pot, - JugLike _jug, - IMakerRegistry _makerRegistry, - IUniswapFactory _uniswapFactory, - IVersionManager _versionManager - ) - MakerV2Base(_lockStorage, _scdMcdMigration, _versionManager) - MakerV2Invest(_pot) - MakerV2Loan(_jug, _makerRegistry, _uniswapFactory) - public - { - } - -} \ No newline at end of file diff --git a/contracts/modules/mocks/ArgentModuleTest.sol b/contracts/modules/mocks/ArgentModuleTest.sol new file mode 100644 index 000000000..5f212313c --- /dev/null +++ b/contracts/modules/mocks/ArgentModuleTest.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.3; + +import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol"; +import "../ArgentModule.sol"; + +/** + * @notice Extends the ArgentModule to get the creation code of uniswap pairs locally + * and enable ERC20 refunds in tests. + */ +contract ArgentModuleTest is ArgentModule { + + bytes32 internal creationCode; + + constructor ( + IModuleRegistry _registry, + IGuardianStorage _guardianStorage, + ITransferStorage _userWhitelist, + IAuthoriser _authoriser, + address _uniswapRouter, + uint256 _securityPeriod, + uint256 _securityWindow, + uint256 _recoveryPeriod, + uint256 _lockPeriod + ) + ArgentModule( + _registry, + _guardianStorage, + _userWhitelist, + _authoriser, + _uniswapRouter, + _securityPeriod, + _securityWindow, + _recoveryPeriod, + _lockPeriod) + { + address uniswapV2Factory = IUniswapV2Router01(_uniswapRouter).factory(); + (bool success, bytes memory _res) = uniswapV2Factory.staticcall(abi.encodeWithSignature("getKeccakOfPairCreationCode()")); + if (success) { + creationCode = abi.decode(_res, (bytes32)); + } + } + function getPairForSorted(address tokenA, address tokenB) internal override view returns (address pair) { + pair = address(uint160(uint256(keccak256(abi.encodePacked( + hex'ff', + uniswapV2Factory, + keccak256(abi.encodePacked(tokenA, tokenB)), + creationCode + ))))); + } +} \ No newline at end of file diff --git a/contracts/wallet/BaseWallet.sol b/contracts/wallet/BaseWallet.sol index 1de6121fa..c3ea1cb41 100644 --- a/contracts/wallet/BaseWallet.sol +++ b/contracts/wallet/BaseWallet.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; import "../modules/common/IModule.sol"; import "./IWallet.sol"; @@ -26,19 +26,16 @@ import "./IWallet.sol"; */ contract BaseWallet is IWallet { - // The implementation of the proxy - address public implementation; // The owner address public override owner; // The authorised modules mapping (address => bool) public override authorised; - // The enabled static calls - mapping (bytes4 => address) public override enabled; + // module executing static calls + address public staticCallExecutor; // The number of modules uint public override modules; event AuthorisedModule(address indexed module, bool value); - event EnabledStaticCall(address indexed module, bytes4 indexed method); event Invoked(address indexed module, address indexed target, uint indexed value, bytes data); event Received(uint indexed value, address indexed sender, bytes data); event OwnerChanged(address owner); @@ -47,7 +44,7 @@ contract BaseWallet is IWallet { * @notice Throws if the sender is not an authorised module. */ modifier moduleOnly { - require(authorised[msg.sender], "BW: msg.sender not an authorized module"); + require(authorised[msg.sender], "BW: sender not authorized"); _; } @@ -58,7 +55,7 @@ contract BaseWallet is IWallet { */ function init(address _owner, address[] calldata _modules) external { require(owner == address(0) && modules == 0, "BW: wallet already initialised"); - require(_modules.length > 0, "BW: construction requires at least 1 module"); + require(_modules.length > 0, "BW: empty modules"); owner = _owner; modules = _modules.length; for (uint256 i = 0; i < _modules.length; i++) { @@ -84,7 +81,7 @@ contract BaseWallet is IWallet { IModule(_module).init(address(this)); } else { modules -= 1; - require(modules > 0, "BW: wallet must have at least one module"); + require(modules > 0, "BW: cannot remove last module"); delete authorised[_module]; } } @@ -93,10 +90,22 @@ contract BaseWallet is IWallet { /** * @inheritdoc IWallet */ - function enableStaticCall(address _module, bytes4 _method) external override moduleOnly { - require(authorised[_module], "BW: must be an authorised module for static call"); - enabled[_method] = _module; - emit EnabledStaticCall(_module, _method); + function enabled(bytes4 _sig) public view override returns (address) { + address executor = staticCallExecutor; + if(executor != address(0) && IModule(executor).supportsStaticCall(_sig)) { + return executor; + } + return address(0); + } + + /** + * @inheritdoc IWallet + */ + function enableStaticCall(address _module, bytes4 /* _method */) external override moduleOnly { + if(staticCallExecutor != _module) { + require(authorised[_module], "BW: unauthorized executor"); + staticCallExecutor = _module; + } } /** @@ -132,11 +141,11 @@ contract BaseWallet is IWallet { * to an enabled module, or logs the call otherwise. */ fallback() external payable { - address module = enabled[msg.sig]; + address module = enabled(msg.sig); if (module == address(0)) { emit Received(msg.value, msg.sender, msg.data); } else { - require(authorised[module], "BW: must be an authorised module for static call"); + require(authorised[module], "BW: unauthorised module"); // solhint-disable-next-line no-inline-assembly assembly { diff --git a/contracts/wallet/IWallet.sol b/contracts/wallet/IWallet.sol index 5428edb80..d3c228a60 100644 --- a/contracts/wallet/IWallet.sol +++ b/contracts/wallet/IWallet.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; /** * @title IWallet diff --git a/contracts/wallet/Proxy.sol b/contracts/wallet/Proxy.sol index 7646d7a92..2bfd65727 100644 --- a/contracts/wallet/Proxy.sol +++ b/contracts/wallet/Proxy.sol @@ -14,7 +14,7 @@ // along with this program. If not, see . // SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.6.12; +pragma solidity ^0.8.3; /** * @title Proxy @@ -24,18 +24,18 @@ pragma solidity ^0.6.12; */ contract Proxy { - address implementation; + address immutable public implementation; event Received(uint indexed value, address indexed sender, bytes data); - constructor(address _implementation) public { + constructor(address _implementation) { implementation = _implementation; } fallback() external payable { + address target = implementation; // solhint-disable-next-line no-inline-assembly assembly { - let target := sload(0) calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) @@ -46,6 +46,6 @@ contract Proxy { } receive() external payable { - emit Received(msg.value, msg.sender, msg.data); + emit Received(msg.value, msg.sender, ""); } } \ No newline at end of file diff --git a/deployment/0_limited_test.js b/deployment/0_limited_test.js index c741b7f11..e1be831d8 100644 --- a/deployment/0_limited_test.js +++ b/deployment/0_limited_test.js @@ -30,7 +30,7 @@ async function main() { const stateValue = await testContractWrapper.state(); expect(stateValue).to.eq.BN(99); - console.log("## completed deployment script 7 ##"); + console.log("## completed deployment script 0 ##"); } // For truffle exec diff --git a/deployment/1_setup_test_environment.js b/deployment/1_setup_test_environment.js index ad6695a6d..3d34e3166 100644 --- a/deployment/1_setup_test_environment.js +++ b/deployment/1_setup_test_environment.js @@ -1,6 +1,7 @@ /* global artifacts */ global.web3 = web3; +global.artifacts = artifacts; const ENSRegistry = artifacts.require("ENSRegistry"); const ENSRegistryWithFallback = artifacts.require("ENSRegistryWithFallback"); @@ -8,17 +9,24 @@ const UniswapFactory = artifacts.require("../lib/uniswap/UniswapFactory"); const UniswapExchange = artifacts.require("../lib/uniswap/UniswapExchange"); const MakerMigration = artifacts.require("MockScdMcdMigration"); +// Uniswap V2 +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniZap = artifacts.require("UniZap"); + // Paraswap -const AugustusSwapper = artifacts.require("AugustusSwapper"); +const AugustusSwapper = artifacts.require("AugustusSwapperMock"); const Whitelisted = artifacts.require("Whitelisted"); const PartnerRegistry = artifacts.require("PartnerRegistry"); const PartnerDeployer = artifacts.require("PartnerDeployer"); -const KyberAdapter = artifacts.require("Kyber"); +const Uniswap = artifacts.require("Uniswap"); +const UniswapProxy = artifacts.require("UniswapProxy"); -const utils = require("../utils/utilities.js"); +const { namehash, sha3 } = require("../utils/utilities.js"); const deployManager = require("../utils/deploy-manager.js"); const BYTES32_NULL = "0x0000000000000000000000000000000000000000000000000000000000000000"; +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; // For development purpose async function deployENSRegistry(owner, domain) { @@ -33,29 +41,33 @@ async function deployENSRegistry(owner, domain) { // Create the 'eth' and 'xyz' namespaces console.log(`Setting Subnode Owner for ${extension}`); - await ENSWrapper.setSubnodeOwner(BYTES32_NULL, utils.sha3(extension), owner); + await ENSWrapper.setSubnodeOwner(BYTES32_NULL, sha3(extension), owner); // Create the 'argentx.xyz' wallet ENS namespace console.log(`Setting Subnode Owner for ${domainName}.${extension}`); - await ENSWrapper.setSubnodeOwner(utils.namehash(extension), utils.sha3(domainName), owner); + await ENSWrapper.setSubnodeOwner(namehash(extension), sha3(domainName), owner); return ENSWrapper.address; } async function deployParaswap(deploymentAccount) { - const whitelist = await Whitelisted.new(); + const uniswapProxy = await UniswapProxy.new(); + const paraswapWhitelist = await Whitelisted.new(); const partnerDeployer = await PartnerDeployer.new(); const partnerRegistry = await PartnerRegistry.new(partnerDeployer.address); - const paraswap = await AugustusSwapper.new( - whitelist.address, - deploymentAccount, + const paraswap = await AugustusSwapper.new(); + await paraswap.initialize( + paraswapWhitelist.address, + ZERO_ADDRESS, partnerRegistry.address, deploymentAccount, - deploymentAccount, + uniswapProxy.address, ); - const kyberAdapter = await KyberAdapter.new(deploymentAccount); - await whitelist.addWhitelisted(kyberAdapter.address); - return { paraswap: paraswap.address, kyberAdapter: kyberAdapter.address }; + const uniAdapter = await Uniswap.new(); + const wlr = await paraswapWhitelist.WHITELISTED_ROLE(); + await paraswapWhitelist.grantRole(wlr, uniAdapter.address); + + return { contract: paraswap.address, uniswapProxy: uniswapProxy.address, uniAdapter: uniAdapter.address }; } async function main() { @@ -69,15 +81,22 @@ async function main() { } if (config.defi.paraswap.deployOwn) { - const { paraswap, kyberAdapter } = await deployParaswap(deploymentAccount); - configurator.updateParaswap(paraswap, { Kyber: kyberAdapter }); + const { contract, uniswapProxy, uniAdapter } = await deployParaswap(deploymentAccount); + configurator.updateParaswap(contract, uniswapProxy, { uniswap: uniAdapter }, []); } if (config.defi.uniswap.deployOwn) { + // uniswap V1 const UniswapFactoryWrapper = await UniswapFactory.new(); configurator.updateUniswapFactory(UniswapFactoryWrapper.address); const UniswapExchangeTemplateWrapper = await UniswapExchange.new(); await UniswapFactoryWrapper.initializeFactory(UniswapExchangeTemplateWrapper.address); + // Uniswap V2 + const UniswapV2FactoryWrapper = await UniswapV2Factory.new(ZERO_ADDRESS); + const UniswapV2RouterWrapper = await UniswapV2Router01.new(UniswapV2FactoryWrapper.address, ZERO_ADDRESS); + const UniZapWrapper = await UniZap.new(UniswapV2FactoryWrapper.address, UniswapV2RouterWrapper.address, ZERO_ADDRESS); + const initCode = await UniswapV2FactoryWrapper.getKeccakOfPairCreationCode(); + configurator.updateUniswapV2(UniswapV2FactoryWrapper.address, UniswapV2RouterWrapper.address, UniZapWrapper.address, initCode); } if (config.defi.maker.deployOwn) { diff --git a/deployment/2_deploy_contracts.js b/deployment/2_deploy_contracts.js index 71e614250..5728006f9 100644 --- a/deployment/2_deploy_contracts.js +++ b/deployment/2_deploy_contracts.js @@ -1,25 +1,19 @@ /* global artifacts */ global.web3 = web3; +global.artifacts = artifacts; const GuardianStorage = artifacts.require("GuardianStorage"); const TransferStorage = artifacts.require("TransferStorage"); -const LockStorage = artifacts.require("LockStorage"); -const LimitStorage = artifacts.require("LimitStorage"); const BaseWallet = artifacts.require("BaseWallet"); const ModuleRegistry = artifacts.require("ModuleRegistry"); -const CompoundRegistry = artifacts.require("CompoundRegistry"); +const DappRegistry = artifacts.require("DappRegistry"); const MultiSig = artifacts.require("MultiSigWallet"); const ENS = artifacts.require("ENSRegistryWithFallback"); const ENSManager = artifacts.require("ArgentENSManager"); const ENSResolver = artifacts.require("ArgentENSResolver"); const WalletFactory = artifacts.require("WalletFactory"); - -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const DexRegistry = artifacts.require("DexRegistry"); - -const MakerRegistry = artifacts.require("MakerRegistry"); -const ScdMcdMigration = artifacts.require("ScdMcdMigration"); +const ArgentWalletDetector = artifacts.require("ArgentWalletDetector"); const utils = require("../utils/utilities.js"); const deployManager = require("../utils/deploy-manager.js"); @@ -40,10 +34,6 @@ async function main() { const GuardianStorageWrapper = await GuardianStorage.new(); // Deploy the Transfer Storage const TransferStorageWrapper = await TransferStorage.new(); - // Deploy the new LockStorage - const LockStorageWrapper = await LockStorage.new(); - // Deploy the new LimitStorage - const LimitStorageWrapper = await LimitStorage.new(); // ////////////////////////////////// // Deploy infrastructure contracts @@ -54,15 +44,9 @@ async function main() { // Deploy the MultiSig const MultiSigWrapper = await MultiSig.new(newConfig.multisig.threshold, newConfig.multisig.owners); - // Deploy the new TokenPriceRegistry - const TokenPriceRegistryWrapper = await TokenPriceRegistry.new(); - // Deploy the DexRegistry - const DexRegistryWrapper = await DexRegistry.new(); - // Deploy Module Registry const ModuleRegistryWrapper = await ModuleRegistry.new(); - // Deploy Compound Registry - const CompoundRegistryWrapper = await CompoundRegistry.new(); + const DappRegistryWrapper = await DappRegistry.new(newConfig.settings.timelockPeriod); // Deploy the ENS Resolver const ENSResolverWrapper = await ENSResolver.new(); // Deploy the ENS Manager @@ -70,17 +54,11 @@ async function main() { walletRootEns, utils.namehash(walletRootEns), newConfig.ENS.ensRegistry, ENSResolverWrapper.address); // Deploy the Wallet Factory const WalletFactoryWrapper = await WalletFactory.new( - ModuleRegistryWrapper.address, BaseWalletWrapper.address, GuardianStorageWrapper.address, prevConfig.backend.refundCollector); - - // Deploy and configure Maker Registry - const ScdMcdMigrationWrapper = await ScdMcdMigration.at(newConfig.defi.maker.migration); - const vatAddress = await ScdMcdMigrationWrapper.vat(); - const MakerRegistryWrapper = await MakerRegistry.new(vatAddress); - const wethJoinAddress = await ScdMcdMigrationWrapper.wethJoin(); - console.log(`Adding join adapter ${wethJoinAddress} to the MakerRegistry`); - await MakerRegistryWrapper.addCollateral(wethJoinAddress); - console.log("Set the MultiSig as the owner of the MakerRegistry"); - await MakerRegistryWrapper.changeOwner(newConfig.contracts.MultiSigWallet); + BaseWalletWrapper.address, GuardianStorageWrapper.address, prevConfig.backend.refundCollector); + + // Deploy ArgentWalletDetector contract + console.log("Deploying ArgentWalletDetector..."); + const ArgentWalletDetectorWrapper = await ArgentWalletDetector.new([], []); // ///////////////////////////////////////////////// // Making ENSManager owner of the root wallet ENS @@ -108,35 +86,22 @@ async function main() { throw new Error(`Ownership of ${walletRootEns} not changed`); } - // ///////////////////////////////////////////////// - // Add token to the Compound Registry - // ///////////////////////////////////////////////// - - for (const underlying in newConfig.defi.compound.markets) { - const cToken = newConfig.defi.compound.markets[underlying]; - console.log(`Adding unerlying ${underlying} with cToken ${cToken} to the registry`); - await CompoundRegistryWrapper.addCToken(underlying, cToken); - } - // ///////////////////////////////////////////////// // Update config and Upload ABIs // ///////////////////////////////////////////////// configurator.updateModuleAddresses({ GuardianStorage: GuardianStorageWrapper.address, TransferStorage: TransferStorageWrapper.address, - LockStorage: LockStorageWrapper.address, - LimitStorage: LimitStorageWrapper.address, - TokenPriceRegistry: TokenPriceRegistryWrapper.address, }); configurator.updateInfrastructureAddresses({ MultiSigWallet: MultiSigWrapper.address, WalletFactory: WalletFactoryWrapper.address, + ArgentWalletDetector: ArgentWalletDetectorWrapper.address, ENSResolver: ENSResolverWrapper.address, ENSManager: ENSManagerWrapper.address, ModuleRegistry: ModuleRegistryWrapper.address, - CompoundRegistry: CompoundRegistryWrapper.address, - DexRegistry: DexRegistryWrapper.address, + DappRegistry: DappRegistryWrapper.address, BaseWallet: BaseWalletWrapper.address, }); await configurator.save(); @@ -144,16 +109,13 @@ async function main() { await Promise.all([ abiUploader.upload(GuardianStorageWrapper, "modules"), abiUploader.upload(TransferStorageWrapper, "modules"), - abiUploader.upload(LockStorageWrapper, "modules"), - abiUploader.upload(LimitStorageWrapper, "modules"), - abiUploader.upload(TokenPriceRegistryWrapper, "modules"), abiUploader.upload(MultiSigWrapper, "contracts"), abiUploader.upload(WalletFactoryWrapper, "contracts"), + abiUploader.upload(ArgentWalletDetectorWrapper, "contracts"), abiUploader.upload(ENSResolverWrapper, "contracts"), abiUploader.upload(ENSManagerWrapper, "contracts"), abiUploader.upload(ModuleRegistryWrapper, "contracts"), - abiUploader.upload(CompoundRegistryWrapper, "contracts"), - abiUploader.upload(DexRegistryWrapper, "contracts"), + abiUploader.upload(DappRegistryWrapper, "contracts"), abiUploader.upload(BaseWalletWrapper, "contracts"), ]); diff --git a/deployment/3_setup_contracts.js b/deployment/3_setup_contracts.js index 2803ffe9b..02772bafa 100644 --- a/deployment/3_setup_contracts.js +++ b/deployment/3_setup_contracts.js @@ -5,9 +5,7 @@ const ModuleRegistry = artifacts.require("ModuleRegistry"); const ENSManager = artifacts.require("ArgentENSManager"); const ENSResolver = artifacts.require("ArgentENSResolver"); const WalletFactory = artifacts.require("WalletFactory"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const CompoundRegistry = artifacts.require("CompoundRegistry"); -const DexRegistry = artifacts.require("DexRegistry"); +const ArgentWalletDetector = artifacts.require("ArgentWalletDetector"); const deployManager = require("../utils/deploy-manager.js"); @@ -19,14 +17,7 @@ async function main() { const ENSManagerWrapper = await ENSManager.at(config.contracts.ENSManager); const WalletFactoryWrapper = await WalletFactory.at(config.contracts.WalletFactory); const ModuleRegistryWrapper = await ModuleRegistry.at(config.contracts.ModuleRegistry); - const CompoundRegistryWrapper = await CompoundRegistry.at(config.contracts.CompoundRegistry); - const TokenPriceRegistryWrapper = await TokenPriceRegistry.at(config.modules.TokenPriceRegistry); - const DexRegistryWrapper = await DexRegistry.at(config.contracts.DexRegistry); - - // Configure DexRegistry - const authorisedExchanges = Object.values(config.defi.paraswap.authorisedExchanges); - console.log("Setting up DexRegistry"); - await DexRegistryWrapper.setAuthorised(authorisedExchanges, Array(authorisedExchanges.length).fill(true)); + const ArgentWalletDetectorWrapper = await ArgentWalletDetector.at(config.contracts.ArgentWalletDetector); // ////////////////////////////////// // Set contracts' managers @@ -45,9 +36,6 @@ async function main() { const account = config.backend.accounts[idx]; console.log(`Set ${account} as the manager of the WalletFactory`); await WalletFactoryWrapper.addManager(account); - - console.log(`Set ${account} as the manager of the TokenPriceRegistry`); - await TokenPriceRegistryWrapper.addManager(account); } // ////////////////////////////////// @@ -59,9 +47,7 @@ async function main() { ENSManagerWrapper, WalletFactoryWrapper, ModuleRegistryWrapper, - CompoundRegistryWrapper, - TokenPriceRegistryWrapper, - DexRegistryWrapper]; + ArgentWalletDetectorWrapper]; for (let idx = 0; idx < wrappers.length; idx += 1) { const wrapper = wrappers[idx]; diff --git a/deployment/4_finalise_test_environment.js b/deployment/4_finalise_test_environment.js index 09add4232..0745fb52f 100644 --- a/deployment/4_finalise_test_environment.js +++ b/deployment/4_finalise_test_environment.js @@ -1,6 +1,7 @@ /* global artifacts */ global.web3 = web3; +global.artifacts = artifacts; const ENS = artifacts.require("ENSRegistryWithFallback"); const ENSReverseRegistrar = artifacts.require("ReverseRegistrar"); diff --git a/deployment/5_deploy_modules.js b/deployment/5_deploy_modules.js index 777d976ad..44621230b 100644 --- a/deployment/5_deploy_modules.js +++ b/deployment/5_deploy_modules.js @@ -4,147 +4,40 @@ global.web3 = web3; const childProcess = require("child_process"); -const ApprovedTransfer = artifacts.require("ApprovedTransfer"); -const CompoundManager = artifacts.require("CompoundManager"); -const GuardianManager = artifacts.require("GuardianManager"); -const LockManager = artifacts.require("LockManager"); -const NftTransfer = artifacts.require("NftTransfer"); -const RecoveryManager = artifacts.require("RecoveryManager"); -const TokenExchanger = artifacts.require("TokenExchanger"); -const MakerV2Manager = artifacts.require("MakerV2Manager"); -const TransferManager = artifacts.require("TransferManager"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); +const ArgentModule = artifacts.require("ArgentModule"); const deployManager = require("../utils/deploy-manager.js"); // /////////////////////////////////////////////////////// -// Version 2.1 +// Version 2.5 // /////////////////////////////////////////////////////// async function main() { - const { network, configurator, abiUploader } = await deployManager.getProps(); + const { configurator, abiUploader } = await deployManager.getProps(); const { config } = configurator; - console.log(config); // ////////////////////////////////// - // Deploy VersionManager module + // Deploy modules // ////////////////////////////////// - const VersionManagerWrapper = await VersionManager.new( + + // Deploy ArgentModule + const ArgentModuleWrapper = await ArgentModule.new( config.contracts.ModuleRegistry, - config.modules.LockStorage, config.modules.GuardianStorage, config.modules.TransferStorage, - config.modules.LimitStorage, - ); - - // ////////////////////////////////// - // Deploy features - // ////////////////////////////////// - - // Deploy the GuardianManager module - const GuardianManagerWrapper = await GuardianManager.new( - config.modules.LockStorage, - config.modules.GuardianStorage, - VersionManagerWrapper.address, + config.contracts.DappRegistry, + config.defi.uniswap.v2Router, config.settings.securityPeriod || 0, config.settings.securityWindow || 0, - ); - // Deploy the LockManager module - const LockManagerWrapper = await LockManager.new( - config.modules.LockStorage, - config.modules.GuardianStorage, - VersionManagerWrapper.address, - config.settings.lockPeriod || 0, - ); - // Deploy the RecoveryManager module - const RecoveryManagerWrapper = await RecoveryManager.new( - config.modules.LockStorage, - config.modules.GuardianStorage, - VersionManagerWrapper.address, config.settings.recoveryPeriod || 0, - config.settings.lockPeriod || 0, - ); - // Deploy the ApprovedTransfer module - const ApprovedTransferWrapper = await ApprovedTransfer.new( - config.modules.LockStorage, - config.modules.GuardianStorage, - config.modules.LimitStorage, - VersionManagerWrapper.address, - config.defi.weth, - ); - // Deploy the TransferManager module - const TransferManagerWrapper = await TransferManager.new( - config.modules.LockStorage, - config.modules.TransferStorage, - config.modules.LimitStorage, - config.modules.TokenPriceRegistry, - VersionManagerWrapper.address, - config.settings.securityPeriod || 0, - config.settings.securityWindow || 0, - config.settings.defaultLimit || "1000000000000000000", - config.defi.weth, - ["test", "staging", "prod"].includes(network) ? config.modules.TransferManager : "0x0000000000000000000000000000000000000000", - ); - // Deploy the TokenExchanger module - const TokenExchangerWrapper = await TokenExchanger.new( - config.modules.LockStorage, - config.modules.TokenPriceRegistry, - VersionManagerWrapper.address, - config.contracts.DexRegistry, - config.defi.paraswap.contract, - "argent", - ); - // Deploy the NFTTransfer module - const NftTransferWrapper = await NftTransfer.new( - config.modules.LockStorage, - config.modules.TokenPriceRegistry, - VersionManagerWrapper.address, - config.CryptoKitties.contract, - ); - // Deploy the CompoundManager module - const CompoundManagerWrapper = await CompoundManager.new( - config.modules.LockStorage, - config.defi.compound.comptroller, - config.contracts.CompoundRegistry, - VersionManagerWrapper.address, - ); - // Deploy MakerManagerV2 - const MakerV2ManagerWrapper = await MakerV2Manager.new( - config.modules.LockStorage, - config.defi.maker.migration, - config.defi.maker.pot, - config.defi.maker.jug, - config.contracts.MakerRegistry, - config.defi.uniswap.factory, - VersionManagerWrapper.address, - ); - // Deploy RelayerManager - const RelayerManagerWrapper = await RelayerManager.new( - config.modules.LockStorage, - config.modules.GuardianStorage, - config.modules.LimitStorage, - config.modules.TokenPriceRegistry, - VersionManagerWrapper.address, - ); + config.settings.lockPeriod || 0); // ///////////////////////////////////////////////// // Update config and Upload ABIs // ///////////////////////////////////////////////// - // TODO: change name from "module" to "feature" where appropriate configurator.updateModuleAddresses({ - GuardianManager: GuardianManagerWrapper.address, - LockManager: LockManagerWrapper.address, - RecoveryManager: RecoveryManagerWrapper.address, - ApprovedTransfer: ApprovedTransferWrapper.address, - TransferManager: TransferManagerWrapper.address, - TokenExchanger: TokenExchangerWrapper.address, - NftTransfer: NftTransferWrapper.address, - CompoundManager: CompoundManagerWrapper.address, - MakerV2Manager: MakerV2ManagerWrapper.address, - RelayerManager: RelayerManagerWrapper.address, - VersionManager: VersionManagerWrapper.address, + ArgentModule: ArgentModuleWrapper.address, }); const gitHash = childProcess.execSync("git rev-parse HEAD").toString("utf8").replace(/\n$/, ""); @@ -153,17 +46,7 @@ async function main() { await configurator.save(); await Promise.all([ - abiUploader.upload(GuardianManagerWrapper, "modules"), - abiUploader.upload(LockManagerWrapper, "modules"), - abiUploader.upload(RecoveryManagerWrapper, "modules"), - abiUploader.upload(ApprovedTransferWrapper, "modules"), - abiUploader.upload(TransferManagerWrapper, "modules"), - abiUploader.upload(TokenExchangerWrapper, "modules"), - abiUploader.upload(NftTransferWrapper, "modules"), - abiUploader.upload(MakerV2ManagerWrapper, "modules"), - abiUploader.upload(CompoundManagerWrapper, "modules"), - abiUploader.upload(RelayerManagerWrapper, "modules"), - abiUploader.upload(VersionManagerWrapper, "modules"), + abiUploader.upload(ArgentModuleWrapper, "modules"), ]); console.log("## completed deployment script 5 ##"); diff --git a/deployment/6_register_modules.js b/deployment/6_register_modules.js index 2bd9802b8..ab8ba63ac 100644 --- a/deployment/6_register_modules.js +++ b/deployment/6_register_modules.js @@ -1,21 +1,12 @@ /* global artifacts */ global.web3 = web3; +global.artifacts = artifacts; const ModuleRegistry = artifacts.require("ModuleRegistry"); const MultiSig = artifacts.require("MultiSigWallet"); -const ApprovedTransfer = artifacts.require("ApprovedTransfer"); -const CompoundManager = artifacts.require("CompoundManager"); -const GuardianManager = artifacts.require("GuardianManager"); -const LockManager = artifacts.require("LockManager"); -const NftTransfer = artifacts.require("NftTransfer"); -const RecoveryManager = artifacts.require("RecoveryManager"); -const TokenExchanger = artifacts.require("TokenExchanger"); -const MakerV2Manager = artifacts.require("MakerV2Manager"); -const TransferManager = artifacts.require("TransferManager"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); +const ArgentModule = artifacts.require("ArgentModule"); const utils = require("../utils/utilities.js"); const deployManager = require("../utils/deploy-manager.js"); @@ -25,53 +16,14 @@ async function main() { const { deploymentAccount, configurator, versionUploader } = await deployManager.getProps(); const { config } = configurator; - const GuardianManagerWrapper = await GuardianManager.at(config.modules.GuardianManager); - const LockManagerWrapper = await LockManager.at(config.modules.LockManager); - const RecoveryManagerWrapper = await RecoveryManager.at(config.modules.RecoveryManager); - const ApprovedTransferWrapper = await ApprovedTransfer.at(config.modules.ApprovedTransfer); - const TransferManagerWrapper = await TransferManager.at(config.modules.TransferManager); - const TokenExchangerWrapper = await TokenExchanger.at(config.modules.TokenExchanger); - const NftTransferWrapper = await NftTransfer.at(config.modules.NftTransfer); - const CompoundManagerWrapper = await CompoundManager.at(config.modules.CompoundManager); - const MakerV2ManagerWrapper = await MakerV2Manager.at(config.modules.MakerV2Manager); - const RelayerManagerWrapper = await RelayerManager.at(config.modules.RelayerManager); - const VersionManagerWrapper = await VersionManager.at(config.modules.VersionManager); - + const ArgentModuleWrapper = await ArgentModule.at(config.modules.ArgentModule); const ModuleRegistryWrapper = await ModuleRegistry.at(config.contracts.ModuleRegistry); const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); - const wrappers = [VersionManagerWrapper]; - - // Add Features to Version Manager - const features = [ - GuardianManagerWrapper.address, - LockManagerWrapper.address, - RecoveryManagerWrapper.address, - ApprovedTransferWrapper.address, - TransferManagerWrapper.address, - TokenExchangerWrapper.address, - NftTransferWrapper.address, - CompoundManagerWrapper.address, - MakerV2ManagerWrapper.address, - RelayerManagerWrapper.address, - ]; - const featuresWithNoInit = [ // all features except the TransferManager - GuardianManagerWrapper.address, - LockManagerWrapper.address, - RecoveryManagerWrapper.address, - ApprovedTransferWrapper.address, - TokenExchangerWrapper.address, - NftTransferWrapper.address, - CompoundManagerWrapper.address, - MakerV2ManagerWrapper.address, - RelayerManagerWrapper.address, - ]; - const featureToInit = features.filter((f) => !featuresWithNoInit.includes(f)); - console.log("Adding New Version"); - await VersionManagerWrapper.addVersion(features, featureToInit); + const wrappers = [ArgentModuleWrapper]; // ////////////////////////////////// - // Register and configure VersionManager + // Register and configure module wrappers // ////////////////////////////////// const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); @@ -82,9 +34,6 @@ async function main() { [wrapper.address, utils.asciiToBytes32(wrapper.constructor.contractName)]); } - console.log("Set the MultiSig as the owner of VersionManagerWrapper"); - await VersionManagerWrapper.changeOwner(config.contracts.MultiSigWallet); - // ////////////////////////////////// // Upload Version // ////////////////////////////////// diff --git a/deployment/7_deploy_2.5_infrastructure.js b/deployment/7_deploy_2.5_infrastructure.js new file mode 100644 index 000000000..2e070f6e3 --- /dev/null +++ b/deployment/7_deploy_2.5_infrastructure.js @@ -0,0 +1,357 @@ +/* global artifacts */ +global.web3 = web3; +global.artifacts = artifacts; + +const ethers = require("ethers"); +const childProcess = require("child_process"); + +const DappRegistry = artifacts.require("DappRegistry"); +const MultiSig = artifacts.require("MultiSigWallet"); +const BaseWallet = artifacts.require("BaseWallet"); +const WalletFactory = artifacts.require("WalletFactory"); +const MultiCallHelper = artifacts.require("MultiCallHelper"); +const ArgentWalletDetector = artifacts.require("ArgentWalletDetector"); +const Proxy = artifacts.require("Proxy"); +const TokenRegistry = artifacts.require("TokenRegistry"); + +const CompoundFilter = artifacts.require("CompoundCTokenFilter"); +const IAugustusSwapper = artifacts.require("IAugustusSwapper"); +const ParaswapFilter = artifacts.require("ParaswapFilter"); +const ParaswapUniV2RouterFilter = artifacts.require("ParaswapUniV2RouterFilter"); +const WhitelistedZeroExV2Filter = artifacts.require("WhitelistedZeroExV2Filter"); +const WhitelistedZeroExV4Filter = artifacts.require("WhitelistedZeroExV4Filter"); +const OnlyApproveFilter = artifacts.require("OnlyApproveFilter"); +const AaveV2Filter = artifacts.require("AaveV2Filter"); +const BalancerFilter = artifacts.require("BalancerFilter"); +const YearnFilter = artifacts.require("YearnFilter"); +const PotFilter = artifacts.require("PotFilter"); +const DaiJoinFilter = artifacts.require("DaiJoinFilter"); +const VatFilter = artifacts.require("VatFilter"); +const ScdMcdMigration = artifacts.require("ScdMcdMigration"); +const UniswapV2Filter = artifacts.require("UniswapV2UniZapFilter"); +const LidoFilter = artifacts.require("LidoFilter"); +const CurveFilter = artifacts.require("CurveFilter"); +const AaveV1LendingPoolFilter = artifacts.require("AaveV1LendingPoolFilter"); +const AaveV1ATokenFilter = artifacts.require("AaveV1ATokenFilter"); + +const deployManager = require("../utils/deploy-manager.js"); +const MultisigExecutor = require("../utils/multisigexecutor.js"); + +const main = async () => { + const { deploymentAccount, configurator, abiUploader, network } = await deployManager.getProps(); + + // ////////////////////////////////// + // Setup + // ////////////////////////////////// + + const { config } = configurator; + const ArgentWalletDetectorWrapper = await ArgentWalletDetector.at(config.contracts.ArgentWalletDetector); + const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); + const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); + + // ////////////////////////////////// + // Deploy infrastructure + // ////////////////////////////////// + + // Deploy new BaseWallet + const BaseWalletWrapper = await BaseWallet.new(); + console.log("Deployed BaseWallet at ", BaseWalletWrapper.address); + + console.log("Adding wallet code"); + const proxyCode = ethers.utils.keccak256(Proxy.deployedBytecode); + await multisigExecutor.executeCall(ArgentWalletDetectorWrapper, "addCode", [proxyCode]); + console.log("Adding wallet implementation"); + await multisigExecutor.executeCall(ArgentWalletDetectorWrapper, "addImplementation", [BaseWalletWrapper.address]); + + // Deploy the Wallet Factory + const WalletFactoryWrapper = await WalletFactory.new( + BaseWalletWrapper.address, config.modules.GuardianStorage, config.backend.refundCollector + ); + console.log("Deployed WalletFactory at ", WalletFactoryWrapper.address); + + // Deploy DappRegistry (initial timelock of 0 to enable immediate addition to Argent Registry) + const DappRegistryWrapper = await DappRegistry.new(0); + console.log("Deployed DappRegistry at ", DappRegistryWrapper.address); + + // Deploy MultiCall Helper + const MultiCallHelperWrapper = await MultiCallHelper.new(config.modules.TransferStorage, DappRegistryWrapper.address); + console.log("Deployed MultiCallHelper at ", MultiCallHelperWrapper.address); + + // Deploy new TokenRegistry + const TokenRegistryWrapper = await TokenRegistry.new(); + console.log("Deployed TokenRegistry at ", TokenRegistryWrapper.address); + + // ////////////////////////////////// + // Deploy and add filters to Argent Registry + // ////////////////////////////////// + + // refund collector + await DappRegistryWrapper.addDapp(0, config.backend.refundCollector, ethers.constants.AddressZero); + if (network !== "test") { + // trade commision collector. In Test the refundCollector account is used + await DappRegistryWrapper.addDapp(0, config.backend.tradeCommissionCollector, ethers.constants.AddressZero); + } + + const filters = {}; + + // Compound + filters.CompoundFilter = []; + for (const [underlying, cToken] of Object.entries(config.defi.compound.markets)) { + console.log(`Deploying filter for Compound Underlying ${underlying}`); + const CompoundFilterWrapper = await CompoundFilter.new(underlying); + console.log(`Deployed filter for Compound Underlying ${underlying} at ${CompoundFilterWrapper.address}`); + filters.CompoundFilter.push(CompoundFilterWrapper.address); + console.log(`Adding filter for Compound Underlying ${underlying}`); + await DappRegistryWrapper.addDapp(0, cToken, CompoundFilterWrapper.address); + } + + // + // Paraswap + // + console.log("Deploying ParaswapFilter"); + const ParaswapFilterWrapper = await ParaswapFilter.new( + TokenRegistryWrapper.address, + DappRegistryWrapper.address, + config.defi.paraswap.contract, + config.defi.paraswap.uniswapProxy, + config.defi.paraswap.uniswapForks.map((f) => f.factory), + config.defi.paraswap.uniswapForks.map((f) => f.initCode), + [ + config.defi.paraswap.adapters.uniswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.uniswapV2 || ethers.constants.AddressZero, + config.defi.paraswap.adapters.sushiswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.linkswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.defiswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.zeroexV2 || ethers.constants.AddressZero, + config.defi.paraswap.adapters.zeroexV4 || ethers.constants.AddressZero, + config.defi.paraswap.adapters.curve || ethers.constants.AddressZero, + config.defi.paraswap.adapters.weth || ethers.constants.AddressZero, + ], + [].concat(...Object.values(config.defi.paraswap.targetExchanges || {})), // flattened targetExchanges values + config.defi.paraswap.marketMakers || [], + ); + console.log(`Deployed ParaswapFilter at ${ParaswapFilterWrapper.address}`); + filters.ParaswapFilter = ParaswapFilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.paraswap.contract, ParaswapFilterWrapper.address); + + console.log("Deploying ParaswapUniV2RouterFilter"); + const factories = [config.defi.uniswap.factoryV2, ...config.defi.paraswap.uniswapForks.map((f) => f.factory)]; + const initCodes = [config.defi.uniswap.initCodeV2, ...config.defi.paraswap.uniswapForks.map((f) => f.initCode)]; + const routers = [config.defi.uniswap.paraswapUniV2Router, ...config.defi.paraswap.uniswapForks.map((f) => f.paraswapUniV2Router)]; + filters.ParaswapUniV2RouterFilter = []; + for (let i = 0; i < routers.length; i += 1) { + const ParaswapUniV2RouterFilterWrapper = await ParaswapUniV2RouterFilter.new( + TokenRegistryWrapper.address, + factories[i], + initCodes[i], + config.defi.weth + ); + console.log(`Deployed ParaswapUniV2RouterFilter #${i} at ${ParaswapUniV2RouterFilterWrapper.address}`); + filters.ParaswapUniV2RouterFilter.push(ParaswapUniV2RouterFilterWrapper.address); + await DappRegistryWrapper.addDapp(0, routers[i], ParaswapUniV2RouterFilterWrapper.address); + } + + // Paraswap Proxies + console.log("Deploying OnlyApproveFilter"); + const OnlyApproveFilterWrapper = await OnlyApproveFilter.new(); + console.log(`Deployed OnlyApproveFilter at ${OnlyApproveFilterWrapper.address}`); + filters.OnlyApproveFilter = OnlyApproveFilterWrapper.address; + const AugustusSwapperWrapper = await IAugustusSwapper.at(config.defi.paraswap.contract); + const proxies = [await AugustusSwapperWrapper.getTokenTransferProxy(), ...Object.values(config.defi.paraswap.proxies || {})]; + for (const proxy of proxies) { + console.log(`Adding OnlyApproveFilter for proxy ${proxy}`); + await DappRegistryWrapper.addDapp(0, proxy, OnlyApproveFilterWrapper.address); + } + + // Paraswap ZeroEx filters + if (config.defi.paraswap.targetExchanges.zeroexv2) { + console.log("Deploying WhitelistedZeroExV2Filter"); + const WhitelistedZeroExV2FilterWrapper = await WhitelistedZeroExV2Filter.new(config.defi.paraswap.marketMakers); + console.log(`Deployed WhitelistedZeroExV2Filter at ${WhitelistedZeroExV2FilterWrapper.address}`); + filters.WhitelistedZeroExV2Filter = WhitelistedZeroExV2FilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.paraswap.targetExchanges.zeroexv2, WhitelistedZeroExV2FilterWrapper.address); + } + if (config.defi.paraswap.targetExchanges.zeroexv4) { + console.log("Deploying WhitelistedZeroExV4Filter"); + const WhitelistedZeroExV4FilterWrapper = await WhitelistedZeroExV4Filter.new(config.defi.paraswap.marketMakers); + console.log(`Deployed WhitelistedZeroExV4Filter at ${WhitelistedZeroExV4FilterWrapper.address}`); + filters.WhitelistedZeroExV4Filter = WhitelistedZeroExV4FilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.paraswap.targetExchanges.zeroexv4, WhitelistedZeroExV4FilterWrapper.address); + } + + // Curve filters + console.log("Deploying CurveFilter"); + const CurveFilterWrapper = await CurveFilter.new(); + console.log(`Deployed CurveFilter at ${CurveFilterWrapper.address}`); + filters.CurveFilter = CurveFilterWrapper.address; + for (const pool of config.defi.paraswap.targetExchanges.curve || []) { + console.log(`Adding CurveFilter for pool ${pool}`); + await DappRegistryWrapper.addDapp(0, pool, CurveFilterWrapper.address); + } + + // The following filters can't be setup on Ropsten due to tha lack of integrations + if (network !== "test") { + // AaveV2 + console.log("Deploying AaveV2Filter"); + const AaveV2FilterWrapper = await AaveV2Filter.new(); + console.log(`Deployed AaveV2Filter at ${AaveV2FilterWrapper.address}`); + filters.AaveV2Filter = AaveV2FilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.aave.contract, AaveV2FilterWrapper.address); + + // Balancer + console.log("Deploying BalancerFilter"); + const BalancerFilterWrapper = await BalancerFilter.new(); + console.log(`Deployed BalancerFilter at ${BalancerFilterWrapper.address}`); + filters.BalancerFilter = BalancerFilterWrapper.address; + for (const pool of (config.defi.balancer.pools)) { + console.log(`Adding filter for Balancer pool ${pool}`); + await DappRegistryWrapper.addDapp(0, pool, BalancerFilterWrapper.address); + } + + // yEarn + filters.YearnFilter = []; + console.log("Deploying YearnFilter (isWeth=false)"); + const YearnFilterWrapper = await YearnFilter.new(false); + console.log(`Deployed YearnFilter (isWeth=false) at ${YearnFilterWrapper.address}`); + filters.YearnFilter.push(YearnFilterWrapper.address); + console.log("Deploying YearnFilter (isWeth=true)"); + const WethYearnFilterWrapper = await YearnFilter.new(true); + console.log(`Deployed YearnFilter (isWeth=true) at ${WethYearnFilterWrapper.address}`); + filters.YearnFilter.push(WethYearnFilterWrapper.address); + for (const pool of (config.defi.yearn.pools)) { + console.log(`Adding filter for Yearn pool ${pool}`); + await DappRegistryWrapper.addDapp(0, pool, YearnFilterWrapper.address); + } + for (const pool of (config.defi.yearn.wethPools)) { + console.log(`Adding filter for WETH Yearn pool ${pool}`); + await DappRegistryWrapper.addDapp(0, pool, WethYearnFilterWrapper.address); + } + + // Lido + console.log("Deploying LidoFilter"); + const LidoFilterWrapper = await LidoFilter.new(); + console.log(`Deployed LidoFilter at ${LidoFilterWrapper.address}`); + filters.LidoFilter = LidoFilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.lido.contract, LidoFilterWrapper.address); + // Note: The filter for the stETH -> ETH curve Pool was deployed in the Curve section + } + + // DSR + console.log("Deploying PotFilter"); + const PotFilterWrapper = await PotFilter.new(); + console.log(`Deployed PotFilter at ${PotFilterWrapper.address}`); + filters.PotFilter = PotFilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.maker.pot, PotFilterWrapper.address); + + console.log("Deploying DaiJoinFilter"); + const DaiJoinFilterWrapper = await DaiJoinFilter.new(); + console.log(`Deployed DaiJoinFilter at ${DaiJoinFilterWrapper.address}`); + filters.DaiJoinFilter = DaiJoinFilterWrapper.address; + const migration = await ScdMcdMigration.at(config.defi.maker.migration); + const daiJoin = await migration.daiJoin(); + await DappRegistryWrapper.addDapp(0, daiJoin, DaiJoinFilterWrapper.address); + + console.log("Deploying VatFilter"); + const vat = await migration.vat(); + const VatFilterWrapper = await VatFilter.new(daiJoin, config.defi.maker.pot); + console.log(`Deployed VatFilter at ${VatFilterWrapper.address}`); + filters.VatFilter = VatFilterWrapper.address; + await DappRegistryWrapper.addDapp(0, vat, VatFilterWrapper.address); + + // Uniswap V2 + console.log("Deploying UniswapV2Filter"); + const UniswapV2FilterWrapper = await UniswapV2Filter.new( + TokenRegistryWrapper.address, + config.defi.uniswap.factoryV2, + config.defi.uniswap.initCodeV2, + config.defi.weth + ); + console.log(`Deployed UniswapV2Filter at ${UniswapV2FilterWrapper.address}`); + filters.UniswapV2UniZapFilter = UniswapV2FilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.uniswap.unizap, UniswapV2FilterWrapper.address); + + // Aave V1 + console.log("Deploying AaveV1"); + + const AaveV1LendingPoolFilterWrapper = await AaveV1LendingPoolFilter.new(); + console.log(`Deployed AaveV1LendingPoolFilter at ${AaveV1LendingPoolFilterWrapper.address}`); + filters.AaveV1LendingPoolFilter = AaveV1LendingPoolFilterWrapper.address; + await DappRegistryWrapper.addDapp(0, config.defi.aave.lendingPool, AaveV1LendingPoolFilterWrapper.address); + console.log("Adding OnlyApproveFilter for AavelendingPoolCore"); + await DappRegistryWrapper.addDapp(0, config.defi.aave.lendingPoolCore, OnlyApproveFilterWrapper.address); + + const AaveV1ATokenFilterWrapper = await AaveV1ATokenFilter.new(); + console.log(`Deployed AaveV1ATokenFilter at ${AaveV1ATokenFilterWrapper.address}`); + filters.AaveV1ATokenFilter = AaveV1ATokenFilterWrapper.address; + for (const aToken of (config.defi.aave.aTokens)) { + console.log(`Adding filter for Aave token ${aToken}`); + await DappRegistryWrapper.addDapp(0, aToken, AaveV1ATokenFilterWrapper.address); + } + + // Setting timelock + console.log(`Setting Timelock to ${config.settings.timelockPeriod}`); + await DappRegistryWrapper.requestTimelockChange(config.settings.timelockPeriod); + await DappRegistryWrapper.confirmTimelockChange(); + console.log("Timelock changed."); + + // ////////////////////////////////// + // Set contracts' managers + // ////////////////////////////////// + + for (const idx in config.backend.accounts) { + const account = config.backend.accounts[idx]; + console.log(`Setting ${account} as the manager of the WalletFactory`); + await WalletFactoryWrapper.addManager(account); + console.log(`Setting ${account} as the manager of the TokenRegistry`); + await TokenRegistryWrapper.addManager(account); + } + + // ////////////////////////////////// + // Set contracts' owners + // ////////////////////////////////// + + console.log("Setting the MultiSig as the owner of WalletFactoryWrapper"); + await WalletFactoryWrapper.changeOwner(config.contracts.MultiSigWallet); + + console.log("Setting the MultiSig as the owner of TokenRegistry"); + await TokenRegistryWrapper.changeOwner(config.contracts.MultiSigWallet); + + console.log("Setting the MultiSig as the owner of default registry"); + await DappRegistryWrapper.changeOwner(0, config.contracts.MultiSigWallet); + + // ///////////////////////////////////////////////// + // Update config and Upload ABIs + // ///////////////////////////////////////////////// + + configurator.updateInfrastructureAddresses({ + DappRegistry: DappRegistryWrapper.address, + WalletFactory: WalletFactoryWrapper.address, + BaseWallet: BaseWalletWrapper.address, + MultiCallHelper: MultiCallHelperWrapper.address, + TokenRegistry: TokenRegistryWrapper.address, + }); + + configurator.updateFilterAddresses(filters); + + configurator.updateFilterAddresses({ + OnlyApproveFilter: OnlyApproveFilterWrapper.address, + }); + + const gitHash = childProcess.execSync("git rev-parse HEAD").toString("utf8").replace(/\n$/, ""); + configurator.updateGitHash(gitHash); + + console.log("Saving new config"); + await configurator.save(); + + console.log("Uploading ABIs"); + await Promise.all([ + abiUploader.upload(DappRegistryWrapper, "contracts"), + abiUploader.upload(WalletFactoryWrapper, "contracts"), + abiUploader.upload(BaseWalletWrapper, "contracts"), + abiUploader.upload(MultiCallHelperWrapper, "contracts"), + abiUploader.upload(TokenRegistryWrapper, "contracts"), + ]); +}; + +// For truffle exec +module.exports = (cb) => main().then(cb).catch(cb); diff --git a/scripts/module_upgrade_template.js b/deployment/8_deploy_2.5_module.js similarity index 63% rename from scripts/module_upgrade_template.js rename to deployment/8_deploy_2.5_module.js index fc7f31ee7..240e3cb86 100644 --- a/scripts/module_upgrade_template.js +++ b/deployment/8_deploy_2.5_module.js @@ -1,33 +1,47 @@ /* global artifacts */ global.web3 = web3; +global.artifacts = artifacts; const semver = require("semver"); const childProcess = require("child_process"); const MultiSig = artifacts.require("MultiSigWallet"); const ModuleRegistry = artifacts.require("ModuleRegistry"); -const VersionManager = artifacts.require("VersionManager"); -const Upgrader = artifacts.require("UpgraderToVersionManager"); +const ArgentModule = artifacts.require("ArgentModule"); +const Upgrader = artifacts.require("SimpleUpgrader"); const deployManager = require("../utils/deploy-manager.js"); const MultisigExecutor = require("../utils/multisigexecutor.js"); const utils = require("../utils/utilities.js"); -const TARGET_VERSION = "2.1.0"; +const TARGET_VERSION = "2.5.0"; const MODULES_TO_ENABLE = [ + "ArgentModule", +]; +const MODULES_TO_DISABLE = [ "VersionManager", + "MakerV2Manager", + "TokenExchanger", + "LockManager", + "RecoveryManager", + "TransferManager", + "NftTransfer", + "RelayerManager", + "CompoundManager", + "GuardianManager", + "ApprovedTransfer", + "UniswapManager", + "MakerManager", + "DappManager", + "ModuleManager", + "TokenTransfer" ]; -const MODULES_TO_DISABLE = []; -const BACKWARD_COMPATIBILITY = 3; +const BACKWARD_COMPATIBILITY = 5; -const main = async (network) => { - if (!["kovan", "kovan-fork", "staging", "prod"].includes(network)) { - console.warn("------------------------------------------------------------------------"); - console.warn(`WARNING: The MakerManagerV2 module is not fully functional on ${network}`); - console.warn("------------------------------------------------------------------------"); - } +const main = async () => { + const { deploymentAccount, configurator, versionUploader, abiUploader } = await deployManager.getProps(); const newModuleWrappers = []; const newVersion = {}; @@ -35,45 +49,51 @@ const main = async (network) => { // ////////////////////////////////// // Setup // ////////////////////////////////// - const { deploymentAccount, configurator, versionUploader } = await deployManager.getProps(); - const { config } = configurator; + const { config } = configurator; const ModuleRegistryWrapper = await ModuleRegistry.at(config.contracts.ModuleRegistry); const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); // ////////////////////////////////// - // Initialise the new version + // Deploy modules // ////////////////////////////////// - const VersionManagerWrapper = await VersionManager.at(config.modules.VersionManager); - // ////////////////////////////////// - // Setup new infrastructure - // ////////////////////////////////// + console.log("Deploying modules"); - // ////////////////////////////////// - // Set contracts' managers - // ////////////////////////////////// + // Deploy ArgentModule + const ArgentModuleWrapper = await ArgentModule.new( + config.contracts.ModuleRegistry, + config.modules.GuardianStorage, + config.modules.TransferStorage, + config.contracts.DappRegistry, + config.defi.uniswap.v2Router, + config.settings.securityPeriod || 0, + config.settings.securityWindow || 0, + config.settings.recoveryPeriod || 0, + config.settings.lockPeriod || 0); - // ////////////////////////////////// - // Set contracts' owners - // ////////////////////////////////// + console.log(`Deployed ArgentModule at ${ArgentModuleWrapper.address}`); + + newModuleWrappers.push(ArgentModuleWrapper); // ///////////////////////////////////////////////// // Update config and Upload ABIs // ///////////////////////////////////////////////// configurator.updateModuleAddresses({ - }); - - configurator.updateInfrastructureAddresses({ + ArgentModule: ArgentModuleWrapper.address, }); const gitHash = childProcess.execSync("git rev-parse HEAD").toString("utf8").replace(/\n$/, ""); configurator.updateGitHash(gitHash); + + console.log("Saving new config"); await configurator.save(); + console.log("Uploading ABIs"); await Promise.all([ + abiUploader.upload(ArgentModuleWrapper, "modules"), ]); // ////////////////////////////////// @@ -82,8 +102,9 @@ const main = async (network) => { for (let idx = 0; idx < newModuleWrappers.length; idx += 1) { const wrapper = newModuleWrappers[idx]; + console.log(`Registering module ${wrapper.constructor.contractName}`); await multisigExecutor.executeCall(ModuleRegistryWrapper, "registerModule", - [wrapper.contractAddress, utils.asciiToBytes32(wrapper.constructor.contractName)]); + [wrapper.address, utils.asciiToBytes32(wrapper.constructor.contractName)]); } // ////////////////////////////////// @@ -91,6 +112,7 @@ const main = async (network) => { // ////////////////////////////////// let fingerprint; + console.log(`Loading last ${BACKWARD_COMPATIBILITY} versions`); const versions = await versionUploader.load(BACKWARD_COMPATIBILITY); for (let idx = 0; idx < versions.length; idx += 1) { const version = versions[idx]; @@ -99,7 +121,7 @@ const main = async (network) => { const moduleNamesToRemove = MODULES_TO_DISABLE.concat(MODULES_TO_ENABLE); toRemove = version.modules.filter((module) => moduleNamesToRemove.includes(module.name)); toAdd = newModuleWrappers.map((wrapper) => ({ - address: wrapper.contractAddress, + address: wrapper.address, name: wrapper.constructor.contractName, })); const toKeep = version.modules.filter((module) => !moduleNamesToRemove.includes(module.name)); @@ -118,17 +140,20 @@ const main = async (network) => { const upgraderName = `${version.fingerprint}_${fingerprint}`; + console.log(`Deploying upgrader ${upgraderName}`); const UpgraderWrapper = await Upgrader.new( config.contracts.ModuleRegistry, - config.modules.GuardianStorage, // using the "old LockStorage" here which was part of the GuardianStorage in 1.6 toRemove.map((module) => module.address), - VersionManagerWrapper.contractAddress, // to add + toAdd.map((module) => module.address) ); + + console.log(`Registering ${upgraderName} as a module`); await multisigExecutor.executeCall(ModuleRegistryWrapper, "registerModule", - [UpgraderWrapper.contractAddress, utils.asciiToBytes32(upgraderName)]); + [UpgraderWrapper.address, utils.asciiToBytes32(upgraderName)]); + console.log(`Registering ${upgraderName} as an upgrader`); await multisigExecutor.executeCall(ModuleRegistryWrapper, "registerUpgrader", - [UpgraderWrapper.contractAddress, utils.asciiToBytes32(upgraderName)]); + [UpgraderWrapper.address, utils.asciiToBytes32(upgraderName)]); } // ////////////////////////////////// @@ -138,6 +163,5 @@ const main = async (network) => { await versionUploader.upload(newVersion); }; -main().catch((err) => { - throw err; -}); +// For truffle exec +module.exports = (cb) => main().then(cb).catch(cb); diff --git a/deployment/9_update_ens.js b/deployment/9_update_ens.js new file mode 100644 index 000000000..34acb5982 --- /dev/null +++ b/deployment/9_update_ens.js @@ -0,0 +1,87 @@ +/* global artifacts */ +global.web3 = web3; +global.artifacts = artifacts; + +const TruffleContract = require("@truffle/contract"); + +const ENSManager = artifacts.require("ArgentENSManager"); +const ENSResolver = artifacts.require("ArgentENSResolver"); +const MultiSig = artifacts.require("MultiSigWallet"); +const WalletFactory16Contract = require("../build-legacy/v1.6.0/WalletFactory"); + +const WalletFactory16 = TruffleContract(WalletFactory16Contract); + +const deployManager = require("../utils/deploy-manager.js"); +const MultisigExecutor = require("../utils/multisigexecutor.js"); +const utils = require("../utils/utilities.js"); + +async function main() { + // ////////////////////////////////// + // Setup + // ////////////////////////////////// + const { deploymentAccount, configurator, abiUploader } = await deployManager.getProps(); + const { config } = configurator; + const { domain } = config.ENS; + + WalletFactory16.setProvider(web3.currentProvider); + + const walletFactory16Wrapper = await WalletFactory16.at(config.contracts.WalletFactory16); + + // Instantiate the ENS Registry and existing ENSManager + const ENSManagerWrapper = await ENSManager.at(config.contracts.ENSManager); + const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); + const ENSResolverWrapper = await ENSResolver.at(config.contracts.ENSResolver); + const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); + + // ////////////////////////////////// + // Deploy new contracts + // ////////////////////////////////// + + // Deploy the updated ENSManager + const NewENSManagerWrapper = await ENSManager.new(domain, utils.namehash(domain), config.ENS.ensRegistry, config.contracts.ENSResolver); + + // ////////////////////////////////// + // Configure ENS + // ////////////////////////////////// + + // Set the backend accounts as a manager of the new ENSManager + for (const idx in config.backend.accounts) { + const account = config.backend.accounts[idx]; + console.log(`Set ${account} as the manager of the new ENSManager`); + await NewENSManagerWrapper.addManager(account); + } + + // The legacy factory has to be a manager of the new ENSManager as it calls it's register function for new wallets + await NewENSManagerWrapper.addManager(walletFactory16Wrapper.address); + + await multisigExecutor.executeCall(walletFactory16Wrapper, "changeENSManager", [NewENSManagerWrapper.address]); + console.log(`EnsManager on legacy wallet factory changed to ${NewENSManagerWrapper.address}`); + + // Set the MultiSig as the owner of the new ENSManager + await NewENSManagerWrapper.changeOwner(config.contracts.MultiSigWallet); + + // Decomission old ENSManager + await multisigExecutor.executeCall(ENSManagerWrapper, "changeRootnodeOwner", ["0x0000000000000000000000000000000000000000"]); + console.log(`Owner of ${domain} changed from from old ENSManager to 0x0000000000000000000000000000000000000000`); + + // Set new ENSManager as a manager of ENSResolver + await multisigExecutor.executeCall(ENSResolverWrapper, "addManager", [NewENSManagerWrapper.address]); + + // ///////////////////////////////////////////////// + // Update config and Upload ABIs + // ///////////////////////////////////////////////// + + configurator.updateInfrastructureAddresses({ + ENSManager: NewENSManagerWrapper.address + }); + await configurator.save(); + + await Promise.all([ + abiUploader.upload(NewENSManagerWrapper, "contracts") + ]); +} + +// For truffle exec +module.exports = function (callback) { + main().then(() => callback()).catch((err) => callback(err)); +}; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 000000000..16f04d6bd --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "allowUnreachableCode": true + } +} diff --git a/lib/compound/CToken.sol b/lib/compound/CToken.sol deleted file mode 100644 index 923273fa1..000000000 --- a/lib/compound/CToken.sol +++ /dev/null @@ -1,1571 +0,0 @@ -pragma solidity ^0.5.4; - -import "./ComptrollerInterface.sol"; -import "./ErrorReporter.sol"; -import "./Exponential.sol"; -import "./EIP20Interface.sol"; -import "./EIP20NonStandardInterface.sol"; -import "./ReentrancyGuard.sol"; -import "./InterestRateModel.sol"; - -/** - * @title Compound's CToken Contract - * @notice Abstract base for CTokens - * @author Compound - */ -contract CToken is EIP20Interface, Exponential, TokenErrorReporter, ReentrancyGuard { - /** - * @notice Indicator that this is a CToken contract (for inspection) - */ - bool public constant isCToken = true; - - /** - * @notice EIP-20 token name for this token - */ - string public name; - - /** - * @notice EIP-20 token symbol for this token - */ - string public symbol; - - /** - * @notice EIP-20 token decimals for this token - */ - uint public decimals; - - /** - * @notice Maximum borrow rate that can ever be applied (.0005% / block) - */ - uint constant borrowRateMaxMantissa = 5e14; - - /** - * @notice Maximum fraction of interest that can be set aside for reserves - */ - uint constant reserveFactorMaxMantissa = 1e18; - - /** - * @notice Administrator for this contract - */ - address payable public admin; - - /** - * @notice Pending administrator for this contract - */ - address payable public pendingAdmin; - - /** - * @notice Contract which oversees inter-cToken operations - */ - ComptrollerInterface public comptroller; - - /** - * @notice Model which tells what the current interest rate should be - */ - InterestRateModel public interestRateModel; - - /** - * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0) - */ - uint public initialExchangeRateMantissa; - - /** - * @notice Fraction of interest currently set aside for reserves - */ - uint public reserveFactorMantissa; - - /** - * @notice Block number that interest was last accrued at - */ - uint public accrualBlockNumber; - - /** - * @notice Accumulator of total earned interest since the opening of the market - */ - uint public borrowIndex; - - /** - * @notice Total amount of outstanding borrows of the underlying in this market - */ - uint public totalBorrows; - - /** - * @notice Total amount of reserves of the underlying held in this market - */ - uint public totalReserves; - - /** - * @notice Total number of tokens in circulation - */ - uint256 public totalSupply; - - /** - * @notice Official record of token balances for each account - */ - mapping (address => uint256) accountTokens; - - /** - * @notice Approved token transfer amounts on behalf of others - */ - mapping (address => mapping (address => uint256)) transferAllowances; - - /** - * @notice Container for borrow balance information - * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action - * @member interestIndex Global borrowIndex as of the most recent balance-changing action - */ - struct BorrowSnapshot { - uint principal; - uint interestIndex; - } - - /** - * @notice Mapping of account addresses to outstanding borrow balances - */ - mapping(address => BorrowSnapshot) accountBorrows; - - - /*** Market Events ***/ - - /** - * @notice Event emitted when interest is accrued - */ - event AccrueInterest(uint interestAccumulated, uint borrowIndex, uint totalBorrows); - - /** - * @notice Event emitted when tokens are minted - */ - event Mint(address minter, uint mintAmount, uint mintTokens); - - /** - * @notice Event emitted when tokens are redeemed - */ - event Redeem(address redeemer, uint redeemAmount, uint redeemTokens); - - /** - * @notice Event emitted when underlying is borrowed - */ - event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows); - - /** - * @notice Event emitted when a borrow is repaid - */ - event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows); - - /** - * @notice Event emitted when a borrow is liquidated - */ - event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens); - - - /*** Admin Events ***/ - - /** - * @notice Event emitted when pendingAdmin is changed - */ - event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); - - /** - * @notice Event emitted when pendingAdmin is accepted, which means admin is updated - */ - event NewAdmin(address oldAdmin, address newAdmin); - - /** - * @notice Event emitted when comptroller is changed - */ - event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); - - /** - * @notice Event emitted when interestRateModel is changed - */ - event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel); - - /** - * @notice Event emitted when the reserve factor is changed - */ - event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa); - - /** - * @notice Event emitted when the reserves are reduced - */ - event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves); - - /** - * @notice Construct a new money market - * @param comptroller_ The address of the Comptroller - * @param interestRateModel_ The address of the interest rate model - * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 - * @param name_ EIP-20 name of this token - * @param symbol_ EIP-20 symbol of this token - * @param decimals_ EIP-20 decimal precision of this token - */ - constructor(ComptrollerInterface comptroller_, - InterestRateModel interestRateModel_, - uint initialExchangeRateMantissa_, - string memory name_, - string memory symbol_, - uint decimals_) internal { - // Set admin to msg.sender - admin = msg.sender; - - // Set initial exchange rate - initialExchangeRateMantissa = initialExchangeRateMantissa_; - require(initialExchangeRateMantissa > 0, "Initial exchange rate must be greater than zero."); - - // Set the comptroller - uint err = _setComptroller(comptroller_); - require(err == uint(Error.NO_ERROR), "Setting comptroller failed"); - - // Initialize block number and borrow index (block number mocks depend on comptroller being set) - accrualBlockNumber = getBlockNumber(); - borrowIndex = mantissaOne; - - // Set the interest rate model (depends on block number / borrow index) - err = _setInterestRateModelFresh(interestRateModel_); - require(err == uint(Error.NO_ERROR), "Setting interest rate model failed"); - - name = name_; - symbol = symbol_; - decimals = decimals_; - } - - /** - * @notice Transfer `tokens` tokens from `src` to `dst` by `spender` - * @dev Called by both `transfer` and `transferFrom` internally - * @param spender The address of the account performing the transfer - * @param src The address of the source account - * @param dst The address of the destination account - * @param tokens The number of tokens to transfer - * @return Whether or not the transfer succeeded - */ - function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) { - /* Fail if transfer not allowed */ - uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed); - } - - /* Do not allow self-transfers */ - if (src == dst) { - return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED); - } - - /* Get the allowance, infinite for the account owner */ - uint startingAllowance = 0; - if (spender == src) { - startingAllowance = uint(-1); - } else { - startingAllowance = transferAllowances[src][spender]; - } - - /* Do the calculations, checking for {under,over}flow */ - MathError mathErr; - uint allowanceNew; - uint srcTokensNew; - uint dstTokensNew; - - (mathErr, allowanceNew) = subUInt(startingAllowance, tokens); - if (mathErr != MathError.NO_ERROR) { - return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED); - } - - (mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens); - if (mathErr != MathError.NO_ERROR) { - return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH); - } - - (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens); - if (mathErr != MathError.NO_ERROR) { - return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - accountTokens[src] = srcTokensNew; - accountTokens[dst] = dstTokensNew; - - /* Eat some of the allowance (if necessary) */ - if (startingAllowance != uint(-1)) { - transferAllowances[src][spender] = allowanceNew; - } - - /* We emit a Transfer event */ - emit Transfer(src, dst, tokens); - - /* We call the defense hook (which checks for under-collateralization) */ - comptroller.transferVerify(address(this), src, dst, tokens); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfer `amount` tokens from `msg.sender` to `dst` - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - * @return Whether or not the transfer succeeded - */ - function transfer(address dst, uint256 amount) external nonReentrant returns (bool) { - return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR); - } - - /** - * @notice Transfer `amount` tokens from `src` to `dst` - * @param src The address of the source account - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - * @return Whether or not the transfer succeeded - */ - function transferFrom(address src, address dst, uint256 amount) external nonReentrant returns (bool) { - return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR); - } - - /** - * @notice Approve `spender` to transfer up to `amount` from `src` - * @dev This will overwrite the approval amount for `spender` - * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) - * @param spender The address of the account which may transfer tokens - * @param amount The number of tokens that are approved (-1 means infinite) - * @return Whether or not the approval succeeded - */ - function approve(address spender, uint256 amount) external returns (bool) { - address src = msg.sender; - transferAllowances[src][spender] = amount; - emit Approval(src, spender, amount); - return true; - } - - /** - * @notice Get the current allowance from `owner` for `spender` - * @param owner The address of the account which owns the tokens to be spent - * @param spender The address of the account which may transfer tokens - * @return The number of tokens allowed to be spent (-1 means infinite) - */ - function allowance(address owner, address spender) external view returns (uint256) { - return transferAllowances[owner][spender]; - } - - /** - * @notice Get the token balance of the `owner` - * @param owner The address of the account to query - * @return The number of tokens owned by `owner` - */ - function balanceOf(address owner) external view returns (uint256) { - return accountTokens[owner]; - } - - /** - * @notice Get the underlying balance of the `owner` - * @dev This also accrues interest in a transaction - * @param owner The address of the account to query - * @return The amount of underlying owned by `owner` - */ - function balanceOfUnderlying(address owner) external returns (uint) { - Exp memory exchangeRate = Exp({mantissa: exchangeRateCurrent()}); - (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]); - require(mErr == MathError.NO_ERROR); - return balance; - } - - /** - * @notice Get a snapshot of the account's balances, and the cached exchange rate - * @dev This is used by comptroller to more efficiently perform liquidity checks. - * @param account Address of the account to snapshot - * @return (possible error, token balance, borrow balance, exchange rate mantissa) - */ - function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { - uint cTokenBalance = accountTokens[account]; - uint borrowBalance; - uint exchangeRateMantissa; - - MathError mErr; - - (mErr, borrowBalance) = borrowBalanceStoredInternal(account); - if (mErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0, 0, 0); - } - - (mErr, exchangeRateMantissa) = exchangeRateStoredInternal(); - if (mErr != MathError.NO_ERROR) { - return (uint(Error.MATH_ERROR), 0, 0, 0); - } - - return (uint(Error.NO_ERROR), cTokenBalance, borrowBalance, exchangeRateMantissa); - } - - /** - * @dev Function to simply retrieve block number - * This exists mainly for inheriting test contracts to stub this result. - */ - function getBlockNumber() internal view returns (uint) { - return block.number; - } - - /** - * @notice Returns the current per-block borrow interest rate for this cToken - * @return The borrow interest rate per block, scaled by 1e18 - */ - function borrowRatePerBlock() external view returns (uint) { - (uint opaqueErr, uint borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves); - require(opaqueErr == 0, "borrowRatePerBlock: interestRateModel.borrowRate failed"); // semi-opaque - return borrowRateMantissa; - } - - /** - * @notice Returns the current per-block supply interest rate for this cToken - * @return The supply interest rate per block, scaled by 1e18 - */ - function supplyRatePerBlock() external view returns (uint) { - /* We calculate the supply rate: - * underlying = totalSupply × exchangeRate - * borrowsPer = totalBorrows ÷ underlying - * supplyRate = borrowRate × (1-reserveFactor) × borrowsPer - */ - uint exchangeRateMantissa = exchangeRateStored(); - - (uint e0, uint borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves); - require(e0 == 0, "supplyRatePerBlock: calculating borrowRate failed"); // semi-opaque - - (MathError e1, Exp memory underlying) = mulScalar(Exp({mantissa: exchangeRateMantissa}), totalSupply); - require(e1 == MathError.NO_ERROR, "supplyRatePerBlock: calculating underlying failed"); - - (MathError e2, Exp memory borrowsPer) = divScalarByExp(totalBorrows, underlying); - require(e2 == MathError.NO_ERROR, "supplyRatePerBlock: calculating borrowsPer failed"); - - (MathError e3, Exp memory oneMinusReserveFactor) = subExp(Exp({mantissa: mantissaOne}), Exp({mantissa: reserveFactorMantissa})); - require(e3 == MathError.NO_ERROR, "supplyRatePerBlock: calculating oneMinusReserveFactor failed"); - - (MathError e4, Exp memory supplyRate) = mulExp3(Exp({mantissa: borrowRateMantissa}), oneMinusReserveFactor, borrowsPer); - require(e4 == MathError.NO_ERROR, "supplyRatePerBlock: calculating supplyRate failed"); - - return supplyRate.mantissa; - } - - /** - * @notice Returns the current total borrows plus accrued interest - * @return The total borrows with interest - */ - function totalBorrowsCurrent() external nonReentrant returns (uint) { - require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); - return totalBorrows; - } - - /** - * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex - * @param account The address whose balance should be calculated after updating borrowIndex - * @return The calculated balance - */ - function borrowBalanceCurrent(address account) external nonReentrant returns (uint) { - require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); - return borrowBalanceStored(account); - } - - /** - * @notice Return the borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return The calculated balance - */ - function borrowBalanceStored(address account) public view returns (uint) { - (MathError err, uint result) = borrowBalanceStoredInternal(account); - require(err == MathError.NO_ERROR, "borrowBalanceStored: borrowBalanceStoredInternal failed"); - return result; - } - - /** - * @notice Return the borrow balance of account based on stored data - * @param account The address whose balance should be calculated - * @return (error code, the calculated balance or 0 if error code is non-zero) - */ - function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) { - /* Note: we do not assert that the market is up to date */ - MathError mathErr; - uint principalTimesIndex; - uint result; - - /* Get borrowBalance and borrowIndex */ - BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; - - /* If borrowBalance = 0 then borrowIndex is likely also 0. - * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. - */ - if (borrowSnapshot.principal == 0) { - return (MathError.NO_ERROR, 0); - } - - /* Calculate new borrow balance using the interest index: - * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex - */ - (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex); - if (mathErr != MathError.NO_ERROR) { - return (mathErr, 0); - } - - (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex); - if (mathErr != MathError.NO_ERROR) { - return (mathErr, 0); - } - - return (MathError.NO_ERROR, result); - } - - /** - * @notice Accrue interest then return the up-to-date exchange rate - * @return Calculated exchange rate scaled by 1e18 - */ - function exchangeRateCurrent() public nonReentrant returns (uint) { - require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); - return exchangeRateStored(); - } - - /** - * @notice Calculates the exchange rate from the underlying to the CToken - * @dev This function does not accrue interest before calculating the exchange rate - * @return Calculated exchange rate scaled by 1e18 - */ - function exchangeRateStored() public view returns (uint) { - (MathError err, uint result) = exchangeRateStoredInternal(); - require(err == MathError.NO_ERROR, "exchangeRateStored: exchangeRateStoredInternal failed"); - return result; - } - - /** - * @notice Calculates the exchange rate from the underlying to the CToken - * @dev This function does not accrue interest before calculating the exchange rate - * @return (error code, calculated exchange rate scaled by 1e18) - */ - function exchangeRateStoredInternal() internal view returns (MathError, uint) { - if (totalSupply == 0) { - /* - * If there are no tokens minted: - * exchangeRate = initialExchangeRate - */ - return (MathError.NO_ERROR, initialExchangeRateMantissa); - } else { - /* - * Otherwise: - * exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply - */ - uint totalCash = getCashPrior(); - uint cashPlusBorrowsMinusReserves; - Exp memory exchangeRate; - MathError mathErr; - - (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves); - if (mathErr != MathError.NO_ERROR) { - return (mathErr, 0); - } - - (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, totalSupply); - if (mathErr != MathError.NO_ERROR) { - return (mathErr, 0); - } - - return (MathError.NO_ERROR, exchangeRate.mantissa); - } - } - - /** - * @notice Get cash balance of this cToken in the underlying asset - * @return The quantity of underlying asset owned by this contract - */ - function getCash() external view returns (uint) { - return getCashPrior(); - } - - struct AccrueInterestLocalVars { - MathError mathErr; - uint opaqueErr; - uint borrowRateMantissa; - uint currentBlockNumber; - uint blockDelta; - - Exp simpleInterestFactor; - - uint interestAccumulated; - uint totalBorrowsNew; - uint totalReservesNew; - uint borrowIndexNew; - } - - /** - * @notice Applies accrued interest to total borrows and reserves. - * @dev This calculates interest accrued from the last checkpointed block - * up to the current block and writes new checkpoint to storage. - */ - function accrueInterest() public returns (uint) { - AccrueInterestLocalVars memory vars; - - /* Calculate the current borrow interest rate */ - (vars.opaqueErr, vars.borrowRateMantissa) = interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves); - require(vars.borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high"); - if (vars.opaqueErr != 0) { - return failOpaque(Error.INTEREST_RATE_MODEL_ERROR, FailureInfo.ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED, vars.opaqueErr); - } - - /* Remember the initial block number */ - vars.currentBlockNumber = getBlockNumber(); - - /* Calculate the number of blocks elapsed since the last accrual */ - (vars.mathErr, vars.blockDelta) = subUInt(vars.currentBlockNumber, accrualBlockNumber); - assert(vars.mathErr == MathError.NO_ERROR); // Block delta should always succeed and if it doesn't, blow up. - - /* - * Calculate the interest accumulated into borrows and reserves and the new index: - * simpleInterestFactor = borrowRate * blockDelta - * interestAccumulated = simpleInterestFactor * totalBorrows - * totalBorrowsNew = interestAccumulated + totalBorrows - * totalReservesNew = interestAccumulated * reserveFactor + totalReserves - * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex - */ - (vars.mathErr, vars.simpleInterestFactor) = mulScalar(Exp({mantissa: vars.borrowRateMantissa}), vars.blockDelta); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.interestAccumulated) = mulScalarTruncate(vars.simpleInterestFactor, totalBorrows); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.totalBorrowsNew) = addUInt(vars.interestAccumulated, totalBorrows); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa: reserveFactorMantissa}), vars.interestAccumulated, totalReserves); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.borrowIndexNew) = mulScalarTruncateAddUInt(vars.simpleInterestFactor, borrowIndex, borrowIndex); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, uint(vars.mathErr)); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* We write the previously calculated values into storage */ - accrualBlockNumber = vars.currentBlockNumber; - borrowIndex = vars.borrowIndexNew; - totalBorrows = vars.totalBorrowsNew; - totalReserves = vars.totalReservesNew; - - /* We emit an AccrueInterest event */ - emit AccrueInterest(vars.interestAccumulated, vars.borrowIndexNew, totalBorrows); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sender supplies assets into the market and receives cTokens in exchange - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param mintAmount The amount of the underlying asset to supply - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function mintInternal(uint mintAmount) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed - return fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED); - } - // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to - return mintFresh(msg.sender, mintAmount); - } - - struct MintLocalVars { - Error err; - MathError mathErr; - uint exchangeRateMantissa; - uint mintTokens; - uint totalSupplyNew; - uint accountTokensNew; - } - - /** - * @notice User supplies assets into the market and receives cTokens in exchange - * @dev Assumes interest has already been accrued up to the current block - * @param minter The address of the account which is supplying the assets - * @param mintAmount The amount of the underlying asset to supply - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function mintFresh(address minter, uint mintAmount) internal returns (uint) { - /* Fail if mint not allowed */ - uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed); - } - - /* Verify market's block number equals current block number */ - if (accrualBlockNumber != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK); - } - - MintLocalVars memory vars; - - /* Fail if checkTransferIn fails */ - vars.err = checkTransferIn(minter, mintAmount); - if (vars.err != Error.NO_ERROR) { - return fail(vars.err, FailureInfo.MINT_TRANSFER_IN_NOT_POSSIBLE); - } - - /* - * We get the current exchange rate and calculate the number of cTokens to be minted: - * mintTokens = mintAmount / exchangeRate - */ - (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(mintAmount, Exp({mantissa: vars.exchangeRateMantissa})); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - /* - * We calculate the new total supply of cTokens and minter token balance, checking for overflow: - * totalSupplyNew = totalSupply + mintTokens - * accountTokensNew = accountTokens[minter] + mintTokens - */ - (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* - * We call doTransferIn for the minter and the mintAmount - * Note: The cToken must handle variations between ERC-20 and ETH underlying. - * On success, the cToken holds an additional mintAmount of cash. - * If doTransferIn fails despite the fact we checked pre-conditions, - * we revert because we can't be sure if side effects occurred. - */ - vars.err = doTransferIn(minter, mintAmount); - if (vars.err != Error.NO_ERROR) { - return fail(vars.err, FailureInfo.MINT_TRANSFER_IN_FAILED); - } - - /* We write previously calculated values into storage */ - totalSupply = vars.totalSupplyNew; - accountTokens[minter] = vars.accountTokensNew; - - /* We emit a Mint event, and a Transfer event */ - emit Mint(minter, mintAmount, vars.mintTokens); - emit Transfer(address(this), minter, vars.mintTokens); - - /* We call the defense hook */ - comptroller.mintVerify(address(this), minter, mintAmount, vars.mintTokens); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sender redeems cTokens in exchange for the underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemTokens The number of cTokens to redeem into underlying - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeemInternal(uint redeemTokens) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed - return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); - } - // redeemFresh emits redeem-specific logs on errors, so we don't need to - return redeemFresh(msg.sender, redeemTokens, 0); - } - - /** - * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset - * @dev Accrues interest whether or not the operation succeeds, unless reverted - * @param redeemAmount The amount of underlying to redeem - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeemUnderlyingInternal(uint redeemAmount) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed - return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); - } - // redeemFresh emits redeem-specific logs on errors, so we don't need to - return redeemFresh(msg.sender, 0, redeemAmount); - } - - struct RedeemLocalVars { - Error err; - MathError mathErr; - uint exchangeRateMantissa; - uint redeemTokens; - uint redeemAmount; - uint totalSupplyNew; - uint accountTokensNew; - } - - /** - * @notice User redeems cTokens in exchange for the underlying asset - * @dev Assumes interest has already been accrued up to the current block - * @param redeemer The address of the account which is redeeming the tokens - * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be zero) - * @param redeemAmountIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be zero) - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) { - require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); - - RedeemLocalVars memory vars; - - /* exchangeRate = invoke Exchange Rate Stored() */ - (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); - } - - /* If redeemTokensIn > 0: */ - if (redeemTokensIn > 0) { - /* - * We calculate the exchange rate and the amount of underlying to be redeemed: - * redeemTokens = redeemTokensIn - * redeemAmount = redeemTokensIn x exchangeRateCurrent - */ - vars.redeemTokens = redeemTokensIn; - - (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr)); - } - } else { - /* - * We get the current exchange rate and calculate the amount to be redeemed: - * redeemTokens = redeemAmountIn / exchangeRate - * redeemAmount = redeemAmountIn - */ - - (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa})); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr)); - } - - vars.redeemAmount = redeemAmountIn; - } - - /* Fail if redeem not allowed */ - uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed); - } - - /* Verify market's block number equals current block number */ - if (accrualBlockNumber != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK); - } - - /* - * We calculate the new total supply and redeemer balance, checking for underflow: - * totalSupplyNew = totalSupply - redeemTokens - * accountTokensNew = accountTokens[redeemer] - redeemTokens - */ - (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - /* Fail gracefully if protocol has insufficient cash */ - if (getCashPrior() < vars.redeemAmount) { - return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* - * We invoke doTransferOut for the redeemer and the redeemAmount. - * Note: The cToken must handle variations between ERC-20 and ETH underlying. - * On success, the cToken has redeemAmount less of cash. - * If doTransferOut fails despite the fact we checked pre-conditions, - * we revert because we can't be sure if side effects occurred. - */ - vars.err = doTransferOut(redeemer, vars.redeemAmount); - require(vars.err == Error.NO_ERROR, "redeem transfer out failed"); - - /* We write previously calculated values into storage */ - totalSupply = vars.totalSupplyNew; - accountTokens[redeemer] = vars.accountTokensNew; - - /* We emit a Transfer event, and a Redeem event */ - emit Transfer(redeemer, address(this), vars.redeemTokens); - emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); - - /* We call the defense hook */ - comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sender borrows assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed - return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED); - } - // borrowFresh emits borrow-specific logs on errors, so we don't need to - return borrowFresh(msg.sender, borrowAmount); - } - - struct BorrowLocalVars { - Error err; - MathError mathErr; - uint accountBorrows; - uint accountBorrowsNew; - uint totalBorrowsNew; - } - - /** - * @notice Users borrow assets from the protocol to their own address - * @param borrowAmount The amount of the underlying asset to borrow - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function borrowFresh(address payable borrower, uint borrowAmount) internal returns (uint) { - /* Fail if borrow not allowed */ - uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.BORROW_COMPTROLLER_REJECTION, allowed); - } - - /* Verify market's block number equals current block number */ - if (accrualBlockNumber != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.BORROW_FRESHNESS_CHECK); - } - - /* Fail gracefully if protocol has insufficient underlying cash */ - if (getCashPrior() < borrowAmount) { - return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.BORROW_CASH_NOT_AVAILABLE); - } - - BorrowLocalVars memory vars; - - /* - * We calculate the new borrower and total borrow balances, failing on overflow: - * accountBorrowsNew = accountBorrows + borrowAmount - * totalBorrowsNew = totalBorrows + borrowAmount - */ - (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* - * We invoke doTransferOut for the borrower and the borrowAmount. - * Note: The cToken must handle variations between ERC-20 and ETH underlying. - * On success, the cToken borrowAmount less of cash. - * If doTransferOut fails despite the fact we checked pre-conditions, - * we revert because we can't be sure if side effects occurred. - */ - vars.err = doTransferOut(borrower, borrowAmount); - require(vars.err == Error.NO_ERROR, "borrow transfer out failed"); - - /* We write the previously calculated values into storage */ - accountBorrows[borrower].principal = vars.accountBorrowsNew; - accountBorrows[borrower].interestIndex = borrowIndex; - totalBorrows = vars.totalBorrowsNew; - - /* We emit a Borrow event */ - emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); - - /* We call the defense hook */ - comptroller.borrowVerify(address(this), borrower, borrowAmount); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sender repays their own borrow - * @param repayAmount The amount to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed - return fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED); - } - // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - return repayBorrowFresh(msg.sender, msg.sender, repayAmount); - } - - /** - * @notice Sender repays a borrow belonging to borrower - * @param borrower the account with the debt being payed off - * @param repayAmount The amount to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed - return fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED); - } - // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to - return repayBorrowFresh(msg.sender, borrower, repayAmount); - } - - struct RepayBorrowLocalVars { - Error err; - MathError mathErr; - uint repayAmount; - uint borrowerIndex; - uint accountBorrows; - uint accountBorrowsNew; - uint totalBorrowsNew; - } - - /** - * @notice Borrows are repaid by another user (possibly the borrower). - * @param payer the account paying off the borrow - * @param borrower the account with the debt being payed off - * @param repayAmount the amount of undelrying tokens being returned - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint) { - /* Fail if repayBorrow not allowed */ - uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed); - } - - /* Verify market's block number equals current block number */ - if (accrualBlockNumber != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK); - } - - RepayBorrowLocalVars memory vars; - - /* We remember the original borrowerIndex for verification purposes */ - vars.borrowerIndex = accountBorrows[borrower].interestIndex; - - /* We fetch the amount the borrower owes, with accumulated interest */ - (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - /* If repayAmount == -1, repayAmount = accountBorrows */ - if (repayAmount == uint(-1)) { - vars.repayAmount = vars.accountBorrows; - } else { - vars.repayAmount = repayAmount; - } - - /* Fail if checkTransferIn fails */ - vars.err = checkTransferIn(payer, vars.repayAmount); - if (vars.err != Error.NO_ERROR) { - return fail(vars.err, FailureInfo.REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE); - } - - /* - * We calculate the new borrower and total borrow balances, failing on underflow: - * accountBorrowsNew = accountBorrows - repayAmount - * totalBorrowsNew = totalBorrows - repayAmount - */ - (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.repayAmount); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.repayAmount); - if (vars.mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* - * We call doTransferIn for the payer and the repayAmount - * Note: The cToken must handle variations between ERC-20 and ETH underlying. - * On success, the cToken holds an additional repayAmount of cash. - * If doTransferIn fails despite the fact we checked pre-conditions, - * we revert because we can't be sure if side effects occurred. - */ - vars.err = doTransferIn(payer, vars.repayAmount); - require(vars.err == Error.NO_ERROR, "repay borrow transfer in failed"); - - /* We write the previously calculated values into storage */ - accountBorrows[borrower].principal = vars.accountBorrowsNew; - accountBorrows[borrower].interestIndex = borrowIndex; - totalBorrows = vars.totalBorrowsNew; - - /* We emit a RepayBorrow event */ - emit RepayBorrow(payer, borrower, vars.repayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); - - /* We call the defense hook */ - comptroller.repayBorrowVerify(address(this), payer, borrower, vars.repayAmount, vars.borrowerIndex); - - return uint(Error.NO_ERROR); - } - - /** - * @notice The sender liquidates the borrowers collateral. - * The collateral seized is transferred to the liquidator. - * @param borrower The borrower of this cToken to be liquidated - * @param cTokenCollateral The market in which to seize collateral from the borrower - * @param repayAmount The amount of the underlying borrowed asset to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function liquidateBorrowInternal(address borrower, uint repayAmount, CToken cTokenCollateral) internal nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed - return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED); - } - - error = cTokenCollateral.accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed - return fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED); - } - - // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to - return liquidateBorrowFresh(msg.sender, borrower, repayAmount, cTokenCollateral); - } - - /** - * @notice The liquidator liquidates the borrowers collateral. - * The collateral seized is transferred to the liquidator. - * @param borrower The borrower of this cToken to be liquidated - * @param liquidator The address repaying the borrow and seizing collateral - * @param cTokenCollateral The market in which to seize collateral from the borrower - * @param repayAmount The amount of the underlying borrowed asset to repay - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function liquidateBorrowFresh(address liquidator, address borrower, uint repayAmount, CToken cTokenCollateral) internal returns (uint) { - /* Fail if liquidate not allowed */ - uint allowed = comptroller.liquidateBorrowAllowed(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed); - } - - /* Verify market's block number equals current block number */ - if (accrualBlockNumber != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK); - } - - /* Verify cTokenCollateral market's block number equals current block number */ - if (cTokenCollateral.accrualBlockNumber() != getBlockNumber()) { - return fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK); - } - - /* Fail if borrower = liquidator */ - if (borrower == liquidator) { - return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER); - } - - /* Fail if repayAmount = 0 */ - if (repayAmount == 0) { - return fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO); - } - - /* Fail if repayAmount = -1 */ - if (repayAmount == uint(-1)) { - return fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX); - } - - /* We calculate the number of collateral tokens that will be seized */ - (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(address(this), address(cTokenCollateral), repayAmount); - if (amountSeizeError != 0) { - return failOpaque(Error.COMPTROLLER_CALCULATION_ERROR, FailureInfo.LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED, amountSeizeError); - } - - /* Fail if seizeTokens > borrower collateral token balance */ - if (seizeTokens > cTokenCollateral.balanceOf(borrower)) { - return fail(Error.TOKEN_INSUFFICIENT_BALANCE, FailureInfo.LIQUIDATE_SEIZE_TOO_MUCH); - } - - /* Fail if repayBorrow fails */ - uint repayBorrowError = repayBorrowFresh(liquidator, borrower, repayAmount); - if (repayBorrowError != uint(Error.NO_ERROR)) { - return fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED); - } - - /* Revert if seize tokens fails (since we cannot be sure of side effects) */ - uint seizeError = cTokenCollateral.seize(liquidator, borrower, seizeTokens); - require(seizeError == uint(Error.NO_ERROR), "token seizure failed"); - - /* We emit a LiquidateBorrow event */ - emit LiquidateBorrow(liquidator, borrower, repayAmount, address(cTokenCollateral), seizeTokens); - - /* We call the defense hook */ - comptroller.liquidateBorrowVerify(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount, seizeTokens); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Transfers collateral tokens (this market) to the liquidator. - * @dev Will fail unless called by another cToken during the process of liquidation. - * Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter. - * @param liquidator The account receiving seized collateral - * @param borrower The account having collateral seized - * @param seizeTokens The number of cTokens to seize - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function seize(address liquidator, address borrower, uint seizeTokens) external nonReentrant returns (uint) { - /* Fail if seize not allowed */ - uint allowed = comptroller.seizeAllowed(address(this), msg.sender, liquidator, borrower, seizeTokens); - if (allowed != 0) { - return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed); - } - - /* Fail if borrower = liquidator */ - if (borrower == liquidator) { - return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER); - } - - MathError mathErr; - uint borrowerTokensNew; - uint liquidatorTokensNew; - - /* - * We calculate the new borrower and liquidator token balances, failing on underflow/overflow: - * borrowerTokensNew = accountTokens[borrower] - seizeTokens - * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens - */ - (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens); - if (mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr)); - } - - (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens); - if (mathErr != MathError.NO_ERROR) { - return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr)); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - /* We write the previously calculated values into storage */ - accountTokens[borrower] = borrowerTokensNew; - accountTokens[liquidator] = liquidatorTokensNew; - - /* Emit a Transfer event */ - emit Transfer(borrower, liquidator, seizeTokens); - - /* We call the defense hook */ - comptroller.seizeVerify(address(this), msg.sender, liquidator, borrower, seizeTokens); - - return uint(Error.NO_ERROR); - } - - - /*** Admin Functions ***/ - - /** - * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. - * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. - * @param newPendingAdmin New pending admin. - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - * - * TODO: Should we add a second arg to verify, like a checksum of `newAdmin` address? - */ - function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { - // Check caller = admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK); - } - - // Save current value, if any, for inclusion in log - address oldPendingAdmin = pendingAdmin; - - // Store pendingAdmin with value newPendingAdmin - pendingAdmin = newPendingAdmin; - - // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) - emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin - * @dev Admin function for pending admin to accept role and update admin - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _acceptAdmin() external returns (uint) { - // Check caller is pendingAdmin and pendingAdmin ≠ address(0) - if (msg.sender != pendingAdmin || msg.sender == address(0)) { - return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK); - } - - // Save current values for inclusion in log - address oldAdmin = admin; - address oldPendingAdmin = pendingAdmin; - - // Store admin with value pendingAdmin - admin = pendingAdmin; - - // Clear the pending value - pendingAdmin = address(0); - - emit NewAdmin(oldAdmin, admin); - emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Sets a new comptroller for the market - * @dev Admin function to set a new comptroller - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setComptroller(ComptrollerInterface newComptroller) public returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_COMPTROLLER_OWNER_CHECK); - } - - ComptrollerInterface oldComptroller = comptroller; - // Ensure invoke comptroller.isComptroller() returns true - require(newComptroller.isComptroller(), "marker method returned false"); - - // Set market's comptroller to newComptroller - comptroller = newComptroller; - - // Emit NewComptroller(oldComptroller, newComptroller) - emit NewComptroller(oldComptroller, newComptroller); - - return uint(Error.NO_ERROR); - } - - /** - * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh - * @dev Admin function to accrue interest and set a new reserve factor - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setReserveFactor(uint newReserveFactorMantissa) external nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed. - return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED); - } - // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to. - return _setReserveFactorFresh(newReserveFactorMantissa); - } - - /** - * @notice Sets a new reserve factor for the protocol (*requires fresh interest accrual) - * @dev Admin function to set a new reserve factor - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) { - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_RESERVE_FACTOR_ADMIN_CHECK); - } - - // Verify market's block number equals current block number - if (accrualBlockNumber != getBlockNumber()) { - // TODO: static_assert + no error code? - return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK); - } - - // Check newReserveFactor ≤ maxReserveFactor - if (newReserveFactorMantissa > reserveFactorMaxMantissa) { - return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK); - } - - uint oldReserveFactorMantissa = reserveFactorMantissa; - reserveFactorMantissa = newReserveFactorMantissa; - - emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa); - - return uint(Error.NO_ERROR); - } - - /** - * @notice Accrues interest and reduces reserves by transferring to admin - * @param reduceAmount Amount of reduction to reserves - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _reduceReserves(uint reduceAmount) external nonReentrant returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed. - return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED); - } - // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to. - return _reduceReservesFresh(reduceAmount); - } - - /** - * @notice Reduces reserves by transferring to admin - * @dev Requires fresh interest accrual - * @param reduceAmount Amount of reduction to reserves - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _reduceReservesFresh(uint reduceAmount) internal returns (uint) { - Error err; - // totalReserves - reduceAmount - uint totalReservesNew; - - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.REDUCE_RESERVES_ADMIN_CHECK); - } - - // We fail gracefully unless market's block number equals current block number - if (accrualBlockNumber != getBlockNumber()) { - // TODO: static_assert + no error code? - return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK); - } - - // Fail gracefully if protocol has insufficient underlying cash - if (getCashPrior() < reduceAmount) { - return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE); - } - - // Check reduceAmount ≤ reserves[n] (totalReserves) - // TODO: I'm following the spec literally here but I think we should we just use SafeMath instead and fail on an error (which would be underflow) - if (reduceAmount > totalReserves) { - return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION); - } - - ///////////////////////// - // EFFECTS & INTERACTIONS - // (No safe failures beyond this point) - - totalReservesNew = totalReserves - reduceAmount; - // We checked reduceAmount <= totalReserves above, so this should never revert. - require(totalReservesNew <= totalReserves, "reduce reserves unexpected underflow"); - - // Store reserves[n+1] = reserves[n] - reduceAmount - totalReserves = totalReservesNew; - - // invoke doTransferOut(reduceAmount, admin) - err = doTransferOut(admin, reduceAmount); - // we revert on the failure of this command - require(err == Error.NO_ERROR, "reduce reserves transfer out failed"); - - emit ReservesReduced(admin, reduceAmount, totalReservesNew); - - return uint(Error.NO_ERROR); - } - - /** - * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh - * @dev Admin function to accrue interest and update the interest rate model - * @param newInterestRateModel the new interest rate model to use - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { - uint error = accrueInterest(); - if (error != uint(Error.NO_ERROR)) { - // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed - return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED); - } - // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. - return _setInterestRateModelFresh(newInterestRateModel); - } - - /** - * @notice updates the interest rate model (*requires fresh interest accrual) - * @dev Admin function to update the interest rate model - * @param newInterestRateModel the new interest rate model to use - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ - function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal returns (uint) { - - // Used to store old model for use in the event that is emitted on success - InterestRateModel oldInterestRateModel; - - // Check caller is admin - if (msg.sender != admin) { - return fail(Error.UNAUTHORIZED, FailureInfo.SET_INTEREST_RATE_MODEL_OWNER_CHECK); - } - - // We fail gracefully unless market's block number equals current block number - if (accrualBlockNumber != getBlockNumber()) { - // TODO: static_assert + no error code? - return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK); - } - - // Track the market's current interest rate model - oldInterestRateModel = interestRateModel; - - // Ensure invoke newInterestRateModel.isInterestRateModel() returns true - require(newInterestRateModel.isInterestRateModel(), "marker method returned false"); - - // Set the interest rate model to newInterestRateModel - interestRateModel = newInterestRateModel; - - // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel) - emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel); - - return uint(Error.NO_ERROR); - } - - /*** Safe Token ***/ - - /** - * @notice Gets balance of this contract in terms of the underlying - * @dev This excludes the value of the current message, if any - * @return The quantity of underlying owned by this contract - */ - function getCashPrior() internal view returns (uint); - - /** - * @dev Checks whether or not there is sufficient allowance for this contract to move amount from `from` and - * whether or not `from` has a balance of at least `amount`. Does NOT do a transfer. - */ - function checkTransferIn(address from, uint amount) internal view returns (Error); - - /** - * @dev Performs a transfer in, ideally returning an explanatory error code upon failure rather than reverting. - * If caller has not called `checkTransferIn`, this may revert due to insufficient balance or insufficient allowance. - * If caller has called `checkTransferIn` successfully, this should not revert in normal conditions. - */ - function doTransferIn(address from, uint amount) internal returns (Error); - - /** - * @dev Performs a transfer out, ideally returning an explanatory error code upon failure tather than reverting. - * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract. - * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions. - */ - function doTransferOut(address payable to, uint amount) internal returns (Error); -} diff --git a/lib/compound/CarefulMath.sol b/lib/compound/CarefulMath.sol deleted file mode 100644 index d41fb1e05..000000000 --- a/lib/compound/CarefulMath.sol +++ /dev/null @@ -1,85 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title Careful Math - * @author Compound - * @notice Derived from OpenZeppelin's SafeMath library - * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol - */ -contract CarefulMath { - - /** - * @dev Possible error codes that we can return - */ - enum MathError { - NO_ERROR, - DIVISION_BY_ZERO, - INTEGER_OVERFLOW, - INTEGER_UNDERFLOW - } - - /** - * @dev Multiplies two numbers, returns an error on overflow. - */ - function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { - if (a == 0) { - return (MathError.NO_ERROR, 0); - } - - uint c = a * b; - - if (c / a != b) { - return (MathError.INTEGER_OVERFLOW, 0); - } else { - return (MathError.NO_ERROR, c); - } - } - - /** - * @dev Integer division of two numbers, truncating the quotient. - */ - function divUInt(uint a, uint b) internal pure returns (MathError, uint) { - if (b == 0) { - return (MathError.DIVISION_BY_ZERO, 0); - } - - return (MathError.NO_ERROR, a / b); - } - - /** - * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). - */ - function subUInt(uint a, uint b) internal pure returns (MathError, uint) { - if (b <= a) { - return (MathError.NO_ERROR, a - b); - } else { - return (MathError.INTEGER_UNDERFLOW, 0); - } - } - - /** - * @dev Adds two numbers, returns an error on overflow. - */ - function addUInt(uint a, uint b) internal pure returns (MathError, uint) { - uint c = a + b; - - if (c >= a) { - return (MathError.NO_ERROR, c); - } else { - return (MathError.INTEGER_OVERFLOW, 0); - } - } - - /** - * @dev add a and b and then subtract c - */ - function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { - (MathError err0, uint sum) = addUInt(a, b); - - if (err0 != MathError.NO_ERROR) { - return (err0, 0); - } - - return subUInt(sum, c); - } -} \ No newline at end of file diff --git a/lib/compound/ComptrollerInterface.sol b/lib/compound/ComptrollerInterface.sol deleted file mode 100644 index 581cd8bb9..000000000 --- a/lib/compound/ComptrollerInterface.sol +++ /dev/null @@ -1,75 +0,0 @@ -pragma solidity ^0.5.4; - -interface ComptrollerInterface { - /** - * @notice Marker function used for light validation when updating the comptroller of a market - * @dev Implementations should simply return true. - * @return true - */ - function isComptroller() external view returns (bool); - - /*** Assets You Are In ***/ - - function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); - function exitMarket(address cToken) external returns (uint); - - /*** Policy Hooks ***/ - - function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint); - function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external; - - function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint); - function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external; - - function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint); - function borrowVerify(address cToken, address borrower, uint borrowAmount) external; - - function repayBorrowAllowed( - address cToken, - address payer, - address borrower, - uint repayAmount) external returns (uint); - function repayBorrowVerify( - address cToken, - address payer, - address borrower, - uint repayAmount, - uint borrowerIndex) external; - - function liquidateBorrowAllowed( - address cTokenBorrowed, - address cTokenCollateral, - address liquidator, - address borrower, - uint repayAmount) external returns (uint); - function liquidateBorrowVerify( - address cTokenBorrowed, - address cTokenCollateral, - address liquidator, - address borrower, - uint repayAmount, - uint seizeTokens) external; - - function seizeAllowed( - address cTokenCollateral, - address cTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens) external returns (uint); - function seizeVerify( - address cTokenCollateral, - address cTokenBorrowed, - address liquidator, - address borrower, - uint seizeTokens) external; - - function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint); - function transferVerify(address cToken, address src, address dst, uint transferTokens) external; - - /*** Liquidity/Liquidation Calculations ***/ - - function liquidateCalculateSeizeTokens( - address cTokenBorrowed, - address cTokenCollateral, - uint repayAmount) external view returns (uint, uint); -} diff --git a/lib/compound/ComptrollerStorage.sol b/lib/compound/ComptrollerStorage.sol deleted file mode 100644 index a997864f3..000000000 --- a/lib/compound/ComptrollerStorage.sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CToken.sol"; -import "./PriceOracle.sol"; - -contract UnitrollerAdminStorage { - /** - * @notice Administrator for this contract - */ - address public admin; - - /** - * @notice Pending administrator for this contract - */ - address public pendingAdmin; - - /** - * @notice Active brains of Unitroller - */ - address public comptrollerImplementation; - - /** - * @notice Pending brains of Unitroller - */ - address public pendingComptrollerImplementation; -} - -contract ComptrollerV1Storage is UnitrollerAdminStorage { - - /** - * @notice Oracle which gives the price of any given asset - */ - PriceOracle public oracle; - - /** - * @notice Multiplier used to calculate the maximum repayAmount when liquidating a borrow - */ - uint public closeFactorMantissa; - - /** - * @notice Multiplier representing the discount on collateral that a liquidator receives - */ - uint public liquidationIncentiveMantissa; - - /** - * @notice Max number of assets a single account can participate in (borrow or use as collateral) - */ - uint public maxAssets; - - /** - * @notice Per-account mapping of "assets you are in", capped by maxAssets - */ - mapping(address => CToken[]) public accountAssets; - -} diff --git a/lib/compound/EIP20Interface.sol b/lib/compound/EIP20Interface.sol deleted file mode 100644 index 62e14391f..000000000 --- a/lib/compound/EIP20Interface.sol +++ /dev/null @@ -1,60 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title ERC 20 Token Standard Interface - * https://eips.ethereum.org/EIPS/eip-20 - */ -interface EIP20Interface { - - /** - * @notice Get the total number of tokens in circulation - * @return The supply of tokens - */ - function totalSupply() external view returns (uint256); - - /** - * @notice Gets the balance of the specified address - * @param owner The address from which the balance will be retrieved - * @return The balance - */ - function balanceOf(address owner) external view returns (uint256 balance); - - /** - * @notice Transfer `amount` tokens from `msg.sender` to `dst` - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - * @return Whether or not the transfer succeeded - */ - function transfer(address dst, uint256 amount) external returns (bool success); - - /** - * @notice Transfer `amount` tokens from `src` to `dst` - * @param src The address of the source account - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - * @return Whether or not the transfer succeeded - */ - function transferFrom(address src, address dst, uint256 amount) external returns (bool success); - - /** - * @notice Approve `spender` to transfer up to `amount` from `src` - * @dev This will overwrite the approval amount for `spender` - * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) - * @param spender The address of the account which may transfer tokens - * @param amount The number of tokens that are approved (-1 means infinite) - * @return Whether or not the approval succeeded - */ - function approve(address spender, uint256 amount) external returns (bool success); - - /** - * @notice Get the current allowance from `owner` for `spender` - * @param owner The address of the account which owns the tokens to be spent - * @param spender The address of the account which may transfer tokens - * @return The number of tokens allowed to be spent (-1 means infinite) - */ - function allowance(address owner, address spender) external view returns (uint256 remaining); - - // XXX docs/verify - event Transfer(address indexed from, address indexed to, uint256 amount); - event Approval(address indexed owner, address indexed spender, uint256 amount); -} diff --git a/lib/compound/EIP20NonStandardInterface.sol b/lib/compound/EIP20NonStandardInterface.sol deleted file mode 100644 index 7a5dc13aa..000000000 --- a/lib/compound/EIP20NonStandardInterface.sol +++ /dev/null @@ -1,70 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title EIP20NonStandardInterface - * @dev Version of ERC20 with no return values for `transfer` and `transferFrom` - * See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca - */ -interface EIP20NonStandardInterface { - - /** - * @notice Get the total number of tokens in circulation - * @return The supply of tokens - */ - function totalSupply() external view returns (uint256); - - /** - * @notice Gets the balance of the specified address - * @param owner The address from which the balance will be retrieved - * @return The balance - */ - function balanceOf(address owner) external view returns (uint256 balance); - - /// - /// !!!!!!!!!!!!!! - /// !!! NOTICE !!! `transfer` does not return a value, in violation of the ERC-20 specification - /// !!!!!!!!!!!!!! - /// - - /** - * @notice Transfer `amount` tokens from `msg.sender` to `dst` - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - */ - function transfer(address dst, uint256 amount) external; - - /// - /// !!!!!!!!!!!!!! - /// !!! NOTICE !!! `transferFrom` does not return a value, in violation of the ERC-20 specification - /// !!!!!!!!!!!!!! - /// - - /** - * @notice Transfer `amount` tokens from `src` to `dst` - * @param src The address of the source account - * @param dst The address of the destination account - * @param amount The number of tokens to transfer - */ - function transferFrom(address src, address dst, uint256 amount) external; - - /** - * @notice Approve `spender` to transfer up to `amount` from `src` - * @dev This will overwrite the approval amount for `spender` - * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) - * @param spender The address of the account which may transfer tokens - * @param amount The number of tokens that are approved - * @return Whether or not the approval succeeded - */ - function approve(address spender, uint256 amount) external returns (bool success); - - /** - * @notice Get the current allowance from `owner` for `spender` - * @param owner The address of the account which owns the tokens to be spent - * @param spender The address of the account which may transfer tokens - * @return The number of tokens allowed to be spent - */ - function allowance(address owner, address spender) external view returns (uint256 remaining); - - event Transfer(address indexed from, address indexed to, uint256 amount); - event Approval(address indexed owner, address indexed spender, uint256 amount); -} diff --git a/lib/compound/ErrorReporter.sol b/lib/compound/ErrorReporter.sol deleted file mode 100644 index 6d031a8b3..000000000 --- a/lib/compound/ErrorReporter.sol +++ /dev/null @@ -1,204 +0,0 @@ -pragma solidity ^0.5.4; - -contract ComptrollerErrorReporter { - enum Error { - NO_ERROR, - UNAUTHORIZED, - COMPTROLLER_MISMATCH, - INSUFFICIENT_SHORTFALL, - INSUFFICIENT_LIQUIDITY, - INVALID_CLOSE_FACTOR, - INVALID_COLLATERAL_FACTOR, - INVALID_LIQUIDATION_INCENTIVE, - MARKET_NOT_ENTERED, - MARKET_NOT_LISTED, - MARKET_ALREADY_LISTED, - MATH_ERROR, - NONZERO_BORROW_BALANCE, - PRICE_ERROR, - REJECTION, - SNAPSHOT_ERROR, - TOO_MANY_ASSETS, - TOO_MUCH_REPAY - } - - enum FailureInfo { - ACCEPT_ADMIN_PENDING_ADMIN_CHECK, - ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK, - EXIT_MARKET_BALANCE_OWED, - EXIT_MARKET_REJECTION, - SET_CLOSE_FACTOR_OWNER_CHECK, - SET_CLOSE_FACTOR_VALIDATION, - SET_COLLATERAL_FACTOR_OWNER_CHECK, - SET_COLLATERAL_FACTOR_NO_EXISTS, - SET_COLLATERAL_FACTOR_VALIDATION, - SET_COLLATERAL_FACTOR_WITHOUT_PRICE, - SET_IMPLEMENTATION_OWNER_CHECK, - SET_LIQUIDATION_INCENTIVE_OWNER_CHECK, - SET_LIQUIDATION_INCENTIVE_VALIDATION, - SET_MAX_ASSETS_OWNER_CHECK, - SET_PENDING_ADMIN_OWNER_CHECK, - SET_PENDING_IMPLEMENTATION_OWNER_CHECK, - SET_PRICE_ORACLE_OWNER_CHECK, - SUPPORT_MARKET_EXISTS, - SUPPORT_MARKET_OWNER_CHECK, - ZUNUSED - } - - /** - * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary - * contract-specific code that enables us to report opaque error codes from upgradeable contracts. - **/ - event Failure(uint error, uint info, uint detail); - - /** - * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator - */ - function fail(Error err, FailureInfo info) internal returns (uint) { - emit Failure(uint(err), uint(info), 0); - - return uint(err); - } - - /** - * @dev use this when reporting an opaque error from an upgradeable collaborator contract - */ - function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { - emit Failure(uint(err), uint(info), opaqueError); - - return uint(err); - } -} - -contract TokenErrorReporter { - enum Error { - NO_ERROR, - UNAUTHORIZED, - BAD_INPUT, - COMPTROLLER_REJECTION, - COMPTROLLER_CALCULATION_ERROR, - INTEREST_RATE_MODEL_ERROR, - INVALID_ACCOUNT_PAIR, - INVALID_CLOSE_AMOUNT_REQUESTED, - INVALID_COLLATERAL_FACTOR, - MATH_ERROR, - MARKET_NOT_FRESH, - MARKET_NOT_LISTED, - TOKEN_INSUFFICIENT_ALLOWANCE, - TOKEN_INSUFFICIENT_BALANCE, - TOKEN_INSUFFICIENT_CASH, - TOKEN_TRANSFER_IN_FAILED, - TOKEN_TRANSFER_OUT_FAILED - } - - /* - * Note: FailureInfo (but not Error) is kept in alphabetical order - * This is because FailureInfo grows significantly faster, and - * the order of Error has some meaning, while the order of FailureInfo - * is entirely arbitrary. - */ - enum FailureInfo { - ACCEPT_ADMIN_PENDING_ADMIN_CHECK, - ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, - ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED, - ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, - ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, - ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, - ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, - BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, - BORROW_ACCRUE_INTEREST_FAILED, - BORROW_CASH_NOT_AVAILABLE, - BORROW_FRESHNESS_CHECK, - BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, - BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, - BORROW_MARKET_NOT_LISTED, - BORROW_COMPTROLLER_REJECTION, - LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED, - LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED, - LIQUIDATE_COLLATERAL_FRESHNESS_CHECK, - LIQUIDATE_COMPTROLLER_REJECTION, - LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED, - LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX, - LIQUIDATE_CLOSE_AMOUNT_IS_ZERO, - LIQUIDATE_FRESHNESS_CHECK, - LIQUIDATE_LIQUIDATOR_IS_BORROWER, - LIQUIDATE_REPAY_BORROW_FRESH_FAILED, - LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, - LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, - LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, - LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER, - LIQUIDATE_SEIZE_TOO_MUCH, - MINT_ACCRUE_INTEREST_FAILED, - MINT_COMPTROLLER_REJECTION, - MINT_EXCHANGE_CALCULATION_FAILED, - MINT_EXCHANGE_RATE_READ_FAILED, - MINT_FRESHNESS_CHECK, - MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, - MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, - MINT_TRANSFER_IN_FAILED, - MINT_TRANSFER_IN_NOT_POSSIBLE, - REDEEM_ACCRUE_INTEREST_FAILED, - REDEEM_COMPTROLLER_REJECTION, - REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, - REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, - REDEEM_EXCHANGE_RATE_READ_FAILED, - REDEEM_FRESHNESS_CHECK, - REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, - REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, - REDEEM_TRANSFER_OUT_NOT_POSSIBLE, - REDUCE_RESERVES_ACCRUE_INTEREST_FAILED, - REDUCE_RESERVES_ADMIN_CHECK, - REDUCE_RESERVES_CASH_NOT_AVAILABLE, - REDUCE_RESERVES_FRESH_CHECK, - REDUCE_RESERVES_VALIDATION, - REPAY_BEHALF_ACCRUE_INTEREST_FAILED, - REPAY_BORROW_ACCRUE_INTEREST_FAILED, - REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, - REPAY_BORROW_COMPTROLLER_REJECTION, - REPAY_BORROW_FRESHNESS_CHECK, - REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, - REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, - REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE, - SET_COLLATERAL_FACTOR_OWNER_CHECK, - SET_COLLATERAL_FACTOR_VALIDATION, - SET_COMPTROLLER_OWNER_CHECK, - SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED, - SET_INTEREST_RATE_MODEL_FRESH_CHECK, - SET_INTEREST_RATE_MODEL_OWNER_CHECK, - SET_MAX_ASSETS_OWNER_CHECK, - SET_ORACLE_MARKET_NOT_LISTED, - SET_PENDING_ADMIN_OWNER_CHECK, - SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED, - SET_RESERVE_FACTOR_ADMIN_CHECK, - SET_RESERVE_FACTOR_FRESH_CHECK, - SET_RESERVE_FACTOR_BOUNDS_CHECK, - TRANSFER_COMPTROLLER_REJECTION, - TRANSFER_NOT_ALLOWED, - TRANSFER_NOT_ENOUGH, - TRANSFER_TOO_MUCH - } - - /** - * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary - * contract-specific code that enables us to report opaque error codes from upgradeable contracts. - **/ - event Failure(uint error, uint info, uint detail); - - /** - * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator - */ - function fail(Error err, FailureInfo info) internal returns (uint) { - emit Failure(uint(err), uint(info), 0); - - return uint(err); - } - - /** - * @dev use this when reporting an opaque error from an upgradeable collaborator contract - */ - function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { - emit Failure(uint(err), uint(info), opaqueError); - - return uint(err); - } -} diff --git a/lib/compound/Exponential.sol b/lib/compound/Exponential.sol deleted file mode 100644 index 9d93d5732..000000000 --- a/lib/compound/Exponential.sol +++ /dev/null @@ -1,219 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CarefulMath.sol"; - -/** - * @title Exponential module for storing fixed-decision decimals - * @author Compound - * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places. - * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: - * `Exp({mantissa: 5100000000000000000})`. - */ -contract Exponential is CarefulMath { - uint constant expScale = 1e18; - uint constant halfExpScale = expScale/2; - uint constant mantissaOne = expScale; - - struct Exp { - uint mantissa; - } - - /** - * @dev Creates an exponential from numerator and denominator values. - * Note: Returns an error if (`num` * 10e18) > MAX_INT, - * or if `denom` is zero. - */ - function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) { - (MathError err0, uint scaledNumerator) = mulUInt(num, expScale); - if (err0 != MathError.NO_ERROR) { - return (err0, Exp({mantissa: 0})); - } - - (MathError err1, uint rational) = divUInt(scaledNumerator, denom); - if (err1 != MathError.NO_ERROR) { - return (err1, Exp({mantissa: 0})); - } - - return (MathError.NO_ERROR, Exp({mantissa: rational})); - } - - /** - * @dev Adds two exponentials, returning a new exponential. - */ - function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { - (MathError error, uint result) = addUInt(a.mantissa, b.mantissa); - - return (error, Exp({mantissa: result})); - } - - /** - * @dev Subtracts two exponentials, returning a new exponential. - */ - function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { - (MathError error, uint result) = subUInt(a.mantissa, b.mantissa); - - return (error, Exp({mantissa: result})); - } - - /** - * @dev Multiply an Exp by a scalar, returning a new Exp. - */ - function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { - (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar); - if (err0 != MathError.NO_ERROR) { - return (err0, Exp({mantissa: 0})); - } - - return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa})); - } - - /** - * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. - */ - function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) { - (MathError err, Exp memory product) = mulScalar(a, scalar); - if (err != MathError.NO_ERROR) { - return (err, 0); - } - - return (MathError.NO_ERROR, truncate(product)); - } - - /** - * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. - */ - function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) { - (MathError err, Exp memory product) = mulScalar(a, scalar); - if (err != MathError.NO_ERROR) { - return (err, 0); - } - - return addUInt(truncate(product), addend); - } - - /** - * @dev Divide an Exp by a scalar, returning a new Exp. - */ - function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { - (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar); - if (err0 != MathError.NO_ERROR) { - return (err0, Exp({mantissa: 0})); - } - - return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa})); - } - - /** - * @dev Divide a scalar by an Exp, returning a new Exp. - */ - function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) { - /* - We are doing this as: - getExp(mulUInt(expScale, scalar), divisor.mantissa) - - How it works: - Exp = a / b; - Scalar = s; - `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale` - */ - (MathError err0, uint numerator) = mulUInt(expScale, scalar); - if (err0 != MathError.NO_ERROR) { - return (err0, Exp({mantissa: 0})); - } - return getExp(numerator, divisor.mantissa); - } - - /** - * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer. - */ - function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) { - (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor); - if (err != MathError.NO_ERROR) { - return (err, 0); - } - - return (MathError.NO_ERROR, truncate(fraction)); - } - - /** - * @dev Multiplies two exponentials, returning a new exponential. - */ - function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { - - (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa); - if (err0 != MathError.NO_ERROR) { - return (err0, Exp({mantissa: 0})); - } - - // We add half the scale before dividing so that we get rounding instead of truncation. - // See "Listing 6" and text above it at https://accu.org/index.php/journals/1717 - // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18. - (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct); - if (err1 != MathError.NO_ERROR) { - return (err1, Exp({mantissa: 0})); - } - - (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale); - // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero. - assert(err2 == MathError.NO_ERROR); - - return (MathError.NO_ERROR, Exp({mantissa: product})); - } - - /** - * @dev Multiplies two exponentials given their mantissas, returning a new exponential. - */ - function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) { - return mulExp(Exp({mantissa: a}), Exp({mantissa: b})); - } - - /** - * @dev Multiplies three exponentials, returning a new exponential. - */ - function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) { - (MathError err, Exp memory ab) = mulExp(a, b); - if (err != MathError.NO_ERROR) { - return (err, ab); - } - return mulExp(ab, c); - } - - /** - * @dev Divides two exponentials, returning a new exponential. - * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b, - * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa) - */ - function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { - return getExp(a.mantissa, b.mantissa); - } - - /** - * @dev Truncates the given exp to a whole number value. - * For example, truncate(Exp{mantissa: 15 * expScale}) = 15 - */ - function truncate(Exp memory exp) pure internal returns (uint) { - // Note: We are not using careful math here as we're performing a division that cannot fail - return exp.mantissa / expScale; - } - - /** - * @dev Checks if first Exp is less than second Exp. - */ - function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { - return left.mantissa < right.mantissa; //TODO: Add some simple tests and this in another PR yo. - } - - /** - * @dev Checks if left Exp <= right Exp. - */ - function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) { - return left.mantissa <= right.mantissa; - } - - /** - * @dev returns true if Exp is exactly zero - */ - function isZeroExp(Exp memory value) pure internal returns (bool) { - return value.mantissa == 0; - } -} diff --git a/lib/compound/InterestRateModel.sol b/lib/compound/InterestRateModel.sol deleted file mode 100644 index a4b4d09d5..000000000 --- a/lib/compound/InterestRateModel.sol +++ /dev/null @@ -1,29 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title The Compound InterestRateModel Interface - * @author Compound - * @notice Any interest rate model should derive from this contract. - * @dev These functions are specifically not marked `pure` as implementations of this - * contract may read from storage variables. - */ -interface InterestRateModel { - /** - * @notice Gets the current borrow interest rate based on the given asset, total cash, total borrows - * and total reserves. - * @dev The return value should be scaled by 1e18, thus a return value of - * `(true, 1000000000000)` implies an interest rate of 0.000001 or 0.0001% *per block*. - * @param cash The total cash of the underlying asset in the CToken - * @param borrows The total borrows of the underlying asset in the CToken - * @param reserves The total reserves of the underlying asset in the CToken - * @return Success or failure and the borrow interest rate per block scaled by 10e18 - */ - function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint, uint); - - /** - * @notice Marker function used for light validation when updating the interest rate model of a market - * @dev Marker function used for light validation when updating the interest rate model of a market. Implementations should simply return true. - * @return Success or failure - */ - function isInterestRateModel() external view returns (bool); -} \ No newline at end of file diff --git a/lib/compound/Maximillion.sol b/lib/compound/Maximillion.sol deleted file mode 100644 index 8896f8770..000000000 --- a/lib/compound/Maximillion.sol +++ /dev/null @@ -1,49 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CEther.sol"; - -/** - * @title Compound's Maximillion Contract - * @author Compound - */ -contract Maximillion { - /** - * @notice The default cEther market to repay in - */ - CEther public cEther; - - /** - * @notice Construct a Maximillion to repay max in a CEther market - */ - constructor(CEther cEther_) public { - cEther = cEther_; - } - - /** - * @notice msg.sender sends Ether to repay an account's borrow in the cEther market - * @dev The provided Ether is applied towards the borrow balance, any excess is refunded - * @param borrower The address of the borrower account to repay on behalf of - * @return The initial borrows before the repay - */ - function repayBehalf(address borrower) public payable { - return repayBehalfExplicit(borrower, cEther); - } - - /** - * @notice msg.sender sends Ether to repay an account's borrow in a cEther market - * @dev The provided Ether is applied towards the borrow balance, any excess is refunded - * @param borrower The address of the borrower account to repay on behalf of - * @param cEther_ The address of the cEther contract to repay in - * @return The initial borrows before the repay - */ - function repayBehalfExplicit(address borrower, CEther cEther_) public payable { - uint received = msg.value; - uint borrows = cEther_.borrowBalanceCurrent(borrower); - if (received > borrows) { - cEther_.repayBorrowBehalf.value(borrows)(borrower); - msg.sender.transfer(received - borrows); - } else { - cEther_.repayBorrowBehalf.value(received)(borrower); - } - } -} diff --git a/lib/compound/PriceOracle.sol b/lib/compound/PriceOracle.sol deleted file mode 100644 index 04b9b90eb..000000000 --- a/lib/compound/PriceOracle.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CToken.sol"; - -interface PriceOracle { - /** - * @notice Indicator that this is a PriceOracle contract (for inspection) - */ - function isPriceOracle() external pure returns (bool); - - /** - * @notice Get the underlying price of a cToken asset - * @param cToken The cToken to get the underlying price of - * @return The underlying asset price mantissa (scaled by 1e18). - * Zero means the price is unavailable. - */ - function getUnderlyingPrice(CToken cToken) external view returns (uint); -} diff --git a/lib/compound/PriceOracleProxy.sol b/lib/compound/PriceOracleProxy.sol deleted file mode 100644 index b9278fb09..000000000 --- a/lib/compound/PriceOracleProxy.sol +++ /dev/null @@ -1,74 +0,0 @@ -pragma solidity ^0.5.4; - -import "./CErc20.sol"; -import "./CToken.sol"; -import "./PriceOracle.sol"; -import "./Comptroller.sol"; - -interface V1PriceOracleInterface { - function assetPrices(address asset) external view returns (uint); -} - -contract PriceOracleProxy is PriceOracle { - /** - * @notice The v1 price oracle, which will continue to serve prices - * prices for v1 assets - */ - V1PriceOracleInterface public v1PriceOracle; - - /** - * @notice The active comptroller, which will be checked for listing status - * to short circuit and return 0 for unlisted assets. - * - * @dev Listed markets are not part of the comptroller interface used by - * cTokens, so we assumena an instance of v1 comptroller.sol instead - */ - Comptroller public comptroller; - - /** - * @notice address of the cEther contract, which has a constant price - */ - address public cEtherAddress; - - /** - * @notice Indicator that this is a PriceOracle contract (for inspection) - */ - bool public constant isPriceOracle = true; - - /** - * @param comptroller_ The address of the active comptroller, which will - * be consulted for market listing status. - * @param v1PriceOracle_ The address of the v1 price oracle, which will - * continue to operate and hold prices for collateral assets. - * @param cEtherAddress_ The address of the cEther contract, which will - * return a constant 1e18, since all prices relative to ether - */ - constructor(address comptroller_, address v1PriceOracle_, address cEtherAddress_) public { - comptroller = Comptroller(comptroller_); - v1PriceOracle = V1PriceOracleInterface(v1PriceOracle_); - cEtherAddress = cEtherAddress_; - } - - /** - * @notice Get the underlying price of a listed cToken asset - * @param cToken The cToken to get the underlying price of - * @return The underlying asset price mantissa (scaled by 1e18). - * Zero means the price is unavailable. - */ - function getUnderlyingPrice(CToken cToken) public view returns (uint) { - address cTokenAddress = address(cToken); - (bool isListed, ) = comptroller.markets(cTokenAddress); - - if (!isListed) { - // not listed, worthless - return 0; - } else if (cTokenAddress == cEtherAddress) { - // ether always worth 1 - return 1e18; - } else { - // read from v1 oracle - address underlying = CErc20(cTokenAddress).underlying(); - return v1PriceOracle.assetPrices(underlying); - } - } -} diff --git a/lib/compound/ReentrancyGuard.sol b/lib/compound/ReentrancyGuard.sol deleted file mode 100644 index 35209e34d..000000000 --- a/lib/compound/ReentrancyGuard.sol +++ /dev/null @@ -1,32 +0,0 @@ -pragma solidity ^0.5.4; - -/** - * @title Helps contracts guard against reentrancy attacks. - * @author Remco Bloemen , Eenae - * @dev If you mark a function `nonReentrant`, you should also - * mark it `external`. - */ -contract ReentrancyGuard { - /// @dev counter to allow mutex lock with only one SSTORE operation - uint256 private _guardCounter; - - constructor () internal { - // The counter starts at one to prevent changing it from zero to a non-zero - // value, which is a more expensive operation. - _guardCounter = 1; - } - - /** - * @dev Prevents a contract from calling itself, directly or indirectly. - * Calling a `nonReentrant` function from another `nonReentrant` - * function is not supported. It is possible to prevent this from happening - * by making the `nonReentrant` function external, and make it call a - * `private` function that does the actual work. - */ - modifier nonReentrant() { - _guardCounter += 1; - uint256 localCounter = _guardCounter; - _; - require(localCounter == _guardCounter, "re-entered"); - } -} diff --git a/lib/compound/SimplePriceOracle.sol b/lib/compound/SimplePriceOracle.sol deleted file mode 100644 index 60b018cc8..000000000 --- a/lib/compound/SimplePriceOracle.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.5.4; - -import "./PriceOracle.sol"; -import "./CErc20.sol"; - -contract SimplePriceOracle is PriceOracle { - mapping(address => uint) prices; - bool public constant isPriceOracle = true; - - function getUnderlyingPrice(CToken cToken) public view returns (uint) { - return prices[address(CErc20(address(cToken)).underlying())]; - } - - function setUnderlyingPrice(CToken cToken, uint underlyingPriceMantissa) public { - prices[address(CErc20(address(cToken)).underlying())] = underlyingPriceMantissa; - } - - // v1 price oracle interface for use as backing of proxy - function assetPrices(address asset) external view returns (uint) { - return prices[asset]; - } -} diff --git a/lib/compound/WhitePaperInterestRateModel.sol b/lib/compound/WhitePaperInterestRateModel.sol deleted file mode 100644 index 2a6eded3e..000000000 --- a/lib/compound/WhitePaperInterestRateModel.sol +++ /dev/null @@ -1,133 +0,0 @@ -pragma solidity ^0.5.4; - -import "./Exponential.sol"; -import "./InterestRateModel.sol"; - -/** - * @title The Compound Standard Interest Rate Model with pluggable constants - * @author Compound - * @notice See Section 2.4 of the Compound Whitepaper - */ -contract WhitePaperInterestRateModel is InterestRateModel, Exponential { - /** - * @notice Indicator that this is an InterestRateModel contract (for inspection) - */ - bool public constant isInterestRateModel = true; - - /** - * @notice The multiplier of utilization rate that gives the slope of the interest rate - */ - uint public multiplier; - - /** - * @notice The base interest rate which is the y-intercept when utilization rate is 0 - */ - uint public baseRate; - - /** - * @notice The approximate number of blocks per year that is assumed by the interest rate model - */ - uint public constant blocksPerYear = 2102400; - - constructor(uint baseRate_, uint multiplier_) public { - baseRate = baseRate_; - multiplier = multiplier_; - } - - enum IRError { - NO_ERROR, - FAILED_TO_ADD_CASH_PLUS_BORROWS, - FAILED_TO_GET_EXP, - FAILED_TO_MUL_UTILIZATION_RATE, - FAILED_TO_ADD_BASE_RATE - } - - /* - * @dev Calculates the utilization rate (borrows / (cash + borrows)) as an Exp - */ - function getUtilizationRate(uint cash, uint borrows) pure internal returns (IRError, Exp memory) { - if (borrows == 0) { - // Utilization rate is zero when there's no borrows - return (IRError.NO_ERROR, Exp({mantissa: 0})); - } - - (MathError err0, uint cashPlusBorrows) = addUInt(cash, borrows); - if (err0 != MathError.NO_ERROR) { - return (IRError.FAILED_TO_ADD_CASH_PLUS_BORROWS, Exp({mantissa: 0})); - } - - (MathError err1, Exp memory utilizationRate) = getExp(borrows, cashPlusBorrows); - if (err1 != MathError.NO_ERROR) { - return (IRError.FAILED_TO_GET_EXP, Exp({mantissa: 0})); - } - - return (IRError.NO_ERROR, utilizationRate); - } - - /* - * @dev Calculates the utilization and borrow rates for use by getBorrowRate function - */ - function getUtilizationAndAnnualBorrowRate(uint cash, uint borrows) view internal returns (IRError, Exp memory, Exp memory) { - (IRError err0, Exp memory utilizationRate) = getUtilizationRate(cash, borrows); - if (err0 != IRError.NO_ERROR) { - return (err0, Exp({mantissa: 0}), Exp({mantissa: 0})); - } - - // Borrow Rate is 5% + UtilizationRate * 45% (baseRate + UtilizationRate * multiplier); - // 45% of utilizationRate, is `rate * 45 / 100` - (MathError err1, Exp memory utilizationRateMuled) = mulScalar(utilizationRate, multiplier); - // `mulScalar` only overflows when the product is >= 2^256. - // utilizationRate is a real number on the interval [0,1], which means that - // utilizationRate.mantissa is in the interval [0e18,1e18], which means that 45 times - // that is in the interval [0e18,45e18]. That interval has no intersection with 2^256, and therefore - // this can never overflow for the standard rates. - if (err1 != MathError.NO_ERROR) { - return (IRError.FAILED_TO_MUL_UTILIZATION_RATE, Exp({mantissa: 0}), Exp({mantissa: 0})); - } - - (MathError err2, Exp memory utilizationRateScaled) = divScalar(utilizationRateMuled, mantissaOne); - // 100 is a constant, and therefore cannot be zero, which is the only error case of divScalar. - assert(err2 == MathError.NO_ERROR); - - // Add the 5% for (5% + 45% * Ua) - (MathError err3, Exp memory annualBorrowRate) = addExp(utilizationRateScaled, Exp({mantissa: baseRate})); - // `addExp` only fails when the addition of mantissas overflow. - // As per above, utilizationRateMuled is capped at 45e18, - // and utilizationRateScaled is capped at 4.5e17. mantissaFivePercent = 0.5e17, and thus the addition - // is capped at 5e17, which is less than 2^256. This only applies to the standard rates - if (err3 != MathError.NO_ERROR) { - return (IRError.FAILED_TO_ADD_BASE_RATE, Exp({mantissa: 0}), Exp({mantissa: 0})); - } - - return (IRError.NO_ERROR, utilizationRate, annualBorrowRate); - } - - /** - * @notice Gets the current borrow interest rate based on the given asset, total cash, total borrows - * and total reserves. - * @dev The return value should be scaled by 1e18, thus a return value of - * `(true, 1000000000000)` implies an interest rate of 0.000001 or 0.0001% *per block*. - * @param cash The total cash of the underlying asset in the CToken - * @param borrows The total borrows of the underlying asset in the CToken - * @param _reserves The total reserves of the underlying asset in the CToken - * @return Success or failure and the borrow interest rate per block scaled by 10e18 - */ - function getBorrowRate(uint cash, uint borrows, uint _reserves) public view returns (uint, uint) { - _reserves; // pragma ignore unused argument - - (IRError err0, Exp memory _utilizationRate, Exp memory annualBorrowRate) = getUtilizationAndAnnualBorrowRate(cash, borrows); - if (err0 != IRError.NO_ERROR) { - return (uint(err0), 0); - } - - // And then divide down by blocks per year. - (MathError err1, Exp memory borrowRate) = divScalar(annualBorrowRate, blocksPerYear); // basis points * blocks per year - // divScalar only fails when divisor is zero. This is clearly not the case. - assert(err1 == MathError.NO_ERROR); - - _utilizationRate; // pragma ignore unused variable - - // Note: mantissa is the rate scaled 1e18, which matches the expected result - return (uint(IRError.NO_ERROR), borrowRate.mantissa); - } -} diff --git a/lib/ens/ENS.sol b/lib/ens/ENS.sol deleted file mode 100644 index b04853220..000000000 --- a/lib/ens/ENS.sol +++ /dev/null @@ -1,37 +0,0 @@ -pragma solidity >=0.4.24; - -/** - * ENS Registry interface. - * Reference: https://github.com/ensdomains/ens/blob/master/contracts/ENS.sol - */ - -interface ENS { - - // Logged when the owner of a node assigns a new owner to a subnode. - event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); - - // Logged when the owner of a node transfers ownership to a new account. - event Transfer(bytes32 indexed node, address owner); - - // Logged when the resolver for a node changes. - event NewResolver(bytes32 indexed node, address resolver); - - // Logged when the TTL of a node changes - event NewTTL(bytes32 indexed node, uint64 ttl); - - // Logged when an operator is added or removed. - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external; - function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external; - function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external returns(bytes32); - function setResolver(bytes32 node, address resolver) external; - function setOwner(bytes32 node, address owner) external; - function setTTL(bytes32 node, uint64 ttl) external; - function setApprovalForAll(address operator, bool approved) external; - function owner(bytes32 node) external view returns (address); - function resolver(bytes32 node) external view returns (address); - function ttl(bytes32 node) external view returns (uint64); - function recordExists(bytes32 node) external view returns (bool); - function isApprovedForAll(address owner, address operator) external view returns (bool); -} \ No newline at end of file diff --git a/lib/ens/ENSRegistry.sol b/lib/ens/ENSRegistry.sol deleted file mode 100644 index 37e559a58..000000000 --- a/lib/ens/ENSRegistry.sol +++ /dev/null @@ -1,180 +0,0 @@ -/** - * ENS Registry implementation. - * Reference: https://github.com/ensdomains/ens/blob/master/contracts/ENSRegistry.sol - */ - -pragma solidity ^0.5.0; - -import "./ENS.sol"; - -contract ENSRegistry is ENS { - - struct Record { - address owner; - address resolver; - uint64 ttl; - } - - mapping (bytes32 => Record) records; - mapping (address => mapping(address => bool)) operators; - - // Permits modifications only by the owner of the specified node. - modifier authorised(bytes32 node) { - address owner = records[node].owner; - require(owner == msg.sender || operators[owner][msg.sender]); - _; - } - - /** - * @dev Constructs a new ENS registrar. - */ - constructor() public { - records[0x0].owner = msg.sender; - } - - /** - * @dev Sets the record for a node. - * @param node The node to update. - * @param owner The address of the new owner. - * @param resolver The address of the resolver. - * @param ttl The TTL in seconds. - */ - function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external { - setOwner(node, owner); - _setResolverAndTTL(node, resolver, ttl); - } - - /** - * @dev Sets the record for a subnode. - * @param node The parent node. - * @param label The hash of the label specifying the subnode. - * @param owner The address of the new owner. - * @param resolver The address of the resolver. - * @param ttl The TTL in seconds. - */ - function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external { - bytes32 subnode = setSubnodeOwner(node, label, owner); - _setResolverAndTTL(subnode, resolver, ttl); - } - - /** - * @dev Transfers ownership of a node to a new address. May only be called by the current owner of the node. - * @param node The node to transfer ownership of. - * @param owner The address of the new owner. - */ - function setOwner(bytes32 node, address owner) public authorised(node) { - _setOwner(node, owner); - emit Transfer(node, owner); - } - - /** - * @dev Transfers ownership of a subnode keccak256(node, label) to a new address. May only be called by the owner of the parent node. - * @param node The parent node. - * @param label The hash of the label specifying the subnode. - * @param owner The address of the new owner. - */ - function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public authorised(node) returns(bytes32) { - bytes32 subnode = keccak256(abi.encodePacked(node, label)); - _setOwner(subnode, owner); - emit NewOwner(node, label, owner); - return subnode; - } - - /** - * @dev Sets the resolver address for the specified node. - * @param node The node to update. - * @param resolver The address of the resolver. - */ - function setResolver(bytes32 node, address resolver) public authorised(node) { - emit NewResolver(node, resolver); - records[node].resolver = resolver; - } - - /** - * @dev Sets the TTL for the specified node. - * @param node The node to update. - * @param ttl The TTL in seconds. - */ - function setTTL(bytes32 node, uint64 ttl) public authorised(node) { - emit NewTTL(node, ttl); - records[node].ttl = ttl; - } - - /** - * @dev Enable or disable approval for a third party ("operator") to manage - * all of `msg.sender`'s ENS records. Emits the ApprovalForAll event. - * @param operator Address to add to the set of authorized operators. - * @param approved True if the operator is approved, false to revoke approval. - */ - function setApprovalForAll(address operator, bool approved) external { - operators[msg.sender][operator] = approved; - emit ApprovalForAll(msg.sender, operator, approved); - } - - /** - * @dev Returns the address that owns the specified node. - * @param node The specified node. - * @return address of the owner. - */ - function owner(bytes32 node) public view returns (address) { - address addr = records[node].owner; - if (addr == address(this)) { - return address(0x0); - } - - return addr; - } - - /** - * @dev Returns the address of the resolver for the specified node. - * @param node The specified node. - * @return address of the resolver. - */ - function resolver(bytes32 node) public view returns (address) { - return records[node].resolver; - } - - /** - * @dev Returns the TTL of a node, and any records associated with it. - * @param node The specified node. - * @return ttl of the node. - */ - function ttl(bytes32 node) public view returns (uint64) { - return records[node].ttl; - } - - /** - * @dev Returns whether a record has been imported to the registry. - * @param node The specified node. - * @return Bool if record exists - */ - function recordExists(bytes32 node) public view returns (bool) { - return records[node].owner != address(0x0); - } - - /** - * @dev Query if an address is an authorized operator for another address. - * @param owner The address that owns the records. - * @param operator The address that acts on behalf of the owner. - * @return True if `operator` is an approved operator for `owner`, false otherwise. - */ - function isApprovedForAll(address owner, address operator) external view returns (bool) { - return operators[owner][operator]; - } - - function _setOwner(bytes32 node, address owner) internal { - records[node].owner = owner; - } - - function _setResolverAndTTL(bytes32 node, address resolver, uint64 ttl) internal { - if(resolver != records[node].resolver) { - records[node].resolver = resolver; - emit NewResolver(node, resolver); - } - - if(ttl != records[node].ttl) { - records[node].ttl = ttl; - emit NewTTL(node, ttl); - } - } -} \ No newline at end of file diff --git a/lib/ens/ENSRegistryWithFallback.sol b/lib/ens/ENSRegistryWithFallback.sol deleted file mode 100644 index 2c7880338..000000000 --- a/lib/ens/ENSRegistryWithFallback.sol +++ /dev/null @@ -1,69 +0,0 @@ -/** - * ENS Registry with fallback interface. - * Reference: https://github.com/ensdomains/ens/blob/master/contracts/ENSRegistryWithFallback.sol - */ - -pragma solidity ^0.5.0; - -import "./ENS.sol"; -import "./ENSRegistry.sol"; - -contract ENSRegistryWithFallback is ENSRegistry { - - ENS public old; - - /** - * @dev Constructs a new ENS registrar. - */ - constructor(ENS _old) public ENSRegistry() { - old = _old; - } - - /** - * @dev Returns the address of the resolver for the specified node. - * @param node The specified node. - * @return address of the resolver. - */ - function resolver(bytes32 node) public view returns (address) { - if (!recordExists(node)) { - return old.resolver(node); - } - - return super.resolver(node); - } - - /** - * @dev Returns the address that owns the specified node. - * @param node The specified node. - * @return address of the owner. - */ - function owner(bytes32 node) public view returns (address) { - if (!recordExists(node)) { - return old.owner(node); - } - - return super.owner(node); - } - - /** - * @dev Returns the TTL of a node, and any records associated with it. - * @param node The specified node. - * @return ttl of the node. - */ - function ttl(bytes32 node) public view returns (uint64) { - if (!recordExists(node)) { - return old.ttl(node); - } - - return super.ttl(node); - } - - function _setOwner(bytes32 node, address owner) internal { - address addr = owner; - if (addr == address(0x0)) { - addr = address(this); - } - - super._setOwner(node, addr); - } -} \ No newline at end of file diff --git a/lib/ens/ReverseRegistrar.sol b/lib/ens/ReverseRegistrar.sol deleted file mode 100644 index a0fef6aaf..000000000 --- a/lib/ens/ReverseRegistrar.sol +++ /dev/null @@ -1,124 +0,0 @@ -/** - * ENS ReverseRegistrar implementation. - * Reference: https://github.com/ensdomains/ens/blob/master/contracts/ReverseRegistrar.sol - */ - -pragma solidity ^0.5.0; - -import "./ENS.sol"; - -contract Resolver { - function setName(bytes32 node, string memory name) public; -} - -contract ReverseRegistrar { - // namehash('addr.reverse') - bytes32 public constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; - - ENS public ens; - Resolver public defaultResolver; - - /** - * @dev Constructor - * @param ensAddr The address of the ENS registry. - * @param resolverAddr The address of the default reverse resolver. - */ - constructor(ENS ensAddr, Resolver resolverAddr) public { - ens = ensAddr; - defaultResolver = resolverAddr; - - // Assign ownership of the reverse record to our deployer - ReverseRegistrar oldRegistrar = ReverseRegistrar(ens.owner(ADDR_REVERSE_NODE)); - if (address(oldRegistrar) != address(0x0)) { - oldRegistrar.claim(msg.sender); - } - } - - /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. - * @param owner The address to set as the owner of the reverse record in ENS. - * @return The ENS node hash of the reverse record. - */ - function claim(address owner) public returns (bytes32) { - return claimWithResolver(owner, address(0x0)); - } - - /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. - * @param owner The address to set as the owner of the reverse record in ENS. - * @param resolver The address of the resolver to set; 0 to leave unchanged. - * @return The ENS node hash of the reverse record. - */ - function claimWithResolver(address owner, address resolver) public returns (bytes32) { - bytes32 label = sha3HexAddress(msg.sender); - bytes32 node = keccak256(abi.encodePacked(ADDR_REVERSE_NODE, label)); - address currentOwner = ens.owner(node); - - // Update the resolver if required - if (resolver != address(0x0) && resolver != ens.resolver(node)) { - // Transfer the name to us first if it's not already - if (currentOwner != address(this)) { - ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, address(this)); - currentOwner = address(this); - } - ens.setResolver(node, resolver); - } - - // Update the owner if required - if (currentOwner != owner) { - ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); - } - - return node; - } - - /** - * @dev Sets the `name()` record for the reverse ENS record associated with - * the calling account. First updates the resolver to the default reverse - * resolver if necessary. - * @param name The name to set for this address. - * @return The ENS node hash of the reverse record. - */ - function setName(string memory name) public returns (bytes32) { - bytes32 node = claimWithResolver(address(this), address(defaultResolver)); - defaultResolver.setName(node, name); - return node; - } - - /** - * @dev Returns the node hash for a given account's reverse records. - * @param addr The address to hash - * @return The ENS node hash. - */ - function node(address addr) public pure returns (bytes32) { - return keccak256(abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr))); - } - - /** - * @dev An optimised function to compute the sha3 of the lower-case - * hexadecimal representation of an Ethereum address. - * @param addr The address to hash - * @return The SHA3 hash of the lower-case hexadecimal encoding of the - * input address. - */ - function sha3HexAddress(address addr) private pure returns (bytes32 ret) { - addr; - ret; // Stop warning us about unused variables - assembly { - let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000 - - for { let i := 40 } gt(i, 0) { } { - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - } - - ret := keccak256(0, 40) - } - } -} \ No newline at end of file diff --git a/lib/maker/DS/DSNote.sol b/lib/maker/DS/DSNote.sol deleted file mode 100644 index 2c18a96c9..000000000 --- a/lib/maker/DS/DSNote.sol +++ /dev/null @@ -1,41 +0,0 @@ -/// note.sol -- the `note' modifier, for logging calls as events - -// 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 . - -pragma solidity ^0.5.4; - -contract DSNote { - event LogNote( - bytes4 indexed sig, - address indexed guy, - bytes32 indexed foo, - bytes32 indexed bar, - uint wad, - bytes fax - ) anonymous; - - modifier note { - bytes32 foo; - bytes32 bar; - - assembly { - foo := calldataload(4) - bar := calldataload(36) - } - - emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data); - - _; - } -} diff --git a/lib/maker/DS/DSValue.sol b/lib/maker/DS/DSValue.sol deleted file mode 100644 index e9678e48a..000000000 --- a/lib/maker/DS/DSValue.sol +++ /dev/null @@ -1,40 +0,0 @@ -/// value.sol - a value is a simple thing, it can be get and set - -// Copyright (C) 2017 DappHub, LLC - -// 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 . - -pragma solidity ^0.5.4; - -import "./DSThing.sol"; - -contract DSValue is DSThing { - bool has; - bytes32 val; - function peek() public view returns (bytes32, bool) { - return (val,has); - } - function read() public view returns (bytes32) { - (bytes32 wut, bool haz) = peek(); - assert(haz); - return wut; - } - function poke(bytes32 wut) public payable note auth { - val = wut; - has = true; - } - function void() public payable note auth { // unset the value - has = false; - } -} diff --git a/lib/maker/SaiTub.sol b/lib/maker/SaiTub.sol deleted file mode 100644 index 450b8559c..000000000 --- a/lib/maker/SaiTub.sol +++ /dev/null @@ -1,350 +0,0 @@ -/// tub.sol -- simplified CDP engine (baby brother of `vat') - -// Copyright (C) 2017 Nikolai Mushegian -// Copyright (C) 2017 Daniel Brockman -// Copyright (C) 2017 Rain Break - -// 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 . - -pragma solidity ^0.5.4; - -import "./DS/DSThing.sol"; -import "./DS/DSToken.sol"; -import "./DS/DSValue.sol"; -import "../other/ERC20.sol"; - -import "./SaiVox.sol"; - -contract SaiTubEvents { - event LogNewCup(address indexed lad, bytes32 cup); -} - -contract SaiTub is DSThing, SaiTubEvents { - DSToken public sai; // Stablecoin - DSToken public sin; // Debt (negative sai) - - DSToken public skr; // Abstracted collateral - ERC20 public gem; // Underlying collateral - - DSToken public gov; // Governance token - - SaiVox public vox; // Target price feed - DSValue public pip; // Reference price feed - DSValue public pep; // Governance price feed - - address public tap; // Liquidator - address public pit; // Governance Vault - - uint256 public axe; // Liquidation penalty - uint256 public cap; // Debt ceiling - uint256 public mat; // Liquidation ratio - uint256 public tax; // Stability fee - uint256 public fee; // Governance fee - uint256 public gap; // Join-Exit Spread - - bool public off; // Cage flag - bool public out; // Post cage exit - - uint256 public fit; // REF per SKR (just before settlement) - - uint256 public rho; // Time of last drip - uint256 _chi; // Accumulated Tax Rates - uint256 _rhi; // Accumulated Tax + Fee Rates - uint256 public rum; // Total normalised debt - - uint256 public cupi; - mapping (bytes32 => Cup) public cups; - - struct Cup { - address lad; // CDP owner - uint256 ink; // Locked collateral (in SKR) - uint256 art; // Outstanding normalised debt (tax only) - uint256 ire; // Outstanding normalised debt - } - - function lad(bytes32 cup) public view returns (address) { - return cups[cup].lad; - } - function ink(bytes32 cup) public view returns (uint) { - return cups[cup].ink; - } - function tab(bytes32 cup) public returns (uint) { - return rmul(cups[cup].art, chi()); - } - function rap(bytes32 cup) public returns (uint) { - return sub(rmul(cups[cup].ire, rhi()), tab(cup)); - } - - // Total CDP Debt - function din() public returns (uint) { - return rmul(rum, chi()); - } - // Backing collateral - function air() public view returns (uint) { - return skr.balanceOf(address(this)); - } - // Raw collateral - function pie() public view returns (uint) { - return gem.balanceOf(address(this)); - } - - //------------------------------------------------------------------ - - constructor( - DSToken sai_, - DSToken sin_, - DSToken skr_, - ERC20 gem_, - DSToken gov_, - DSValue pip_, - DSValue pep_, - SaiVox vox_, - address pit_ - ) public { - gem = gem_; - skr = skr_; - - sai = sai_; - sin = sin_; - - gov = gov_; - pit = pit_; - - pip = pip_; - pep = pep_; - vox = vox_; - - axe = RAY; - mat = RAY; - tax = RAY; - fee = RAY; - gap = WAD; - - _chi = RAY; - _rhi = RAY; - - rho = era(); - } - - function era() public view returns (uint) { - return block.timestamp; - } - - //--Risk-parameter-config------------------------------------------- - - function mold(bytes32 param, uint val) public payable note auth { - if (param == 'cap') cap = val; - else if (param == 'mat') { require(val >= RAY); mat = val; } - else if (param == 'tax') { require(val >= RAY); drip(); tax = val; } - else if (param == 'fee') { require(val >= RAY); drip(); fee = val; } - else if (param == 'axe') { require(val >= RAY); axe = val; } - else if (param == 'gap') { require(val >= WAD); gap = val; } - else return; - } - - //--Price-feed-setters---------------------------------------------- - - function setPip(DSValue pip_) public payable note auth { - pip = pip_; - } - function setPep(DSValue pep_) public payable note auth { - pep = pep_; - } - function setVox(SaiVox vox_) public payable note auth { - vox = vox_; - } - - //--Tap-setter------------------------------------------------------ - function turn(address tap_) public payable note { - require(tap == address(0)); - require(tap_ != address(0)); - tap = tap_; - } - - //--Collateral-wrapper---------------------------------------------- - - // Wrapper ratio (gem per skr) - function per() public view returns (uint ray) { - return skr.totalSupply() == 0 ? RAY : rdiv(pie(), skr.totalSupply()); - } - // Join price (gem per skr) - function ask(uint wad) public view returns (uint) { - return rmul(wad, wmul(per(), gap)); - } - // Exit price (gem per skr) - function bid(uint wad) public view returns (uint) { - return rmul(wad, wmul(per(), sub(2 * WAD, gap))); - } - function join(uint wad) public payable note { - require(!off); - require(ask(wad) > 0); - require(gem.transferFrom(msg.sender, address(this), ask(wad))); - skr.mint(msg.sender, wad); - } - function exit(uint wad) public payable note { - require(!off || out); - require(gem.transfer(msg.sender, bid(wad))); - skr.burn(msg.sender, wad); - } - - //--Stability-fee-accumulation-------------------------------------- - - // Accumulated Rates - function chi() public returns (uint) { - drip(); - return _chi; - } - function rhi() public returns (uint) { - drip(); - return _rhi; - } - function drip() public payable note { - if (off) return; - - uint256 rho_ = era(); - uint256 age = rho_ - rho; - if (age == 0) return; // optimised - rho = rho_; - - uint256 inc = RAY; - - if (tax != RAY) { // optimised - uint256 _chi_ = _chi; - inc = rpow(tax, age); - _chi = rmul(_chi, inc); - sai.mint(tap, rmul(sub(_chi, _chi_), rum)); - } - - // optimised - if (fee != RAY) inc = rmul(inc, rpow(fee, age)); - if (inc != RAY) _rhi = rmul(_rhi, inc); - } - - - //--CDP-risk-indicator---------------------------------------------- - - // Abstracted collateral price (ref per skr) - function tag() public view returns (uint wad) { - return off ? fit : wmul(per(), uint(pip.read())); // (ETH/PETH) * (USD/ETH) = USD/PETH - } - // Returns true if cup is well-collateralized - function safe(bytes32 cup) public returns (bool) { - uint256 pro = rmul(tag(), ink(cup)); - uint256 con = rmul(vox.par(), tab(cup)); - uint256 min = rmul(con, mat); - return pro >= min; - } - - - //--CDP-operations-------------------------------------------------- - - function open() public payable note returns (bytes32 cup) { - require(!off); - cupi = add(cupi, 1); - cup = bytes32(cupi); - cups[cup].lad = msg.sender; - emit LogNewCup(msg.sender, cup); - } - function give(bytes32 cup, address guy) public payable note { - require(msg.sender == cups[cup].lad); - require(guy != address(0)); - cups[cup].lad = guy; - } - - function lock(bytes32 cup, uint wad) public payable note { - require(!off); - cups[cup].ink = add(cups[cup].ink, wad); - skr.pull(msg.sender, wad); - require(cups[cup].ink == 0 || cups[cup].ink > 0.005 ether); - } - function free(bytes32 cup, uint wad) public payable note { - require(msg.sender == cups[cup].lad); - cups[cup].ink = sub(cups[cup].ink, wad); - skr.push(msg.sender, wad); - require(safe(cup)); - require(cups[cup].ink == 0 || cups[cup].ink > 0.005 ether); - } - - function draw(bytes32 cup, uint wad) public payable note { - require(!off); - require(msg.sender == cups[cup].lad); - require(rdiv(wad, chi()) > 0); - cups[cup].art = add(cups[cup].art, rdiv(wad, chi())); - rum = add(rum, rdiv(wad, chi())); - - cups[cup].ire = add(cups[cup].ire, rdiv(wad, rhi())); - sai.mint(cups[cup].lad, wad); - - require(safe(cup)); - require(sai.totalSupply() <= cap); - } - function wipe(bytes32 cup, uint wad) public payable note { - require(!off); - - uint256 owe = rmul(wad, rdiv(rap(cup), tab(cup))); - - cups[cup].art = sub(cups[cup].art, rdiv(wad, chi())); - rum = sub(rum, rdiv(wad, chi())); - - cups[cup].ire = sub(cups[cup].ire, rdiv(add(wad, owe), rhi())); - sai.burn(msg.sender, wad); - - (bytes32 val, bool ok) = pep.peek(); - if (ok && val != 0) gov.move(msg.sender, pit, wdiv(owe, uint(val))); - } - - function shut(bytes32 cup) public payable note { - require(!off); - require(msg.sender == cups[cup].lad); - if (tab(cup) != 0) wipe(cup, tab(cup)); - if (ink(cup) != 0) free(cup, ink(cup)); - delete cups[cup]; - } - - function bite(bytes32 cup) public payable note { - require(!safe(cup) || off); - - // Take on all of the debt, except unpaid fees - uint256 rue = tab(cup); - sin.mint(tap, rue); - rum = sub(rum, cups[cup].art); - cups[cup].art = 0; - cups[cup].ire = 0; - - // Amount owed in SKR, including liquidation penalty - uint256 owe = rdiv(rmul(rmul(rue, axe), vox.par()), tag()); - - if (owe > cups[cup].ink) { - owe = cups[cup].ink; - } - - skr.push(tap, owe); - cups[cup].ink = sub(cups[cup].ink, owe); - } - - //------------------------------------------------------------------ - - function cage(uint fit_, uint jam) public payable note auth { - require(!off && fit_ != 0); - off = true; - axe = RAY; - gap = WAD; - fit = fit_; // ref per skr - require(gem.transfer(tap, jam)); - } - function flow() public payable note auth { - require(off); - out = true; - } -} diff --git a/lib/maker/SaiVox.sol b/lib/maker/SaiVox.sol deleted file mode 100644 index 9a6343aee..000000000 --- a/lib/maker/SaiVox.sol +++ /dev/null @@ -1,84 +0,0 @@ -/// vox.sol -- target price feed - -// Copyright (C) 2016, 2017 Nikolai Mushegian -// Copyright (C) 2016, 2017 Daniel Brockman -// Copyright (C) 2017 Rain Break - -// 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 . - -pragma solidity ^0.5.4; - -import "./DS/DSMath.sol"; -import "./DS/DSAuth.sol"; - -contract SaiVox is DSAuth, DSMath { - uint256 _par; - uint256 _way; - - uint256 public fix; - uint256 public how; - uint256 public tau; - - constructor(uint par_) public { - _par = fix = par_; - _way = RAY; - tau = era(); - } - - function era() public view returns (uint) { - return block.timestamp; - } - - function mold(bytes32 param, uint val) public auth { - if (param == 'way') _way = val; - } - - // Dai Target Price (ref per dai) - function par() public returns (uint) { - prod(); - return _par; - } - function way() public returns (uint) { - prod(); - return _way; - } - - function tell(uint256 ray) public auth { - fix = ray; - } - function tune(uint256 ray) public auth { - how = ray; - } - - function prod() public { - uint age = era() - tau; - if (age == 0) return; // optimised - tau = era(); - - if (_way != RAY) _par = rmul(_par, rpow(_way, age)); // optimised - - if (how == 0) return; // optimised - int128 wag = int128(how * age); - _way = inj(prj(_way) + (fix < _par ? wag : -wag)); - } - - function inj(int128 x) internal pure returns (uint256) { - return x >= 0 ? uint256(x) + RAY - : rdiv(RAY, RAY + uint256(-x)); - } - function prj(uint256 x) internal pure returns (int128) { - return x >= RAY ? int128(x - RAY) - : int128(RAY) - int128(rdiv(RAY, x)); - } -} diff --git a/lib/maker/ScdMcdMigration.sol b/lib/maker/ScdMcdMigration.sol deleted file mode 100644 index b012d95e7..000000000 --- a/lib/maker/ScdMcdMigration.sol +++ /dev/null @@ -1,143 +0,0 @@ -pragma solidity ^0.5.4; - -import { JoinLike, ManagerLike, SaiTubLike, VatLike } from "./MakerInterfaces.sol"; - -contract ScdMcdMigration { - SaiTubLike public tub; - VatLike public vat; - ManagerLike public cdpManager; - JoinLike public saiJoin; - JoinLike public wethJoin; - JoinLike public daiJoin; - - constructor( - address tub_, // SCD tub contract address - address cdpManager_, // MCD manager contract address - address saiJoin_, // MCD SAI collateral adapter contract address - address wethJoin_, // MCD ETH collateral adapter contract address - address daiJoin_ // MCD DAI adapter contract address - ) public { - tub = SaiTubLike(tub_); - cdpManager = ManagerLike(cdpManager_); - vat = VatLike(cdpManager.vat()); - saiJoin = JoinLike(saiJoin_); - wethJoin = JoinLike(wethJoin_); - daiJoin = JoinLike(daiJoin_); - - require(wethJoin.gem() == tub.gem(), "non-matching-weth"); - require(saiJoin.gem() == tub.sai(), "non-matching-sai"); - - tub.gov().approve(address(tub), uint(-1)); - tub.skr().approve(address(tub), uint(-1)); - tub.sai().approve(address(tub), uint(-1)); - tub.sai().approve(address(saiJoin), uint(-1)); - wethJoin.gem().approve(address(wethJoin), uint(-1)); - daiJoin.dai().approve(address(daiJoin), uint(-1)); - vat.hope(address(daiJoin)); - } - - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, "add-overflow"); - } - - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, "sub-underflow"); - } - - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, "mul-overflow"); - } - - function toInt(uint x) internal pure returns (int y) { - y = int(x); - require(y >= 0, "int-overflow"); - } - - // Function to swap SAI to DAI - // This function is to be used by users that want to get new DAI in exchange of old one (aka SAI) - // wad amount has to be <= the value pending to reach the debt ceiling (the minimum between general and ilk one) - function swapSaiToDai( - uint wad - ) external { - // Get wad amount of SAI from user's wallet: - saiJoin.gem().transferFrom(msg.sender, address(this), wad); - // Join the SAI wad amount to the `vat`: - saiJoin.join(address(this), wad); - // Lock the SAI wad amount to the CDP and generate the same wad amount of DAI - vat.frob(saiJoin.ilk(), address(this), address(this), address(this), toInt(wad), toInt(wad)); - // Send DAI wad amount as a ERC20 token to the user's wallet - daiJoin.exit(msg.sender, wad); - } - - // Function to swap DAI to SAI - // This function is to be used by users that want to get SAI in exchange of DAI - // wad amount has to be <= the amount of SAI locked (and DAI generated) in the migration contract SAI CDP - function swapDaiToSai( - uint wad - ) external { - // Get wad amount of DAI from user's wallet: - daiJoin.dai().transferFrom(msg.sender, address(this), wad); - // Join the DAI wad amount to the vat: - daiJoin.join(address(this), wad); - // Payback the DAI wad amount and unlocks the same value of SAI collateral - vat.frob(saiJoin.ilk(), address(this), address(this), address(this), -toInt(wad), -toInt(wad)); - // Send SAI wad amount as a ERC20 token to the user's wallet - saiJoin.exit(msg.sender, wad); - } - - // Function to migrate a SCD CDP to MCD one (needs to be used via a proxy so the code can be kept simpler). Check MigrationProxyActions.sol code for usage. - // In order to use migrate function, SCD CDP debtAmt needs to be <= SAI previously deposited in the SAI CDP * (100% - Collateralization Ratio) - function migrate( - bytes32 cup - ) external returns (uint cdp) { - // Get values - uint debtAmt = tub.tab(cup); // CDP SAI debt - uint pethAmt = tub.ink(cup); // CDP locked collateral - uint ethAmt = tub.bid(pethAmt); // CDP locked collateral equiv in ETH - - // Take SAI out from MCD SAI CDP. For this operation is necessary to have a very low collateralization ratio - // This is not actually a problem as this ilk will only be accessed by this migration contract, - // which will make sure to have the amounts balanced out at the end of the execution. - vat.frob( - bytes32(saiJoin.ilk()), - address(this), - address(this), - address(this), - -toInt(debtAmt), - 0 - ); - saiJoin.exit(address(this), debtAmt); // SAI is exited as a token - - // Shut SAI CDP and gets WETH back - tub.shut(cup); // CDP is closed using the SAI just exited and the MKR previously sent by the user (via the proxy call) - tub.exit(pethAmt); // Converts PETH to WETH - - // Open future user's CDP in MCD - cdp = cdpManager.open(wethJoin.ilk(), address(this)); - - // Join WETH to Adapter - wethJoin.join(cdpManager.urns(cdp), ethAmt); - - // Lock WETH in future user's CDP and generate debt to compensate the SAI used to paid the SCD CDP - (, uint rate,,,) = vat.ilks(wethJoin.ilk()); - cdpManager.frob( - cdp, - toInt(ethAmt), - toInt(mul(debtAmt, 10 ** 27) / rate + 1) // To avoid rounding issues we add an extra wei of debt - ); - // Move DAI generated to migration contract (to recover the used funds) - cdpManager.move(cdp, address(this), mul(debtAmt, 10 ** 27)); - // Re-balance MCD SAI migration contract's CDP - vat.frob( - bytes32(saiJoin.ilk()), - address(this), - address(this), - address(this), - 0, - -toInt(debtAmt) - ); - - // Set ownership of CDP to the user - cdpManager.give(cdp, msg.sender); - } -} \ No newline at end of file diff --git a/lib/maker/dai.sol b/lib/maker/dai.sol deleted file mode 100644 index 1af65c449..000000000 --- a/lib/maker/dai.sol +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero 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 Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.4; - -import "./lib.sol"; - -contract Dai is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address guy) external note auth { wards[guy] = 1; } - function deny(address guy) external note auth { wards[guy] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "Dai/not-authorized"); - _; - } - - // --- ERC20 Data --- - string public constant name = "Dai Stablecoin"; - string public constant symbol = "DAI"; - string public constant version = "1"; - uint8 public constant decimals = 18; - uint256 public totalSupply; - - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; - mapping (address => uint) public nonces; - - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - - // --- Math --- - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - - // --- EIP712 niceties --- - bytes32 public DOMAIN_SEPARATOR; - // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); - bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; - - constructor(uint256 chainId_) public { - wards[msg.sender] = 1; - DOMAIN_SEPARATOR = keccak256(abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name)), - keccak256(bytes(version)), - chainId_, - address(this) - )); - } - - // --- Token --- - function transfer(address dst, uint wad) external returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - function transferFrom(address src, address dst, uint wad) - public returns (bool) - { - require(balanceOf[src] >= wad, "Dai/insufficient-balance"); - if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { - require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance"); - allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); - } - balanceOf[src] = sub(balanceOf[src], wad); - balanceOf[dst] = add(balanceOf[dst], wad); - emit Transfer(src, dst, wad); - return true; - } - function mint(address usr, uint wad) external auth { - balanceOf[usr] = add(balanceOf[usr], wad); - totalSupply = add(totalSupply, wad); - emit Transfer(address(0), usr, wad); - } - function burn(address usr, uint wad) external { - require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); - if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) { - require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance"); - allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); - } - balanceOf[usr] = sub(balanceOf[usr], wad); - totalSupply = sub(totalSupply, wad); - emit Transfer(usr, address(0), wad); - } - function approve(address usr, uint wad) external returns (bool) { - allowance[msg.sender][usr] = wad; - emit Approval(msg.sender, usr, wad); - return true; - } - - // --- Alias --- - function push(address usr, uint wad) external { - transferFrom(msg.sender, usr, wad); - } - function pull(address usr, uint wad) external { - transferFrom(usr, msg.sender, wad); - } - function move(address src, address dst, uint wad) external { - transferFrom(src, dst, wad); - } - - // --- Approve by signature --- - function permit(address holder, address spender, uint256 nonce, uint256 expiry, - bool allowed, uint8 v, bytes32 r, bytes32 s) external - { - bytes32 digest = - keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode(PERMIT_TYPEHASH, - holder, - spender, - nonce, - expiry, - allowed)) - )); - - require(holder != address(0), "Dai/invalid-address-0"); - require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit"); - require(expiry == 0 || now <= expiry, "Dai/permit-expired"); - require(nonce == nonces[holder]++, "Dai/invalid-nonce"); - uint wad = allowed ? uint(-1) : 0; - allowance[holder][spender] = wad; - emit Approval(holder, spender, wad); - } -} \ No newline at end of file diff --git a/lib/maker/join.sol b/lib/maker/join.sol deleted file mode 100644 index ead0d6e2e..000000000 --- a/lib/maker/join.sol +++ /dev/null @@ -1,158 +0,0 @@ -/// join.sol -- Basic token adapters - -// Copyright (C) 2018 Rain -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero 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 Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.4; - -import "./lib.sol"; -import "./MakerInterfaces.sol"; - -/* - Here we provide *adapters* to connect the Vat to arbitrary external - token implementations, creating a bounded context for the Vat. The - adapters here are provided as working examples: - - - `GemJoin`: For well behaved ERC20 tokens, with simple transfer - semantics. - - - `ETHJoin`: For native Ether. - - - `DaiJoin`: For connecting internal Dai balances to an external - `DSToken` implementation. - - In practice, adapter implementations will be varied and specific to - individual collateral types, accounting for different transfer - semantics and token standards. - - Adapters need to implement two basic methods: - - - `join`: enter collateral into the system - - `exit`: remove collateral from the system - -*/ - -contract GemJoin is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external note auth { wards[usr] = 1; } - function deny(address usr) external note auth { wards[usr] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "GemJoin/not-authorized"); - _; - } - - VatLike public vat; - bytes32 public ilk; - GemLike public gem; - uint public dec; - uint public live; // Access Flag - - constructor(address vat_, bytes32 ilk_, address gem_) public { - wards[msg.sender] = 1; - live = 1; - vat = VatLike(vat_); - ilk = ilk_; - gem = GemLike(gem_); - dec = gem.decimals(); - } - function cage() external note auth { - live = 0; - } - function join(address usr, uint wad) external note { - require(live == 1, "GemJoin/not-live"); - require(int(wad) >= 0, "GemJoin/overflow"); - vat.slip(ilk, usr, int(wad)); - require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer"); - } - function exit(address usr, uint wad) external note { - require(wad <= 2 ** 255, "GemJoin/overflow"); - vat.slip(ilk, msg.sender, -int(wad)); - require(gem.transfer(usr, wad), "GemJoin/failed-transfer"); - } -} - -contract ETHJoin is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external note auth { wards[usr] = 1; } - function deny(address usr) external note auth { wards[usr] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "ETHJoin/not-authorized"); - _; - } - - VatLike public vat; - bytes32 public ilk; - uint public live; // Access Flag - - constructor(address vat_, bytes32 ilk_) public { - wards[msg.sender] = 1; - live = 1; - vat = VatLike(vat_); - ilk = ilk_; - } - function cage() external note auth { - live = 0; - } - function join(address usr) external payable note { - require(live == 1, "ETHJoin/not-live"); - require(int(msg.value) >= 0, "ETHJoin/overflow"); - vat.slip(ilk, usr, int(msg.value)); - } - function exit(address payable usr, uint wad) external note { - require(int(wad) >= 0, "ETHJoin/overflow"); - vat.slip(ilk, msg.sender, -int(wad)); - usr.transfer(wad); - } -} - -contract DaiJoin is LibNote { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external note auth { wards[usr] = 1; } - function deny(address usr) external note auth { wards[usr] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "DaiJoin/not-authorized"); - _; - } - - VatLike public vat; - DSTokenLike public dai; - uint public live; // Access Flag - - constructor(address vat_, address dai_) public { - wards[msg.sender] = 1; - live = 1; - vat = VatLike(vat_); - dai = DSTokenLike(dai_); - } - function cage() external note auth { - live = 0; - } - uint constant ONE = 10 ** 27; - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x); - } - function join(address usr, uint wad) external note { - vat.move(address(this), usr, mul(ONE, wad)); - dai.burn(msg.sender, wad); - } - function exit(address usr, uint wad) external note { - require(live == 1, "DaiJoin/not-live"); - vat.move(msg.sender, address(this), mul(ONE, wad)); - dai.mint(usr, wad); - } -} \ No newline at end of file diff --git a/lib/maker/lib.sol b/lib/maker/lib.sol deleted file mode 100644 index a600e9cd0..000000000 --- a/lib/maker/lib.sol +++ /dev/null @@ -1,43 +0,0 @@ -// 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 . - -pragma solidity ^0.5.4; - -contract LibNote { - event LogNote( - bytes4 indexed sig, - address indexed usr, - bytes32 indexed arg1, - bytes32 indexed arg2, - bytes data - ) anonymous; - - modifier note { - _; - assembly { - // log an 'anonymous' event with a constant 6 words of calldata - // and four indexed topics: selector, caller, arg1 and arg2 - let mark := msize // end of memory ensures zero - mstore(0x40, add(mark, 288)) // update free memory pointer - mstore(mark, 0x20) // bytes type data offset - mstore(add(mark, 0x20), 224) // bytes size (padded) - calldatacopy(add(mark, 0x40), 0, 224) // bytes payload - log4(mark, 288, // calldata - shl(224, shr(224, calldataload(0))), // msg.sig - caller, // msg.sender - calldataload(4), // arg1 - calldataload(36) // arg2 - ) - } - } -} \ No newline at end of file diff --git a/lib/maker/vat.sol b/lib/maker/vat.sol deleted file mode 100644 index 48ba62119..000000000 --- a/lib/maker/vat.sol +++ /dev/null @@ -1,268 +0,0 @@ -/// vat.sol -- Dai CDP database - -// Copyright (C) 2018 Rain -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero 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 Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -pragma solidity ^0.5.4; - -contract Vat { - // --- Auth --- - mapping (address => uint) public wards; - function rely(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; } - function deny(address usr) external note auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; } - modifier auth { - require(wards[msg.sender] == 1, "Vat/not-authorized"); - _; - } - - mapping(address => mapping (address => uint)) public can; - function hope(address usr) external note { can[msg.sender][usr] = 1; } - function nope(address usr) external note { can[msg.sender][usr] = 0; } - function wish(address bit, address usr) internal view returns (bool) { - return either(bit == usr, can[bit][usr] == 1); - } - - // --- Data --- - struct Ilk { - uint256 Art; // Total Normalised Debt [wad] - uint256 rate; // Accumulated Rates [ray] - uint256 spot; // Price with Safety Margin [ray] - uint256 line; // Debt Ceiling [rad] - uint256 dust; // Urn Debt Floor [rad] - } - struct Urn { - uint256 ink; // Locked Collateral [wad] - uint256 art; // Normalised Debt [wad] - } - - mapping (bytes32 => Ilk) public ilks; - mapping (bytes32 => mapping (address => Urn )) public urns; - mapping (bytes32 => mapping (address => uint)) public gem; // [wad] - mapping (address => uint256) public dai; // [rad] - mapping (address => uint256) public sin; // [rad] - - uint256 public debt; // Total Dai Issued [rad] - uint256 public vice; // Total Unbacked Dai [rad] - uint256 public Line; // Total Debt Ceiling [rad] - uint256 public live; // Access Flag - - // --- Logs --- - event LogNote( - bytes4 indexed sig, - bytes32 indexed arg1, - bytes32 indexed arg2, - bytes32 indexed arg3, - bytes data - ) anonymous; - - modifier note { - _; - assembly { - // log an 'anonymous' event with a constant 6 words of calldata - // and four indexed topics: the selector and the first three args - let mark := msize // end of memory ensures zero - mstore(0x40, add(mark, 288)) // update free memory pointer - mstore(mark, 0x20) // bytes type data offset - mstore(add(mark, 0x20), 224) // bytes size (padded) - calldatacopy(add(mark, 0x40), 0, 224) // bytes payload - log4(mark, 288, // calldata - shl(224, shr(224, calldataload(0))), // msg.sig - calldataload(4), // arg1 - calldataload(36), // arg2 - calldataload(68) // arg3 - ) - } - } - - // --- Init --- - constructor() public { - wards[msg.sender] = 1; - live = 1; - } - - // --- Math --- - function add(uint x, int y) internal pure returns (uint z) { - z = x + uint(y); - require(y >= 0 || z <= x); - require(y <= 0 || z >= x); - } - function sub(uint x, int y) internal pure returns (uint z) { - z = x - uint(y); - require(y <= 0 || z <= x); - require(y >= 0 || z >= x); - } - function mul(uint x, int y) internal pure returns (int z) { - z = int(x) * y; - require(int(x) >= 0); - require(y == 0 || z / y == int(x)); - } - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x); - } - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x); - } - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x); - } - - // --- Administration --- - function init(bytes32 ilk) external note auth { - require(ilks[ilk].rate == 0, "Vat/ilk-already-init"); - ilks[ilk].rate = 10 ** 27; - } - function file(bytes32 what, uint data) external note auth { - require(live == 1, "Vat/not-live"); - if (what == "Line") Line = data; - else revert("Vat/file-unrecognized-param"); - } - function file(bytes32 ilk, bytes32 what, uint data) external note auth { - require(live == 1, "Vat/not-live"); - if (what == "spot") ilks[ilk].spot = data; - else if (what == "line") ilks[ilk].line = data; - else if (what == "dust") ilks[ilk].dust = data; - else revert("Vat/file-unrecognized-param"); - } - function cage() external note auth { - live = 0; - } - - // --- Fungibility --- - function slip(bytes32 ilk, address usr, int256 wad) external note auth { - gem[ilk][usr] = add(gem[ilk][usr], wad); - } - function flux(bytes32 ilk, address src, address dst, uint256 wad) external note { - require(wish(src, msg.sender), "Vat/not-allowed"); - gem[ilk][src] = sub(gem[ilk][src], wad); - gem[ilk][dst] = add(gem[ilk][dst], wad); - } - function move(address src, address dst, uint256 rad) external note { - require(wish(src, msg.sender), "Vat/not-allowed"); - dai[src] = sub(dai[src], rad); - dai[dst] = add(dai[dst], rad); - } - - function either(bool x, bool y) internal pure returns (bool z) { - assembly{ z := or(x, y)} - } - function both(bool x, bool y) internal pure returns (bool z) { - assembly{ z := and(x, y)} - } - - // --- CDP Manipulation --- - function frob(bytes32 i, address u, address v, address w, int dink, int dart) external note { - // system is live - require(live == 1, "Vat/not-live"); - - Urn memory urn = urns[i][u]; - Ilk memory ilk = ilks[i]; - // ilk has been initialised - require(ilk.rate != 0, "Vat/ilk-not-init"); - - urn.ink = add(urn.ink, dink); - urn.art = add(urn.art, dart); - ilk.Art = add(ilk.Art, dart); - - int dtab = mul(ilk.rate, dart); - uint tab = mul(ilk.rate, urn.art); - debt = add(debt, dtab); - - // either debt has decreased, or debt ceilings are not exceeded - require(either(dart <= 0, both(mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded"); - // urn is either less risky than before, or it is safe - require(either(both(dart <= 0, dink >= 0), tab <= mul(urn.ink, ilk.spot)), "Vat/not-safe"); - - // urn is either more safe, or the owner consents - require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u"); - // collateral src consents - require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v"); - // debt dst consents - require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w"); - - // urn has no debt, or a non-dusty amount - require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust"); - - gem[i][v] = sub(gem[i][v], dink); - dai[w] = add(dai[w], dtab); - - urns[i][u] = urn; - ilks[i] = ilk; - } - // --- CDP Fungibility --- - function fork(bytes32 ilk, address src, address dst, int dink, int dart) external note { - Urn storage u = urns[ilk][src]; - Urn storage v = urns[ilk][dst]; - Ilk storage i = ilks[ilk]; - - u.ink = sub(u.ink, dink); - u.art = sub(u.art, dart); - v.ink = add(v.ink, dink); - v.art = add(v.art, dart); - - uint utab = mul(u.art, i.rate); - uint vtab = mul(v.art, i.rate); - - // both sides consent - require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed"); - - // both sides safe - require(utab <= mul(u.ink, i.spot), "Vat/not-safe-src"); - require(vtab <= mul(v.ink, i.spot), "Vat/not-safe-dst"); - - // both sides non-dusty - require(either(utab >= i.dust, u.art == 0), "Vat/dust-src"); - require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst"); - } - // --- CDP Confiscation --- - function grab(bytes32 i, address u, address v, address w, int dink, int dart) external note auth { - Urn storage urn = urns[i][u]; - Ilk storage ilk = ilks[i]; - - urn.ink = add(urn.ink, dink); - urn.art = add(urn.art, dart); - ilk.Art = add(ilk.Art, dart); - - int dtab = mul(ilk.rate, dart); - - gem[i][v] = sub(gem[i][v], dink); - sin[w] = sub(sin[w], dtab); - vice = sub(vice, dtab); - } - - // --- Settlement --- - function heal(uint rad) external note { - address u = msg.sender; - sin[u] = sub(sin[u], rad); - dai[u] = sub(dai[u], rad); - vice = sub(vice, rad); - debt = sub(debt, rad); - } - function suck(address u, address v, uint rad) external note auth { - sin[u] = add(sin[u], rad); - dai[v] = add(dai[v], rad); - vice = add(vice, rad); - debt = add(debt, rad); - } - - // --- Rates --- - function fold(bytes32 i, address u, int rate) external note auth { - require(live == 1, "Vat/not-live"); - Ilk storage ilk = ilks[i]; - ilk.rate = add(ilk.rate, rate); - int rad = mul(ilk.Art, rate); - dai[u] = add(dai[u], rad); - debt = add(debt, rad); - } -} \ No newline at end of file diff --git a/lib/other/CryptoKitties.sol b/lib/other/CryptoKitties.sol deleted file mode 100644 index 00592b29d..000000000 --- a/lib/other/CryptoKitties.sol +++ /dev/null @@ -1,262 +0,0 @@ -pragma solidity ^0.5.4; - - -/// @title Base contract for CryptoKitties. Holds all common structs, events and base variables. -/// @author Axiom Zen (https://www.axiomzen.co) -/// @dev See the KittyCore contract documentation to understand how the various contract facets are arranged. -contract KittyBase { - /*** EVENTS ***/ - - /// @dev The Birth event is fired whenever a new kitten comes into existence. This obviously - /// includes any time a cat is created through the giveBirth method, but it is also called - /// when a new gen0 cat is created. - event Birth(address owner, uint256 kittyId, uint256 matronId, uint256 sireId, uint256 genes); - - /// @dev Transfer event as defined in current draft of ERC721. Emitted every time a kitten - /// ownership is assigned, including births. - event Transfer(address from, address to, uint256 tokenId); - - /*** DATA TYPES ***/ - - /// @dev The main Kitty struct. Every cat in CryptoKitties is represented by a copy - /// of this structure, so great care was taken to ensure that it fits neatly into - /// exactly two 256-bit words. Note that the order of the members in this structure - /// is important because of the byte-packing rules used by Ethereum. - /// Ref: http://solidity.readthedocs.io/en/develop/miscellaneous.html - struct Kitty { - // The Kitty's genetic code is packed into these 256-bits, the format is - // sooper-sekret! A cat's genes never change. - uint256 genes; - - // The timestamp from the block when this cat came into existence. - uint64 birthTime; - - // The minimum timestamp after which this cat can engage in breeding - // activities again. This same timestamp is used for the pregnancy - // timer (for matrons) as well as the siring cooldown. - uint64 cooldownEndBlock; - - // The ID of the parents of this kitty, set to 0 for gen0 cats. - // Note that using 32-bit unsigned integers limits us to a "mere" - // 4 billion cats. This number might seem small until you realize - // that Ethereum currently has a limit of about 500 million - // transactions per year! So, this definitely won't be a problem - // for several years (even as Ethereum learns to scale). - uint32 matronId; - uint32 sireId; - - // Set to the ID of the sire cat for matrons that are pregnant, - // zero otherwise. A non-zero value here is how we know a cat - // is pregnant. Used to retrieve the genetic material for the new - // kitten when the birth transpires. - uint32 siringWithId; - - // Set to the index in the cooldown array (see below) that represents - // the current cooldown duration for this Kitty. This starts at zero - // for gen0 cats, and is initialized to floor(generation/2) for others. - // Incremented by one for each successful breeding action, regardless - // of whether this cat is acting as matron or sire. - uint16 cooldownIndex; - - // The "generation number" of this cat. Cats minted by the CK contract - // for sale are called "gen0" and have a generation number of 0. The - // generation number of all other cats is the larger of the two generation - // numbers of their parents, plus one. - // (i.e. max(matron.generation, sire.generation) + 1) - uint16 generation; - } - - - /*** STORAGE ***/ - - /// @dev An array containing the Kitty struct for all Kitties in existence. The ID - /// of each cat is actually an index into this array. Note that ID 0 is a negacat, - /// the unKitty, the mythical beast that is the parent of all gen0 cats. A bizarre - /// creature that is both matron and sire... to itself! Has an invalid genetic code. - /// In other words, cat ID 0 is invalid... ;-) - Kitty[] kitties; - - /// @dev A mapping from cat IDs to the address that owns them. All cats have - /// some valid owner address, even gen0 cats are created with a non-zero owner. - mapping (uint256 => address) public kittyIndexToOwner; - - // @dev A mapping from owner address to count of tokens that address owns. - // Used internally inside balanceOf() to resolve ownership count. - mapping (address => uint256) ownershipTokenCount; - - /// @dev A mapping from KittyIDs to an address that has been approved to call - /// transferFrom(). Each Kitty can only have one approved address for transfer - /// at any time. A zero value means no approval is outstanding. - mapping (uint256 => address) public kittyIndexToApproved; - - /// @dev A mapping from KittyIDs to an address that has been approved to use - /// this Kitty for siring via breedWith(). Each Kitty can only have one approved - /// address for siring at any time. A zero value means no approval is outstanding. - mapping (uint256 => address) public sireAllowedToAddress; - - /// @dev Assigns ownership of a specific Kitty to an address. - function _transfer(address _from, address _to, uint256 _tokenId) internal { - // Since the number of kittens is capped to 2^32 we can't overflow this - ownershipTokenCount[_to]++; - // transfer ownership - kittyIndexToOwner[_tokenId] = _to; - // When creating new kittens _from is 0x0, but we can't account that address. - if (_from != address(0)) { - ownershipTokenCount[_from]--; - // once the kitten is transferred also clear sire allowances - delete sireAllowedToAddress[_tokenId]; - // clear any previously approved ownership exchange - delete kittyIndexToApproved[_tokenId]; - } - // Emit the transfer event. - emit Transfer(_from, _to, _tokenId); - } - - /// @dev An internal method that creates a new kitty and stores it. This - /// method doesn't do any checking and should only be called when the - /// input data is known to be valid. Will generate both a Birth event - /// and a Transfer event. - /// @param _matronId The kitty ID of the matron of this cat (zero for gen0) - /// @param _sireId The kitty ID of the sire of this cat (zero for gen0) - /// @param _generation The generation number of this cat, must be computed by caller. - /// @param _genes The kitty's genetic code. - /// @param _owner The inital owner of this cat, must be non-zero (except for the unKitty, ID 0) - function _createKitty( - uint256 _matronId, - uint256 _sireId, - uint256 _generation, - uint256 _genes, - address _owner - ) - internal - returns (uint) - { - // These requires are not strictly necessary, our calling code should make - // sure that these conditions are never broken. However! _createKitty() is already - // an expensive call (for storage), and it doesn't hurt to be especially careful - // to ensure our data structures are always valid. - require(_matronId == uint256(uint32(_matronId))); - require(_sireId == uint256(uint32(_sireId))); - require(_generation == uint256(uint16(_generation))); - - // New kitty starts with the same cooldown as parent gen/2 - uint16 cooldownIndex = uint16(_generation / 2); - if (cooldownIndex > 13) { - cooldownIndex = 13; - } - - Kitty memory _kitty = Kitty({ - genes: _genes, - birthTime: uint64(now), - cooldownEndBlock: 0, - matronId: uint32(_matronId), - sireId: uint32(_sireId), - siringWithId: 0, - cooldownIndex: cooldownIndex, - generation: uint16(_generation) - }); - uint256 newKittenId = kitties.push(_kitty) - 1; - - // It's probably never going to happen, 4 billion cats is A LOT, but - // let's just be 100% sure we never let this happen. - require(newKittenId == uint256(uint32(newKittenId))); - - // emit the birth event - emit Birth( - _owner, - newKittenId, - uint256(_kitty.matronId), - uint256(_kitty.sireId), - _kitty.genes - ); - - // This will assign ownership, and also emit the Transfer event as - // per ERC721 draft - _transfer(address(0), _owner, newKittenId); - - return newKittenId; - } - - -} - -/// @title The facet of the CryptoKitties core contract that manages ownership, ERC-721 (draft) compliant. -/// @author Axiom Zen (https://www.axiomzen.co) -/// @dev Ref: https://github.com/ethereum/EIPs/issues/721 -/// See the KittyCore contract documentation to understand how the various contract facets are arranged. -contract CryptoKittyTest is KittyBase { - - /// @notice Name and symbol of the non fungible token, as defined in ERC721. - string public constant name = "CryptoKitties"; - string public constant symbol = "CK"; - - - // Internal utility functions: These functions all assume that their input arguments - // are valid. We leave it to public methods to sanitize their inputs and follow - // the required logic. - - /// @dev Checks if a given address is the current owner of a particular Kitty. - /// @param _claimant the address we are validating against. - /// @param _tokenId kitten id, only valid when > 0 - function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) { - return kittyIndexToOwner[_tokenId] == _claimant; - } - - - - /// @notice Returns the number of Kitties owned by a specific address. - /// @param _owner The owner address to check. - /// @dev Required for ERC-721 compliance - function balanceOf(address _owner) public view returns (uint256 count) { - return ownershipTokenCount[_owner]; - } - - /// @notice Transfers a Kitty to another address. If transferring to a smart - /// contract be VERY CAREFUL to ensure that it is aware of ERC-721 (or - /// CryptoKitties specifically) or your Kitty may be lost forever. Seriously. - /// @param _to The address of the recipient, can be a user or contract. - /// @param _tokenId The ID of the Kitty to transfer. - /// @dev Required for ERC-721 compliance. - function transfer( - address _to, - uint256 _tokenId - ) - external - { - // Safety check to prevent against an unexpected 0x0 default. - require(_to != address(0)); - // Disallow transfers to this contract to prevent accidental misuse. - // The contract should never own any kitties (except very briefly - // after a gen0 cat is created and before it goes on auction). - require(_to != address(this)); - // Disallow transfers to the auction contracts to prevent accidental - // misuse. Auction contracts should only take ownership of kitties - // through the allow + transferFrom flow. - // require(_to != address(saleAuction)); - // require(_to != address(siringAuction)); - - // You can only send your own cat. - require(_owns(msg.sender, _tokenId)); - - // Reassign ownership, clear pending approvals, emit Transfer event. - _transfer(msg.sender, _to, _tokenId); - } - - - function createDumbKitty( - address _owner - ) - external - returns (uint) - { - return _createKitty(1,2,3,4,_owner); - } - -} - - - - - - - diff --git a/lib/paraswap/AugustusSwapper.sol b/lib/paraswap/AugustusSwapper.sol deleted file mode 100644 index b2543de12..000000000 --- a/lib/paraswap/AugustusSwapper.sol +++ /dev/null @@ -1,668 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./IWhitelisted.sol"; -import "./lib/IExchange.sol"; -import "./lib/Utils.sol"; -import "./TokenTransferProxy.sol"; -import "./IPartnerRegistry.sol"; -import "./IPartner.sol"; - - -contract AugustusSwapper is Ownable { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using Address for address; - - TokenTransferProxy private _tokenTransferProxy; - - bool private _paused; - - IWhitelisted private _whitelisted; - - IPartnerRegistry private _partnerRegistry; - address payable private _feeWallet; - - event Paused(); - event Unpaused(); - - event Swapped( - address initiator, - address indexed beneficiary, - address indexed srcToken, - address indexed destToken, - uint256 srcAmount, - uint256 receivedAmount, - uint256 expectedAmount, - string referrer - ); - - event Bought( - address initiator, - address indexed beneficiary, - address indexed srcToken, - address indexed destToken, - uint256 srcAmount, - uint256 receivedAmount, - uint256 expectedAmount, - string referrer - ); - - event Donation(address indexed receiver, uint256 donationPercentage); - - event FeeTaken(uint256 fee, uint256 partnerShare, uint256 paraswapShare); - - /** - * @dev Modifier to make a function callable only when the contract is not paused. - */ - modifier whenNotPaused() { - require(!_paused, "Pausable: paused"); - _; - } - - /** - * @dev Modifier to make a function callable only when the contract is paused. - */ - modifier whenPaused() { - require(_paused, "Pausable: not paused"); - _; - } - - constructor( - address whitelist, - address gasToken, - address partnerRegistry, - address payable feeWallet, - address gstHolder - ) public { - _partnerRegistry = IPartnerRegistry(partnerRegistry); - _tokenTransferProxy = new TokenTransferProxy(gasToken, gstHolder); - _whitelisted = IWhitelisted(whitelist); - _feeWallet = feeWallet; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable whenNotPaused { - address account = msg.sender; - require(account.isContract(), "Sender is not a contract"); - } - - function getPartnerRegistry() external view returns (address) { - return address(_partnerRegistry); - } - - function getWhitelistAddress() external view returns (address) { - return address(_whitelisted); - } - - function getFeeWallet() external view returns (address) { - return _feeWallet; - } - - function setFeeWallet(address payable feeWallet) external onlyOwner { - require(feeWallet != address(0), "Invalid address"); - _feeWallet = feeWallet; - } - - function setPartnerRegistry(address partnerRegistry) external onlyOwner { - require(partnerRegistry != address(0), "Invalid address"); - _partnerRegistry = IPartnerRegistry(partnerRegistry); - } - - function setWhitelistAddress(address whitelisted) external onlyOwner { - require(whitelisted != address(0), "Invalid whitelist address"); - _whitelisted = IWhitelisted(whitelisted); - } - - function getTokenTransferProxy() external view returns (address) { - return address(_tokenTransferProxy); - } - - function changeGSTHolder(address gstHolder) external onlyOwner { - require(gstHolder != address(0), "Invalid address"); - _tokenTransferProxy.changeGSTTokenHolder(gstHolder); - } - - /** - * @dev Returns true if the contract is paused, and false otherwise. - */ - function paused() external view returns (bool) { - return _paused; - } - - /** - * @dev Called by a pauser to pause, triggers stopped state. - */ - function pause() external onlyOwner whenNotPaused { - _paused = true; - emit Paused(); - } - - /** - * @dev Called by a pauser to unpause, returns to normal state. - */ - function unpause() external onlyOwner whenPaused { - _paused = false; - emit Unpaused(); - } - - /** - * @dev Allows owner of the contract to transfer tokens any tokens which are assigned to the contract - * This method is for saftey if by any chance tokens or ETHs are assigned to the contract by mistake - * @dev token Address of the token to be transferred - * @dev destination Recepient of the token - * @dev amount Amount of tokens to be transferred - */ - function ownerTransferTokens( - address token, - address payable destination, - uint256 amount - ) external onlyOwner { - Utils.transferTokens(token, destination, amount); - } - - // /** - // * @dev The function which performs the multi path swap. - // * @param fromToken Address of the source token - // * @param toToken Address of the destination token - // * @param fromAmount Amount of source tokens to be swapped - // * @param toAmount Minimum destination token amount expected out of this swap - // * @param expectedAmount Expected amount of destination tokens without slippage - // * @param path Route to be taken for this swap to take place - // * @param mintPrice Price of gas at the time of minting of gas tokens, if any. In wei. 0 means gas token will not be used - // * @param beneficiary Beneficiary address - // * @param donationPercentage Percentage of returned amount to be transferred to beneficiary, if beneficiary is available. If this is passed as - // * 0 then 100% will be transferred to beneficiary. Pass 10000 for 100% - // * @param referrer referral id - // */ - function multiSwap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - uint256 expectedAmount, - Utils.Path[] memory path, - uint256 mintPrice, - address payable beneficiary, - uint256 donationPercentage, - string memory referrer - ) public payable whenNotPaused returns (uint256) { - //Referral id can never be empty - require(bytes(referrer).length > 0, "Invalid referrer"); - - require(donationPercentage <= 10000, "Invalid value"); - - require(toAmount > 0, "To amount can not be 0"); - - uint256 receivedAmount = performSwap( - fromToken, - toToken, - fromAmount, - toAmount, - path, - mintPrice - ); - - takeFeeAndTransferTokens( - toToken, - toAmount, - receivedAmount, - beneficiary, - donationPercentage, - referrer - ); - - //If any ether is left at this point then we transfer it back to the user - uint256 remEthBalance = Utils.tokenBalance( - Utils.ethAddress(), - address(this) - ); - if (remEthBalance > 0) { - msg.sender.transfer(remEthBalance); - } - - //Contract should not have any remaining balance after entire execution - require( - Utils.tokenBalance(address(toToken), address(this)) == 0, - "Destination tokens are stuck" - ); - - emit Swapped( - msg.sender, - beneficiary == address(0) ? msg.sender : beneficiary, - address(fromToken), - address(toToken), - fromAmount, - receivedAmount, - expectedAmount, - referrer - ); - - return receivedAmount; - } - - /** - * @dev The function which performs the single path buy. - * @param fromToken Address of the source token - * @param toToken Address of the destination token - * @param fromAmount Max amount of source tokens to be swapped - * @param toAmount Destination token amount expected out of this swap - * @param expectedAmount Expected amount of source tokens to be used without slippage - * @param route Route to be taken for this swap to take place - * @param mintPrice Price of gas at the time of minting of gas tokens, if any. In wei. 0 means gas token will not be used - * @param beneficiary Beneficiary address - * @param donationPercentage Percentage of returned amount to be transferred to beneficiary, if beneficiary is available. If this is passed as - * 0 then 100% will be transferred to beneficiary. Pass 10000 for 100% - * @param referrer referral id - */ - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - uint256 expectedAmount, - Utils.BuyRoute[] memory route, - uint256 mintPrice, - address payable beneficiary, - uint256 donationPercentage, - string memory referrer - ) public payable whenNotPaused returns (uint256) { - //Referral id can never be empty - require(bytes(referrer).length > 0, "Invalid referrer"); - - require(donationPercentage <= 10000, "Invalid value"); - - require(toAmount > 0, "To amount can not be 0"); - - uint256 receivedAmount = performBuy( - fromToken, - toToken, - fromAmount, - toAmount, - route, - mintPrice - ); - - takeFeeAndTransferTokens( - toToken, - toAmount, - receivedAmount, - beneficiary, - donationPercentage, - referrer - ); - - uint256 remainingAmount = Utils.tokenBalance( - address(fromToken), - address(this) - ); - Utils.transferTokens(address(fromToken), msg.sender, remainingAmount); - - //If any ether is left at this point then we transfer it back to the user - remainingAmount = Utils.tokenBalance(Utils.ethAddress(), address(this)); - if (remainingAmount > 0) { - Utils.transferTokens( - Utils.ethAddress(), - msg.sender, - remainingAmount - ); - } - - //Contract should not have any remaining balance after entire execution - require( - Utils.tokenBalance(address(toToken), address(this)) == 0, - "Destination tokens are stuck" - ); - - emit Bought( - msg.sender, - beneficiary == address(0) ? msg.sender : beneficiary, - address(fromToken), - address(toToken), - fromAmount, - receivedAmount, - expectedAmount, - referrer - ); - - return receivedAmount; - } - - //Helper function to transfer final amount to the beneficiaries - function takeFeeAndTransferTokens( - IERC20 toToken, - uint256 toAmount, - uint256 receivedAmount, - address payable beneficiary, - uint256 donationPercentage, - string memory referrer - ) private { - uint256 remainingAmount = receivedAmount; - - //Take partner fee - uint256 fee = _takeFee(toToken, receivedAmount, referrer); - remainingAmount = receivedAmount.sub(fee); - - //If beneficiary is not a 0 address then it means it is a transfer transaction - if (beneficiary == address(0)) { - Utils.transferTokens(address(toToken), msg.sender, remainingAmount); - } else { - //Extra check of < 100 is made to ensure that in case of 100% we do not send - //un-necessary transfer call to the msg.sender. This will save some gas - if (donationPercentage > 0 && donationPercentage < 10000) { - //Keep donation amount with the contract and send rest to the msg.sender - uint256 donationAmount = remainingAmount - .mul(donationPercentage) - .div(10000); - - Utils.transferTokens( - address(toToken), - msg.sender, - remainingAmount.sub(donationAmount) - ); - - remainingAmount = donationAmount; - } - - //we will fire donation event if donationPercentage is > 0 even if it is 100% - if (donationPercentage > 0) { - emit Donation(beneficiary, donationPercentage); - } - - Utils.transferTokens( - address(toToken), - beneficiary, - remainingAmount - ); - } - } - - //Helper function to perform swap - function performSwap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - Utils.Path[] memory path, - uint256 mintPrice - ) private returns (uint256) { - uint256 initialGas = gasleft(); - - uint256 _fromAmount = fromAmount; - - require(path.length > 0, "Path not provided for swap"); - require( - path[path.length - 1].to == address(toToken), - "Last to token does not match toToken" - ); - - //if fromToken is not ETH then transfer tokens from user to this contract - if (address(fromToken) != Utils.ethAddress()) { - _tokenTransferProxy.transferFrom( - address(fromToken), - msg.sender, - address(this), - fromAmount - ); - } - - //Assuming path will not be too long to reach out of gas exception - for (uint256 i = 0; i < path.length; i++) { - //_fromToken will be either fromToken of toToken of the previous path - IERC20 _fromToken = i > 0 - ? IERC20(path[i - 1].to) - : IERC20(fromToken); - IERC20 _toToken = IERC20(path[i].to); - - if (i > 0 && address(_fromToken) == Utils.ethAddress()) { - _fromAmount = _fromAmount.sub(path[i].totalNetworkFee); - } - - uint256 initialFromBalance = Utils - .tokenBalance(address(_fromToken), address(this)) - .sub(_fromAmount); - - for (uint256 j = 0; j < path[i].routes.length; j++) { - Utils.Route memory route = path[i].routes[j]; - - //Calculating tokens to be passed to the relevant exchange - //percentage should be 200 for 2% - uint256 fromAmountSlice = _fromAmount.mul(route.percent).div( - 10000 - ); - uint256 value = route.networkFee; - - if (j == path[i].routes.length.sub(1)) { - uint256 remBal = Utils.tokenBalance( - address(_fromToken), - address(this) - ); - - fromAmountSlice = remBal; - - if (address(_fromToken) == Utils.ethAddress()) { - //subtract network fee - fromAmountSlice = fromAmountSlice.sub(value); - } - } - - //Check if exchange is supported - require( - _whitelisted.isWhitelisted(route.exchange), - "Exchange not whitelisted" - ); - - IExchange dex = IExchange(route.exchange); - - Utils.approve(route.exchange, address(_fromToken)); - - uint256 initialExchangeFromBalance = Utils.tokenBalance( - address(_fromToken), - route.exchange - ); - uint256 initialExchangeToBalance = Utils.tokenBalance( - address(_toToken), - route.exchange - ); - - //Call to the exchange - if (address(_fromToken) == Utils.ethAddress()) { - value = value.add(fromAmountSlice); - - dex.swap.value(value)( - _fromToken, - _toToken, - fromAmountSlice, - 1, - route.targetExchange, - route.payload - ); - } else { - _fromToken.safeTransfer(route.exchange, fromAmountSlice); - - dex.swap.value(value)( - _fromToken, - _toToken, - fromAmountSlice, - 1, - route.targetExchange, - route.payload - ); - } - - require( - Utils.tokenBalance(address(_toToken), route.exchange) <= - initialExchangeToBalance, - "Destination tokens are stuck in exchange" - ); - require( - Utils.tokenBalance(address(_fromToken), route.exchange) <= - initialExchangeFromBalance, - "Source tokens are stuck in exchange" - ); - } - - _fromAmount = Utils.tokenBalance(address(_toToken), address(this)); - - //Contract should not have any remaining balance after execution - require( - Utils.tokenBalance(address(_fromToken), address(this)) <= - initialFromBalance, - "From tokens are stuck" - ); - } - - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - require( - receivedAmount >= toAmount, - "Received amount of tokens are less then expected" - ); - - if (mintPrice > 0) { - Utils.refundGas( - address(_tokenTransferProxy), - initialGas, - mintPrice - ); - } - return receivedAmount; - } - - //Helper function to perform swap - function performBuy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - Utils.BuyRoute[] memory routes, - uint256 mintPrice - ) private returns (uint256) { - uint256 initialGas = gasleft(); - IERC20 _fromToken = fromToken; - IERC20 _toToken = toToken; - - //if fromToken is not ETH then transfer tokens from user to this contract - if (address(_fromToken) != Utils.ethAddress()) { - _tokenTransferProxy.transferFrom( - address(_fromToken), - msg.sender, - address(this), - fromAmount - ); - } - - for (uint256 j = 0; j < routes.length; j++) { - Utils.BuyRoute memory route = routes[j]; - - //Check if exchange is supported - require( - _whitelisted.isWhitelisted(route.exchange), - "Exchange not whitelisted" - ); - IExchange dex = IExchange(route.exchange); - Utils.approve(route.exchange, address(_fromToken)); - - uint256 initialExchangeFromBalance = Utils.tokenBalance( - address(_fromToken), - route.exchange - ); - uint256 initialExchangeToBalance = Utils.tokenBalance( - address(_toToken), - route.exchange - ); - //Call to the exchange - if (address(_fromToken) == Utils.ethAddress()) { - uint256 value = route.networkFee.add(route.fromAmount); - dex.buy.value(value)( - _fromToken, - _toToken, - route.fromAmount, - route.toAmount, - route.targetExchange, - route.payload - ); - } else { - _fromToken.safeTransfer(route.exchange, route.fromAmount); - dex.buy.value(route.networkFee)( - _fromToken, - _toToken, - route.fromAmount, - route.toAmount, - route.targetExchange, - route.payload - ); - } - require( - Utils.tokenBalance(address(_toToken), route.exchange) <= - initialExchangeToBalance, - "Destination tokens are stuck in exchange" - ); - require( - Utils.tokenBalance(address(_fromToken), route.exchange) <= - initialExchangeFromBalance, - "Source tokens are stuck in exchange" - ); - } - - uint256 receivedAmount = Utils.tokenBalance( - address(_toToken), - address(this) - ); - require( - receivedAmount >= toAmount, - "Received amount of tokens are less then expected tokens" - ); - - if (mintPrice > 0) { - Utils.refundGas( - address(_tokenTransferProxy), - initialGas, - mintPrice - ); - } - return receivedAmount; - } - - function _takeFee( - IERC20 toToken, - uint256 receivedAmount, - string memory referrer - ) private returns (uint256) { - address partnerContract = _partnerRegistry.getPartnerContract(referrer); - - //If there is no partner associated with the referral id then no fee will be taken - if (partnerContract == address(0)) { - return 0; - } - - uint256 feePercent = IPartner(partnerContract).getFee(); - uint256 partnerSharePercent = IPartner(partnerContract) - .getPartnerShare(); - address payable partnerFeeWallet = IPartner(partnerContract) - .getFeeWallet(); - - //Calculate total fee to be taken - uint256 fee = receivedAmount.mul(feePercent).div(10000); - //Calculate partner's share - uint256 partnerShare = fee.mul(partnerSharePercent).div(10000); - //All remaining fee is paraswap's share - uint256 paraswapShare = fee.sub(partnerShare); - - Utils.transferTokens(address(toToken), partnerFeeWallet, partnerShare); - Utils.transferTokens(address(toToken), _feeWallet, paraswapShare); - - emit FeeTaken(fee, partnerShare, paraswapShare); - return fee; - } -} diff --git a/lib/paraswap/IAugustusSwapper.sol b/lib/paraswap/IAugustusSwapper.sol deleted file mode 100644 index cbce68cf7..000000000 --- a/lib/paraswap/IAugustusSwapper.sol +++ /dev/null @@ -1,28 +0,0 @@ -pragma solidity >=0.5.4 <0.7.0; - -interface IAugustusSwapper { - function getTokenTransferProxy() external view returns (address); - - struct Route { - address payable exchange; - address targetExchange; - uint percent; - bytes payload; - uint256 networkFee; // only used for 0xV3 - } - - struct Path { - address to; - uint256 totalNetworkFee; // only used for 0xV3 - Route[] routes; - } - - struct BuyRoute { - address payable exchange; - address targetExchange; - uint256 fromAmount; - uint256 toAmount; - bytes payload; - uint256 networkFee; // only used for 0xV3 - } -} \ No newline at end of file diff --git a/lib/paraswap/IPartner.sol b/lib/paraswap/IPartner.sol deleted file mode 100644 index f3b0c9b68..000000000 --- a/lib/paraswap/IPartner.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IPartner { - - function getReferralId() external view returns(string memory); - - function getFeeWallet() external view returns(address payable); - - function getFee() external view returns(uint256); - - function getPartnerShare() external returns(uint256); - - function getParaswapShare() external returns(uint256); - - function changeFeeWallet(address payable feeWallet) external; - - function changeFee(uint256 newFee) external; -} \ No newline at end of file diff --git a/lib/paraswap/IPartnerRegistry.sol b/lib/paraswap/IPartnerRegistry.sol deleted file mode 100644 index 248613b9b..000000000 --- a/lib/paraswap/IPartnerRegistry.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IPartnerRegistry { - - function getPartnerContract(string calldata referralId) external view returns(address); - - function addPartner( - string calldata referralId, - address feeWallet, - uint256 fee, - uint256 paraswapShare, - uint256 partnerShare, - address owner - ) - external; - - function removePartner(string calldata referralId) external; -} \ No newline at end of file diff --git a/lib/paraswap/IWETH.sol b/lib/paraswap/IWETH.sol deleted file mode 100644 index c75885dab..000000000 --- a/lib/paraswap/IWETH.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - - -contract IWETH is IERC20 { - function deposit() external payable; - function withdraw(uint256 amount) external; -} diff --git a/lib/paraswap/IWhitelisted.sol b/lib/paraswap/IWhitelisted.sol deleted file mode 100644 index bbdb3d177..000000000 --- a/lib/paraswap/IWhitelisted.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IWhitelisted { - - function isWhitelisted(address account) external view returns (bool); -} \ No newline at end of file diff --git a/lib/paraswap/Whitelisted.sol b/lib/paraswap/Whitelisted.sol deleted file mode 100644 index e79e67204..000000000 --- a/lib/paraswap/Whitelisted.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/access/roles/WhitelistedRole.sol"; - -contract Whitelisted is WhitelistedRole { - -} \ No newline at end of file diff --git a/lib/paraswap/lib/TokenFetcher.sol b/lib/paraswap/lib/TokenFetcher.sol deleted file mode 100644 index 670c0b49b..000000000 --- a/lib/paraswap/lib/TokenFetcher.sol +++ /dev/null @@ -1,26 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "./Utils.sol"; - - -contract TokenFetcher is Ownable { - - /** - * @dev Allows owner of the contract to transfer tokens any tokens which are assigned to the contract - * This method is for saftey if by any chance tokens or ETHs are assigned to the contract by mistake - * @dev token Address of the token to be transferred - * @dev destination Recepient of the token - * @dev amount Amount of tokens to be transferred - */ - function transferTokens( - address token, - address payable destination, - uint256 amount - ) - external - onlyOwner - { - Utils.transferTokens(token, destination, amount); - } -} \ No newline at end of file diff --git a/lib/paraswap/lib/Utils.sol b/lib/paraswap/lib/Utils.sol deleted file mode 100644 index f1ac33329..000000000 --- a/lib/paraswap/lib/Utils.sol +++ /dev/null @@ -1,138 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../ITokenTransferProxy.sol"; - -library Utils { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - address constant ETH_ADDRESS = address( - 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE - ); - - uint256 constant MAX_UINT = 2 ** 256 - 1; - - struct Route { - address payable exchange; - address targetExchange; - uint percent; - bytes payload; - uint256 networkFee;//Network fee is associated with 0xv3 trades - } - - struct Path { - address to; - uint256 totalNetworkFee;//Network fee is associated with 0xv3 trades - Route[] routes; - } - - struct BuyRoute { - address payable exchange; - address targetExchange; - uint256 fromAmount; - uint256 toAmount; - bytes payload; - uint256 networkFee;//Network fee is associated with 0xv3 trades - } - - function ethAddress() internal pure returns (address) {return ETH_ADDRESS;} - - function maxUint() internal pure returns (uint256) {return MAX_UINT;} - - function approve( - address addressToApprove, - address token - ) internal { - if (token != ETH_ADDRESS) { - IERC20 _token = IERC20(token); - - uint allowance = _token.allowance(address(this), addressToApprove); - - if (allowance < MAX_UINT / 10) { - _token.safeApprove(addressToApprove, MAX_UINT); - } - } - } - - function transferTokens( - address token, - address payable destination, - uint256 amount - ) - internal - { - if (token == ETH_ADDRESS) { - destination.transfer(amount); - } - else { - IERC20(token).safeTransfer(destination, amount); - } - } - - function tokenBalance( - address token, - address account - ) - internal - view - returns (uint256) - { - if (token == ETH_ADDRESS) { - return account.balance; - } else { - return IERC20(token).balanceOf(account); - } - } - - /** - * @dev Helper method to refund gas using gas tokens - */ - function refundGas(address tokenProxy, uint256 initialGas, uint256 mintPrice) internal { - - uint256 mintBase = 32254; - uint256 mintToken = 36543; - uint256 freeBase = 14154; - uint256 freeToken = 6870; - uint256 reimburse = 24000; - - uint256 tokens = initialGas.sub( - gasleft()).add(freeBase).div(reimburse.mul(2).sub(freeToken) - ); - - uint256 mintCost = mintBase.add(tokens.mul(mintToken)); - uint256 freeCost = freeBase.add(tokens.mul(freeToken)); - uint256 maxreimburse = tokens.mul(reimburse); - - uint256 efficiency = maxreimburse.mul(tx.gasprice).mul(100).div( - mintCost.mul(mintPrice).add(freeCost.mul(tx.gasprice)) - ); - - if (efficiency > 100) { - freeGasTokens(tokenProxy, tokens); - } - } - - /** - * @dev Helper method to free gas tokens - */ - function freeGasTokens(address tokenProxy, uint256 tokens) internal { - - uint256 tokensToFree = tokens; - uint256 safeNumTokens = 0; - uint256 gas = gasleft(); - - if (gas >= 27710) { - safeNumTokens = gas.sub(27710).div(1148 + 5722 + 150); - } - - if (tokensToFree > safeNumTokens) { - tokensToFree = safeNumTokens; - } - - ITokenTransferProxy(tokenProxy).freeGSTTokens(tokensToFree); - - } -} diff --git a/lib/paraswap/lib/aavee/Aavee.sol b/lib/paraswap/lib/aavee/Aavee.sol deleted file mode 100644 index e2369e991..000000000 --- a/lib/paraswap/lib/aavee/Aavee.sol +++ /dev/null @@ -1,142 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; - -import "./IAavee.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract Aavee is IExchange, TokenFetcher { - using Address for address; - - struct AaveeData { - address aToken; - } - - uint16 public refCode; - - address public spender; - - event RefCodeChanged(uint16 refCode); - - constructor(uint16 _refCode, address _spender) public { - refCode = _refCode; - spender = _spender; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function setRefCode(uint16 _refCode) external onlyOwner { - refCode = _refCode; - emit RefCodeChanged(_refCode); - } - - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload - ) - private - returns (uint256) - { - AaveeData memory data = abi.decode(payload, (AaveeData)); - - Utils.approve(spender, address(fromToken)); - - if (address(fromToken) == address(data.aToken)) { - require( - IAaveToken(data.aToken).underlyingAssetAddress() == address(toToken), - "Invalid to token" - ); - - IAaveToken(data.aToken).redeem(fromAmount); - } - else if(address(toToken) == address(data.aToken)) { - require( - IAaveToken(data.aToken).underlyingAssetAddress() == address(fromToken), - "Invalid to token" - ); - if (address(fromToken) == Utils.ethAddress()) { - IAaveLendingPool(exchange).deposit.value(fromAmount)(fromToken, fromAmount, refCode); - } - else { - IAaveLendingPool(exchange).deposit(fromToken, fromAmount, refCode); - } - } - else { - revert("Invalid aToken"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - -} diff --git a/lib/paraswap/lib/aavee/IAavee.sol b/lib/paraswap/lib/aavee/IAavee.sol deleted file mode 100644 index 3bcdc3a29..000000000 --- a/lib/paraswap/lib/aavee/IAavee.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - -interface IAaveToken { - function redeem(uint256 amount) external; - function underlyingAssetAddress() external view returns(address); - -} - -interface IAaveLendingPool { - function deposit( - IERC20 token, - uint256 amount, - uint16 refCode - ) - external - payable; - -} diff --git a/lib/paraswap/lib/bancor/Bancor.sol b/lib/paraswap/lib/bancor/Bancor.sol deleted file mode 100644 index ee0bafdd7..000000000 --- a/lib/paraswap/lib/bancor/Bancor.sol +++ /dev/null @@ -1,108 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "../IExchange.sol"; -import "../Utils.sol"; -import "./IBancor.sol"; -import "./IContractRegistry.sol"; -import "../TokenFetcher.sol"; - - -contract Bancor is IExchange, TokenFetcher { - using SafeMath for uint256; - using Address for address; - - struct BancorData { - IERC20[] path; - } - - address public affiliateAccount; - uint256 public affiliateCode; - - bytes32 public constant BANCOR_NETWORK = 0x42616e636f724e6574776f726b00000000000000000000000000000000000000; - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function setAffiliateAccount(address account) external onlyOwner { - affiliateAccount = account; - } - - function setAffiliateCode(uint256 code) external onlyOwner { - affiliateCode = code; - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address registry, - bytes calldata payload - ) - external - payable - returns (uint256) - { - BancorData memory data = abi.decode(payload, (BancorData)); - - address bancorNetwork = IContractRegistry(registry).addressOf(BANCOR_NETWORK); - - Utils.approve(bancorNetwork, address(fromToken)); - - uint256 receivedAmount = 0; - - if (address(fromToken) == Utils.ethAddress()) { - receivedAmount = IBancor(bancorNetwork).convert2.value(fromAmount)( - data.path, - fromAmount, - toAmount, - affiliateAccount, - affiliateCode - ); - } - else { - receivedAmount = IBancor(bancorNetwork).claimAndConvert2( - data.path, - fromAmount, - toAmount, - affiliateAccount, - affiliateCode - ); - } - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - revert("METHOD NOT SUPPORTED"); - - } - -} diff --git a/lib/paraswap/lib/bancor/IBancor.sol b/lib/paraswap/lib/bancor/IBancor.sol deleted file mode 100644 index 6153973a8..000000000 --- a/lib/paraswap/lib/bancor/IBancor.sol +++ /dev/null @@ -1,49 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - - -interface IBancor { - - function quickConvert( - address[] calldata _path, - uint256 _amount, - uint256 _minReturn - ) - external - payable - returns (uint256); - - function convert2( - IERC20[] calldata _path, - uint256 _amount, - uint256 _minReturn, - address _affiliateAccount, - uint256 _affiliateFee - ) - external - payable - returns (uint256); - - function claimAndConvert2( - IERC20[] calldata _path, - uint256 _amount, - uint256 _minReturn, - address _affiliateAccount, - uint256 _affiliateFee - ) - external - returns (uint256); - - function claimAndConvertFor2( - IERC20[] calldata _path, - uint256 _amount, - uint256 _minReturn, - address _for, - address _affiliateAccount, - uint256 _affiliateFee - ) - external - returns (uint256); - -} diff --git a/lib/paraswap/lib/bancor/IContractRegistry.sol b/lib/paraswap/lib/bancor/IContractRegistry.sol deleted file mode 100644 index 9268f156a..000000000 --- a/lib/paraswap/lib/bancor/IContractRegistry.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IContractRegistry { - function addressOf(bytes32 _contractName) external view returns (address); - -} \ No newline at end of file diff --git a/lib/paraswap/lib/bdai/IBdai.sol b/lib/paraswap/lib/bdai/IBdai.sol deleted file mode 100644 index 07a1008db..000000000 --- a/lib/paraswap/lib/bdai/IBdai.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IBdai { - - function join(uint wad) external; - - function exit(uint wad) external; -} \ No newline at end of file diff --git a/lib/paraswap/lib/beth/BethExchange.sol b/lib/paraswap/lib/beth/BethExchange.sol deleted file mode 100644 index 58301466f..000000000 --- a/lib/paraswap/lib/beth/BethExchange.sol +++ /dev/null @@ -1,107 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./IBETH.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract BethExchange is IExchange, TokenFetcher { - using Address for address; - - address public constant BETH = address(0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315); - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload - ) - private - returns (uint256) - { - - Utils.approve(address(BETH), address(fromToken)); - - if (address(fromToken) == BETH){ - require(address(toToken) == Utils.ethAddress(), "Destination token should be ETH"); - IBETH(BETH).withdraw(fromAmount); - } - else if (address(fromToken) == Utils.ethAddress()) { - require(address(toToken) == BETH, "Destination token should be BETH"); - IBETH(BETH).deposit.value(fromAmount)(); - } - else { - revert("Invalid fromToken"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/beth/IBETH.sol b/lib/paraswap/lib/beth/IBETH.sol deleted file mode 100644 index 6af3825f5..000000000 --- a/lib/paraswap/lib/beth/IBETH.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - - -contract IBETH is IERC20 { - function deposit() external payable; - function withdraw(uint256 amount) external; -} diff --git a/lib/paraswap/lib/bzx/BZX.sol b/lib/paraswap/lib/bzx/BZX.sol deleted file mode 100644 index 1957e9fdc..000000000 --- a/lib/paraswap/lib/bzx/BZX.sol +++ /dev/null @@ -1,137 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/utils/Address.sol"; -import "./IBZX.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract BZX is IExchange, TokenFetcher { - using Address for address; - - struct BZXData { - address iToken; - } - - address public weth; - - constructor(address wethAddress) public { - weth = wethAddress; - } - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload - ) - private - returns (uint256) { - - BZXData memory data = abi.decode(payload, (BZXData)); - - Utils.approve(address(data.iToken), address(fromToken)); - - if (address(fromToken) == address(data.iToken)) { - if (address(toToken) == Utils.ethAddress()) { - require( - IBZX(data.iToken).loanTokenAddress() == weth, - "Invalid to token" - ); - IBZX(data.iToken).burnToEther(address(this), fromAmount); - } - else { - require( - IBZX(data.iToken).loanTokenAddress() == address(toToken), - "Invalid to token" - ); - IBZX(data.iToken).burn(address(this), fromAmount); - } - } - else if (address(toToken) == address(data.iToken)){ - if (address(fromToken) == Utils.ethAddress()) { - require( - IBZX(data.iToken).loanTokenAddress() == weth, - "Invalid from token" - ); - - IBZX(data.iToken).mintWithEther.value(fromAmount)(address(this)); - } - else { - require( - IBZX(data.iToken).loanTokenAddress() == address(fromToken), - "Invalid from token" - ); - IBZX(data.iToken).mint(address(this), fromAmount); - } - } - else { - revert("Invalid token pair!!"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/bzx/IBZX.sol b/lib/paraswap/lib/bzx/IBZX.sol deleted file mode 100644 index 19b36c0e7..000000000 --- a/lib/paraswap/lib/bzx/IBZX.sol +++ /dev/null @@ -1,30 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IBZX { - - function mint( - address receiver, - uint256 depositAmount - ) - external - returns (uint256 mintAmount); - - function mintWithEther(address receiver) external payable returns (uint256 mintAmount); - - function burn( - address receiver, - uint256 burnAmount - ) - external - returns (uint256 loanAmountPaid); - - function burnToEther( - address payable receiver, - uint256 burnAmount - ) - external - returns (uint256 loanAmountPaid); - - function loanTokenAddress() external view returns(address ); -} \ No newline at end of file diff --git a/lib/paraswap/lib/chai/ChaiExchange.sol b/lib/paraswap/lib/chai/ChaiExchange.sol deleted file mode 100644 index 3dac93ad7..000000000 --- a/lib/paraswap/lib/chai/ChaiExchange.sol +++ /dev/null @@ -1,94 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - -import "./IChai.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract ChaiExchange is IExchange, TokenFetcher { - - address public constant CHAI = address(0x06AF07097C9Eeb7fD685c692751D5C66dB49c215); - address public constant DAI = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload - ) - private - returns (uint256) - { - - Utils.approve(address(CHAI), address(fromToken)); - - if (address(fromToken) == CHAI){ - require(address(toToken) == DAI, "Destination token should be DAI"); - IChai(CHAI).exit(address(this), fromAmount); - } - else if (address(fromToken) == DAI) { - require(address(toToken) == CHAI, "Destination token should be CHAI"); - IChai(CHAI).join(address(this), fromAmount); - } - else { - revert("Invalid fromToken"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/chai/IChai.sol b/lib/paraswap/lib/chai/IChai.sol deleted file mode 100644 index 8c18ae5de..000000000 --- a/lib/paraswap/lib/chai/IChai.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IChai { - - function join(address dst, uint wad) external; - - function exit(address src, uint wad) external; -} \ No newline at end of file diff --git a/lib/paraswap/lib/compound/Compound.sol b/lib/paraswap/lib/compound/Compound.sol deleted file mode 100644 index 3248a16d3..000000000 --- a/lib/paraswap/lib/compound/Compound.sol +++ /dev/null @@ -1,134 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./ICompound.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract Compound is IExchange, TokenFetcher { - using Address for address; - - struct CompoundData { - address cToken; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload) private returns (uint256) { - - CompoundData memory compoundData = abi.decode(payload, (CompoundData)); - - Utils.approve(address(compoundData.cToken), address(fromToken)); - - if (address(fromToken) == address(compoundData.cToken)) { - if (address(toToken) == Utils.ethAddress()) { - require( - address(fromToken) == address(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5), - "Invalid to token" - ); - } - else { - require( - ICERC20(compoundData.cToken).underlying() == address(toToken), - "Invalid from token" - ); - } - - ICToken(compoundData.cToken).redeem(fromAmount); - } - else if(address(toToken) == address(compoundData.cToken)) { - if (address(fromToken) == Utils.ethAddress()) { - require( - address(toToken) == address(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5), - "Invalid to token" - ); - - ICEther(compoundData.cToken).mint.value(fromAmount)(); - } - else { - require( - ICERC20(compoundData.cToken).underlying() == address(fromToken), - "Invalid from token" - ); - - ICERC20(compoundData.cToken).mint(fromAmount); - } - } - else { - revert("Invalid token pair"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/compound/ICompound.sol b/lib/paraswap/lib/compound/ICompound.sol deleted file mode 100644 index 2aa062a62..000000000 --- a/lib/paraswap/lib/compound/ICompound.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - - -contract ICToken is IERC20 { - function redeem(uint redeemTokens) external returns (uint); - - function redeemUnderlying(uint redeemAmount) external returns (uint); -} - - -contract ICEther is ICToken { - function mint() external payable; -} - - -contract ICERC20 is ICToken { - function mint(uint mintAmount) external returns (uint); - - function underlying() external view returns (address token); -} diff --git a/lib/paraswap/lib/curve/Curve.sol b/lib/paraswap/lib/curve/Curve.sol deleted file mode 100644 index 9f22b7e5b..000000000 --- a/lib/paraswap/lib/curve/Curve.sol +++ /dev/null @@ -1,82 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./ICurve.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract Curve is IExchange, TokenFetcher { - using Address for address; - - struct CurveData { - int128 i; - int128 j; - uint256 deadline; - bool underlyingSwap; - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload) external payable returns (uint256) { - - CurveData memory curveData = abi.decode(payload, (CurveData)); - - Utils.approve(address(exchange), address(fromToken)); - - if (curveData.underlyingSwap) { - require( - IPool(exchange).underlying_coins(curveData.i) == address(fromToken), - "Invalid from token" - ); - require( - IPool(exchange).underlying_coins(curveData.j) == address(toToken), - "Invalid to token" - ); - ICurvePool(exchange).exchange_underlying(curveData.i, curveData.j, fromAmount, toAmount); - } - else { - require( - IPool(exchange).coins(curveData.i) == address(fromToken), - "Invalid from token" - ); - require( - IPool(exchange).coins(curveData.j) == address(toToken), - "Invalid to token" - ); - ICurvePool(exchange).exchange(curveData.i, curveData.j, fromAmount, toAmount); - } - - - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - revert("METHOD NOT SUPPORTED"); - - } -} diff --git a/lib/paraswap/lib/curve/ICurve.sol b/lib/paraswap/lib/curve/ICurve.sol deleted file mode 100644 index f23d42f92..000000000 --- a/lib/paraswap/lib/curve/ICurve.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; - -interface IPool { - function underlying_coins(int128 index) external view returns(address); - - function coins(int128 index) external view returns(address); -} - -interface ICurvePool { - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external; - - function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external; - -} - -interface ICompoundPool { - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy, uint256 deadline) external; - - function exchange(int128 i, int128 j, uint256 dx, uint256 minDy , uint256 deadline) external; -} - diff --git a/lib/paraswap/lib/idle/IIdle.sol b/lib/paraswap/lib/idle/IIdle.sol deleted file mode 100644 index 8ad991d4a..000000000 --- a/lib/paraswap/lib/idle/IIdle.sol +++ /dev/null @@ -1,22 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IIdle { - - function redeemIdleToken( - uint256 amount, - bool skipRebalance, - uint256[] calldata clientProtocolAmounts - ) - external - returns(uint256); - - function mintIdleToken( - uint256 amount, - uint256[] calldata clientProtocolAmounts - ) - external - returns(uint256); - - function token() external view returns(address); -} \ No newline at end of file diff --git a/lib/paraswap/lib/idle/Idle.sol b/lib/paraswap/lib/idle/Idle.sol deleted file mode 100644 index da9a90095..000000000 --- a/lib/paraswap/lib/idle/Idle.sol +++ /dev/null @@ -1,122 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./IIdle.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract Idle is IExchange, TokenFetcher { - using Address for address; - - struct IdleData { - address idleToken; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - return _swap( - fromToken, - toToken, - fromAmount, - toAmount, - exchange, - payload - ); - - } - - function _swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes memory payload) private returns (uint256) { - - IdleData memory data = abi.decode(payload, (IdleData)); - - Utils.approve(address(data.idleToken), address(fromToken)); - - if (address(fromToken) == address(data.idleToken)) { - require( - IIdle(data.idleToken).token() == address(toToken), - "Invalid to token" - ); - - IIdle(data.idleToken).redeemIdleToken( - fromAmount, - false, - new uint256[](0) - ); - } - else if (address(toToken) == address(data.idleToken)) { - require( - IIdle(data.idleToken).token() == address(fromToken), - "Invalid to token" - ); - IIdle(data.idleToken).mintIdleToken( - fromAmount, - new uint256[](0) - ); - } - else { - revert("Invalid token pair!!"); - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/kyber/IKyberNetwork.sol b/lib/paraswap/lib/kyber/IKyberNetwork.sol deleted file mode 100644 index e2bf7f8a4..000000000 --- a/lib/paraswap/lib/kyber/IKyberNetwork.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.5.4; - -interface IKyberNetwork { - function maxGasPrice() external view returns(uint); - - function trade( - address src, - uint srcAmount, - address dest, - address destAddress, - uint maxDestAmount, - uint minConversionRate, - address walletId - ) external payable returns (uint); -} diff --git a/lib/paraswap/lib/kyber/Kyber.sol b/lib/paraswap/lib/kyber/Kyber.sol deleted file mode 100644 index b8a688373..000000000 --- a/lib/paraswap/lib/kyber/Kyber.sol +++ /dev/null @@ -1,139 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./IKyberNetwork.sol"; -import "../IExchange.sol"; -import "../Utils.sol"; -import "../TokenFetcher.sol"; - - -contract Kyber is IExchange, TokenFetcher { - using Address for address; - - struct KyberData { - uint256 minConversionRateForBuy; - } - - address public feeWallet; - - constructor(address _feeWallet) public { - feeWallet = _feeWallet; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function setFeeWallet(address _feeWallet) external onlyOwner { - feeWallet = _feeWallet; - } - - function maxGasPrice(address kyberAddress) external view returns (uint) { - return IKyberNetwork(kyberAddress).maxGasPrice(); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address kyberAddress, - bytes calldata payload) external payable returns (uint256) { - - Utils.approve(address(kyberAddress), address(fromToken)); - - uint256 receivedAmount = 0; - - if (address(fromToken) == Utils.ethAddress()) { - receivedAmount = IKyberNetwork(kyberAddress).trade.value(fromAmount)( - address(fromToken), - fromAmount, - address(toToken), - address(this), - Utils.maxUint(), - toAmount, - feeWallet - ); - } - else { - receivedAmount = IKyberNetwork(kyberAddress).trade( - address(fromToken), - fromAmount, - address(toToken), - address(this), - Utils.maxUint(), - toAmount, - feeWallet - ); - } - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address kyberAddress, - bytes calldata payload - ) - external - payable - returns (uint256) - { - KyberData memory data = abi.decode(payload, (KyberData)); - - Utils.approve(address(kyberAddress), address(fromToken)); - - if (address(fromToken) == Utils.ethAddress()) { - IKyberNetwork(kyberAddress).trade.value(fromAmount)( - address(fromToken), - fromAmount, - address(toToken), - address(this), - toAmount, - data.minConversionRateForBuy, - feeWallet - ); - } - else { - IKyberNetwork(kyberAddress).trade( - address(fromToken), - fromAmount, - address(toToken), - address(this), - toAmount, - data.minConversionRateForBuy, - feeWallet - ); - } - - uint256 remainingAmount = Utils.tokenBalance( - address(fromToken), - address(this) - ); - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - Utils.transferTokens(address(fromToken), msg.sender, remainingAmount); - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - - } -} diff --git a/lib/paraswap/lib/oasis/IOasisExchange.sol b/lib/paraswap/lib/oasis/IOasisExchange.sol deleted file mode 100644 index cd633395c..000000000 --- a/lib/paraswap/lib/oasis/IOasisExchange.sol +++ /dev/null @@ -1,130 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IOasisExchange { - - function sellAllAmount( - address otc, - address payToken, - uint payAmt, - address buyToken, - uint minBuyAmt - ) - external - returns (uint buyAmt); - - function sellAllAmountPayEth( - address otc, - address wethToken, - address buyToken, - uint minBuyAmt - ) - external - payable - returns (uint buyAmt); - - function sellAllAmountBuyEth( - address otc, - address payToken, - uint payAmt, - address wethToken, - uint minBuyAmt - ) - external - returns (uint wethAmt); - - function createAndSellAllAmount( - address factory, - address otc, - address payToken, - uint payAmt, - address buyToken, - uint minBuyAmt - ) - external - returns (address proxy, uint buyAmt); - - function createAndSellAllAmountPayEth( - address factory, - address otc, - address buyToken, - uint minBuyAmt - ) - external - payable - returns (address proxy, uint buyAmt); - - function createAndSellAllAmountBuyEth( - address factory, - address otc, - address payToken, - uint payAmt, - uint minBuyAmt - ) - external - returns (address proxy, uint wethAmt); - - - function buyAllAmount( - address otc, - address buyToken, - uint buyAmt, - address payToken, - uint maxPayAmt - ) - external - returns (uint payAmt); - - function buyAllAmountPayEth( - address otc, - address buyToken, - uint buyAmt, - address wethToken - ) - external - payable - returns (uint wethAmt); - - function buyAllAmountBuyEth( - address otc, - address wethToken, - uint wethAmt, - address payToken, - uint maxPayAmt - ) - external - returns (uint payAmt); - - function createAndBuyAllAmount( - address factory, - address otc, - address buyToken, - uint buyAmt, - address payToken, - uint maxPayAmt - ) - external - returns (address proxy, uint payAmt); - - function createAndBuyAllAmountPayEth( - address factory, - address otc, - address buyToken, - uint buyAmt - ) - external - payable - returns (address proxy, uint wethAmt); - - function createAndBuyAllAmountBuyEth( - address factory, - address otc, - uint wethAmt, - address payToken, - uint maxPayAmt - ) - external - returns (address proxy, uint payAmt); - - -} diff --git a/lib/paraswap/lib/oasis/IProxyRegistry.sol b/lib/paraswap/lib/oasis/IProxyRegistry.sol deleted file mode 100644 index df43ec055..000000000 --- a/lib/paraswap/lib/oasis/IProxyRegistry.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.5.4; - - -interface IProxyRegistry { - - function proxies(address account) external view returns(address); -} \ No newline at end of file diff --git a/lib/paraswap/lib/oasis/Oasis.sol b/lib/paraswap/lib/oasis/Oasis.sol deleted file mode 100644 index d50a9f809..000000000 --- a/lib/paraswap/lib/oasis/Oasis.sol +++ /dev/null @@ -1,214 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "./IProxyRegistry.sol"; -import "./IOasisExchange.sol"; -import "../Utils.sol"; -import "../IExchange.sol"; -import "../TokenFetcher.sol"; - - -contract Oasis is IExchange, TokenFetcher { - using Address for address; - - struct OasisData { - address otc; - address weth; - address factory; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - OasisData memory data = abi.decode(payload, (OasisData)); - - Utils.approve(address(exchange), address(fromToken)); - - address proxy = IProxyRegistry(data.factory).proxies(address(this)); - - if (address(fromToken) == Utils.ethAddress()) { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndSellAllAmountPayEth.value(fromAmount)( - data.factory, - data.otc, - address(toToken), - toAmount - ); - } - else { - IOasisExchange(exchange).sellAllAmountPayEth.value(fromAmount)( - data.otc, - data.weth, - address(toToken), - toAmount - ); - } - } - else if (address(toToken) == Utils.ethAddress()) { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndSellAllAmountBuyEth( - data.factory, - data.otc, - address(fromToken), - fromAmount, - toAmount - ); - } - else { - IOasisExchange(exchange).sellAllAmountBuyEth( - data.otc, - address(fromToken), - fromAmount, - data.weth, - toAmount - ); - } - } - else { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndSellAllAmount( - data.factory, - data.otc, - address(fromToken), - fromAmount, - address(toToken), - toAmount - ); - } - else { - IOasisExchange(exchange).sellAllAmount( - data.otc, - address(fromToken), - fromAmount, - address(toToken), - toAmount - ); - } - } - - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload - ) - external - payable - returns (uint256) - { - - OasisData memory data = abi.decode(payload, (OasisData)); - - Utils.approve(address(exchange), address(fromToken)); - - address proxy = IProxyRegistry(data.factory).proxies(address(this)); - - if (address(fromToken) == Utils.ethAddress()) { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndBuyAllAmountPayEth.value(fromAmount)( - data.factory, - data.otc, - address(toToken), - toAmount - ); - } - else { - IOasisExchange(exchange).buyAllAmountPayEth.value(fromAmount)( - data.otc, - address(toToken), - toAmount, - data.weth - ); - } - } - else if (address(toToken) == Utils.ethAddress()) { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndBuyAllAmountBuyEth( - data.factory, - data.otc, - toAmount, - address(fromToken), - fromAmount - ); - } - else { - IOasisExchange(exchange).buyAllAmountBuyEth( - data.otc, - data.weth, - toAmount, - address(fromToken), - fromAmount - ); - } - } - else { - if (proxy == address(0)) { - IOasisExchange(exchange).createAndBuyAllAmount( - data.factory, - data.otc, - address(toToken), - toAmount, - address(fromToken), - fromAmount - ); - } - else { - IOasisExchange(exchange).buyAllAmount( - data.otc, - address(toToken), - toAmount, - address(fromToken), - fromAmount - ); - } - } - - uint256 remainingAmount = Utils.tokenBalance( - address(fromToken), - address(this) - ); - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - Utils.transferTokens(address(fromToken), msg.sender, remainingAmount); - - return receivedAmount; - } -} diff --git a/lib/paraswap/lib/uniswap/Uniswap.sol b/lib/paraswap/lib/uniswap/Uniswap.sol deleted file mode 100644 index 607c16d52..000000000 --- a/lib/paraswap/lib/uniswap/Uniswap.sol +++ /dev/null @@ -1,115 +0,0 @@ -pragma solidity ^0.5.4; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "../IExchange.sol"; -import "../Utils.sol"; -import "./IUniswapExchange.sol"; -import "./IUniswapFactory.sol"; -import "../TokenFetcher.sol"; - - -contract Uniswap is IExchange, TokenFetcher { - using SafeMath for uint256; - using Address for address; - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address factoryAddress, - bytes calldata payload) external payable returns (uint256) { - - address exchange = getExchange(fromToken, toToken, factoryAddress); - - Utils.approve(address(exchange), address(fromToken)); - - uint256 receivedAmount = 0; - - if (address(fromToken) == Utils.ethAddress()) { - receivedAmount = IUniswapExchange(exchange).ethToTokenSwapInput.value(fromAmount)(toAmount, now); - } - else if (address(toToken) == Utils.ethAddress()) { - receivedAmount = IUniswapExchange(exchange).tokenToEthSwapInput(fromAmount, toAmount, now); - } - else { - receivedAmount = IUniswapExchange(exchange).tokenToTokenSwapInput(fromAmount, toAmount, 1, now, address(toToken)); - } - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address factoryAddress, - bytes calldata payload) external payable returns (uint256) { - - address exchange = getExchange(fromToken, toToken, factoryAddress); - - Utils.approve(address(exchange), address(fromToken)); - - if (address(fromToken) == Utils.ethAddress()) { - IUniswapExchange(exchange).ethToTokenSwapOutput.value(fromAmount)(toAmount, now); - } - else if (address(toToken) == Utils.ethAddress()) { - IUniswapExchange(exchange).tokenToEthSwapOutput(toAmount, fromAmount, now); - } - else { - IUniswapExchange(exchange).tokenToTokenSwapOutput( - toAmount, - fromAmount, - Utils.maxUint(), - now, - address(toToken) - ); - } - - uint256 remainingAmount = Utils.tokenBalance( - address(fromToken), - address(this) - ); - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - Utils.transferTokens(address(fromToken), msg.sender, remainingAmount); - - return receivedAmount; - } - - function getExchange( - IERC20 fromToken, - IERC20 toToken, - address factoryAddress - ) - private - view - returns (address) - { - address exchangeAddress = address(fromToken) == Utils.ethAddress() ? address(toToken) : address(fromToken); - - return IUniswapFactory(factoryAddress).getExchange(exchangeAddress); - } -} - diff --git a/lib/paraswap/lib/uniswapv2/UniswapV2.sol b/lib/paraswap/lib/uniswapv2/UniswapV2.sol deleted file mode 100644 index dee307b83..000000000 --- a/lib/paraswap/lib/uniswapv2/UniswapV2.sol +++ /dev/null @@ -1,198 +0,0 @@ -pragma solidity ^0.5.4; -pragma experimental ABIEncoderV2; - -import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; - -import "../IExchange.sol"; -import "../Utils.sol"; -import "./IUniswapRouter.sol"; -import "../TokenFetcher.sol"; - - -contract UniswapV2 is IExchange, TokenFetcher { - using SafeMath for uint256; - using Address for address; - - struct UniswapV2Data { - address[] path; - } - - address public weth; - - constructor(address _weth) public { - weth = _weth; - } - - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); - } - - function swap( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload) external payable returns (uint256) { - - UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); - - Utils.approve(address(exchange), address(fromToken)); - - if (address(fromToken) == Utils.ethAddress()) { - require( - data.path[0] == weth, - "First element in path must be WETH" - ); - - require( - data.path[data.path.length - 1] == address(toToken), - "last element in path must be toToken" - ); - - IUniswapRouter(exchange).swapExactETHForTokens.value(fromAmount)( - toAmount, - data.path, - address(this), - now - ); - } - else if (address(toToken) == Utils.ethAddress()) { - require( - data.path[0] == address(fromToken), - "First element in path must be fromToken" - ); - - require( - data.path[data.path.length - 1] == weth, - "last element in path must be weth" - ); - IUniswapRouter(exchange).swapExactTokensForETH( - fromAmount, - toAmount, - data.path, - address(this), - now - ); - } - else { - require( - data.path[0] == address(fromToken), - "First element in path must be fromToken" - ); - - require( - data.path[data.path.length - 1] == address(toToken), - "last element in path must be toToken" - ); - IUniswapRouter(exchange).swapExactTokensForTokens( - fromAmount, - toAmount, - data.path, - address(this), - now - ); - } - - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; - } - - function buy( - IERC20 fromToken, - IERC20 toToken, - uint256 fromAmount, - uint256 toAmount, - address exchange, - bytes calldata payload) external payable returns (uint256) { - - UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); - - Utils.approve(address(exchange), address(fromToken)); - - if (address(fromToken) == Utils.ethAddress()) { - require( - data.path[0] == weth, - "First element in path must be WETH" - ); - - require( - data.path[data.path.length - 1] == address(toToken), - "last element in path must be toToken" - ); - - IUniswapRouter(exchange).swapETHForExactTokens.value(fromAmount)( - toAmount, - data.path, - address(this), - now - ); - } - else if (address(toToken) == Utils.ethAddress()) { - require( - data.path[0] == address(fromToken), - "First element in path must be fromToken" - ); - - require( - data.path[data.path.length - 1] == weth, - "last element in path must be weth" - ); - IUniswapRouter(exchange).swapTokensForExactETH( - toAmount, - fromAmount, - data.path, - address(this), - now - ); - } - else { - require( - data.path[0] == address(fromToken), - "First element in path must be fromToken" - ); - - require( - data.path[data.path.length - 1] == address(toToken), - "last element in path must be toToken" - ); - IUniswapRouter(exchange).swapTokensForExactTokens( - toAmount, - fromAmount, - data.path, - address(this), - now - ); - } - - uint256 remainingAmount = Utils.tokenBalance( - address(fromToken), - address(this) - ); - uint256 receivedAmount = Utils.tokenBalance( - address(toToken), - address(this) - ); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - Utils.transferTokens(address(fromToken), msg.sender, remainingAmount); - - return receivedAmount; - } -} - diff --git a/lib/uniswap/UniswapExchange.json b/lib/uniswap/UniswapExchange.json deleted file mode 100644 index 977288eab..000000000 --- a/lib/uniswap/UniswapExchange.json +++ /dev/null @@ -1,1013 +0,0 @@ -{ - "contractName": "UniswapExchange", - "abi": [ - { - "name": "TokenPurchase", - "inputs": [ - { - "type": "address", - "name": "buyer", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_sold", - "indexed": true - }, - { - "type": "uint256", - "name": "tokens_bought", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "EthPurchase", - "inputs": [ - { - "type": "address", - "name": "buyer", - "indexed": true - }, - { - "type": "uint256", - "name": "tokens_sold", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_bought", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "AddLiquidity", - "inputs": [ - { - "type": "address", - "name": "provider", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_amount", - "indexed": true - }, - { - "type": "uint256", - "name": "token_amount", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "RemoveLiquidity", - "inputs": [ - { - "type": "address", - "name": "provider", - "indexed": true - }, - { - "type": "uint256", - "name": "eth_amount", - "indexed": true - }, - { - "type": "uint256", - "name": "token_amount", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Transfer", - "inputs": [ - { - "type": "address", - "name": "_from", - "indexed": true - }, - { - "type": "address", - "name": "_to", - "indexed": true - }, - { - "type": "uint256", - "name": "_value", - "indexed": false - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "Approval", - "inputs": [ - { - "type": "address", - "name": "_owner", - "indexed": true - }, - { - "type": "address", - "name": "_spender", - "indexed": true - }, - { - "type": "uint256", - "name": "_value", - "indexed": false - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "setup", - "outputs": [], - "inputs": [ - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 175875 - }, - { - "name": "addLiquidity", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_liquidity" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function", - "gas": 82616 - }, - { - "name": "removeLiquidity", - "outputs": [ - { - "type": "uint256", - "name": "out" - }, - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "amount" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 116814 - }, - { - "name": "__default__", - "outputs": [], - "inputs": [], - "constant": false, - "payable": true, - "type": "function" - }, - { - "name": "ethToTokenSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function", - "gas": 12757 - }, - { - "name": "ethToTokenTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "min_tokens" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": true, - "type": "function", - "gas": 12965 - }, - { - "name": "ethToTokenSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": true, - "type": "function", - "gas": 50463 - }, - { - "name": "ethToTokenTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": true, - "type": "function", - "gas": 50671 - }, - { - "name": "tokenToEthSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 47503 - }, - { - "name": "tokenToEthTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_eth" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 47712 - }, - { - "name": "tokenToEthSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 50175 - }, - { - "name": "tokenToEthTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - }, - { - "type": "uint256", - "name": "max_tokens" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 50384 - }, - { - "name": "tokenToTokenSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 51007 - }, - { - "name": "tokenToTokenTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 51098 - }, - { - "name": "tokenToTokenSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 54928 - }, - { - "name": "tokenToTokenTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "token_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 55019 - }, - { - "name": "tokenToExchangeSwapInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 49342 - }, - { - "name": "tokenToExchangeTransferInput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - }, - { - "type": "uint256", - "name": "min_tokens_bought" - }, - { - "type": "uint256", - "name": "min_eth_bought" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 49532 - }, - { - "name": "tokenToExchangeSwapOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 53233 - }, - { - "name": "tokenToExchangeTransferOutput", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - }, - { - "type": "uint256", - "name": "max_tokens_sold" - }, - { - "type": "uint256", - "name": "max_eth_sold" - }, - { - "type": "uint256", - "name": "deadline" - }, - { - "type": "address", - "name": "recipient" - }, - { - "type": "address", - "name": "exchange_addr" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 53423 - }, - { - "name": "getEthToTokenInputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_sold" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 5542 - }, - { - "name": "getEthToTokenOutputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_bought" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 6872 - }, - { - "name": "getTokenToEthInputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "tokens_sold" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 5637 - }, - { - "name": "getTokenToEthOutputPrice", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "eth_bought" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 6897 - }, - { - "name": "tokenAddress", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1413 - }, - { - "name": "factoryAddress", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1443 - }, - { - "name": "balanceOf", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_owner" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 1645 - }, - { - "name": "transfer", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_to" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 75034 - }, - { - "name": "transferFrom", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_from" - }, - { - "type": "address", - "name": "_to" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 110907 - }, - { - "name": "approve", - "outputs": [ - { - "type": "bool", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_spender" - }, - { - "type": "uint256", - "name": "_value" - } - ], - "constant": false, - "payable": false, - "type": "function", - "gas": 38769 - }, - { - "name": "allowance", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "_owner" - }, - { - "type": "address", - "name": "_spender" - } - ], - "constant": true, - "payable": false, - "type": "function", - "gas": 1925 - }, - { - "name": "name", - "outputs": [ - { - "type": "bytes32", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1623 - }, - { - "name": "symbol", - "outputs": [ - { - "type": "bytes32", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1653 - }, - { - "name": "decimals", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1683 - }, - { - "name": "totalSupply", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function", - "gas": 1713 - } -], - "compiler": { - "name": "solc", - "version": "0.5.4+commit.9549d8ff.Emscripten.clang" - }, - "bytecode": "0x61309c56600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a0526366d38203600051141561013b57602060046101403734156100b457600080fd5b60043560205181106100c557600080fd5b506000610140511415600654156007541516166100e157600080fd5b33600755610140516006557f556e6973776170205631000000000000000000000000000000000000000000006000557f554e492d563100000000000000000000000000000000000000000000000000006001556012600255005b63422f104360005114156105ab5760606004610140376000341160006101605111164261018051111661016d57600080fd5b6003546101a05260006101a051111561043e576000610140511161019057600080fd5b343031101561019e57600080fd5b343031036103a0526006543b6101b357600080fd5b6006543014156101c257600080fd5b602061046060246370a082316103e05230610400526103fc6006545afa6101e857600080fd5b600050610460516103c0526103a05161020057600080fd5b6103a05134151561021257600061022f565b6103c051346103c0513402041461022857600080fd5b6103c05134025b0460016103a05161023f57600080fd5b6103a05134151561025157600061026e565b6103c051346103c0513402041461026757600080fd5b6103c05134025b0401101561027b57600080fd5b60016103a05161028a57600080fd5b6103a05134151561029c5760006102b9565b6103c051346103c051340204146102b257600080fd5b6103c05134025b0401610480526103a0516102cc57600080fd5b6103a0513415156102de5760006102fb565b6101a051346101a051340204146102f457600080fd5b6101a05134025b046104a052610140516104a0511015610480516101605110151661031e57600080fd5b60043360e05260c052604060c02080546104a051825401101561034057600080fd5b6104a0518154018155506101a0516104a0516101a05101101561036257600080fd5b6104a0516101a051016003556006543b61037b57600080fd5b60065430141561038a57600080fd5b602061058060646323b872dd6104c052336104e052306105005261048051610520526104dc60006006545af16103bf57600080fd5b600050610580516103cf57600080fd5b6104805134337f06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca60006000a46104a0516105a0523360007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206105a0a36104a05160005260206000f36105a9565b633b9aca003410156000600654141560006007541415161661045f57600080fd5b306007543b61046d57600080fd5b60075430141561047c57600080fd5b602061024060246306f2bf626101c0526006546101e0526101dc6007545afa6104a457600080fd5b60005061024051146104b557600080fd5b6101605161026052303161028052610280516003556102805160043360e05260c052604060c020556006543b6104ea57600080fd5b6006543014156104f957600080fd5b602061036060646323b872dd6102a052336102c052306102e05261026051610300526102bc60006006545af161052e57600080fd5b6000506103605161053e57600080fd5b6102605134337f06239653922ac7bea6aa2b19dc486b9361821d37712eb796adfd38d81de278ca60006000a461028051610380523360007fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610380a36102805160005260206000f35b005b63f88bf15a600051141561084a57608060046101403734156105cc57600080fd5b600061018051116000610160511116426101a051116000610140511116166105f357600080fd5b6003546101c05260006101c0511161060a57600080fd5b6006543b61061757600080fd5b60065430141561062657600080fd5b602061028060246370a0823161020052306102205261021c6006545afa61064c57600080fd5b600050610280516101e0526101c05161066457600080fd5b6101c051610140511515610679576000610699565b30316101405130316101405102041461069157600080fd5b303161014051025b046102a0526101c0516106ab57600080fd5b6101c0516101405115156106c05760006106e6565b6101e051610140516101e051610140510204146106dc57600080fd5b6101e05161014051025b046102c052610180516102c0511015610160516102a05110151661070957600080fd5b60043360e05260c052604060c020610140518154101561072857600080fd5b61014051815403815550610140516101c051101561074557600080fd5b610140516101c0510360035560006000600060006102a051336000f161076a57600080fd5b6006543b61077757600080fd5b60065430141561078657600080fd5b6020610380604463a9059cbb6102e05233610300526102c051610320526102fc60006006545af16107b657600080fd5b600050610380516107c657600080fd5b6102c0516102a051337f0fbf06c058b90cb038a618f8c2acbf6145f8b3570fd1fa56abb8f0f3f05b36e860006000a4610140516103a0526000337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206103a0a360406103c0526103e06102a05181526102c0518160200152506103c0516103e0f3005b6000156109c6575b6101a05261014052610160526101805260006101805111600061016051111661087a57600080fd5b61014051151561088b5760006108ae565b6103e5610140516103e5610140510204146108a557600080fd5b6103e561014051025b6101c0526101c05115156108c35760006108e9565b610180516101c051610180516101c0510204146108df57600080fd5b610180516101c051025b6101e0526101605115156108fe576000610921565b6103e8610160516103e86101605102041461091857600080fd5b6103e861016051025b6101c051610160511515610936576000610959565b6103e8610160516103e86101605102041461095057600080fd5b6103e861016051025b01101561096557600080fd5b6101c05161016051151561097a57600061099d565b6103e8610160516103e86101605102041461099457600080fd5b6103e861016051025b0161020052610200516109af57600080fd5b610200516101e051046000526000516101a0515650005b600015610bf3575b6101a0526101405261016052610180526000610180511160006101605111166109f657600080fd5b610160511515610a07576000610a2d565b61014051610160516101405161016051020414610a2357600080fd5b6101405161016051025b1515610a3a576000610af6565b6103e8610160511515610a4e576000610a74565b61014051610160516101405161016051020414610a6a57600080fd5b6101405161016051025b6103e8610160511515610a88576000610aae565b61014051610160516101405161016051020414610aa457600080fd5b6101405161016051025b020414610aba57600080fd5b6103e8610160511515610ace576000610af4565b61014051610160516101405161016051020414610aea57600080fd5b6101405161016051025b025b6101c05261014051610180511015610b0d57600080fd5b6101405161018051031515610b23576000610b8e565b6103e561014051610180511015610b3957600080fd5b6101405161018051036103e561014051610180511015610b5857600080fd5b610140516101805103020414610b6d57600080fd5b6103e561014051610180511015610b8357600080fd5b610140516101805103025b6101e0526101e051610b9f57600080fd5b6101e0516101c0510460016101e051610bb757600080fd5b6101e0516101c05104011015610bcc57600080fd5b60016101e051610bdb57600080fd5b6101e0516101c05104016000526000516101a0515650005b600015610df4575b6101e0526101405261016052610180526101a0526101c0526000610160511160006101405111164261018051101516610c3357600080fd5b6006543b610c4057600080fd5b600654301415610c4f57600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa610c7557600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516389f2a8716102e05261014051610300526101405130311015610cd657600080fd5b6101405130310361032052610200516103405261034051610320516103005160065801610852565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c052610160516102c0511015610d5157600080fd5b6006543b610d5e57600080fd5b600654301415610d6d57600080fd5b6020610460604463a9059cbb6103c0526101c0516103e0526102c051610400526103dc60006006545af1610da057600080fd5b60005061046051610db057600080fd5b6102c051610140516101a0517fcd60aa75dea3072fbc07ae6d7d856b5dc5f4eee88854f5b4abf7b680ef8bc50f60006000a46102c0516000526000516101e0515650005b63f39b5b9b6000511415610e715760406004610140376101405161016051638c717a3361018052346101a052610140516101c052610160516101e0523361020052336102205261022051610200516101e0516101c0516101a05160065801610bfb565b6102805261016052610140526102805160005260206000f3005b63ad65d76d6000511415610f245760606004610140376044356020518110610e9857600080fd5b5060006101805114153061018051141516610eb257600080fd5b610140516101605161018051638c717a336101a052346101c052610140516101e0526101605161020052336102205261018051610240526102405161022051610200516101e0516101c05160065801610bfb565b6102a0526101805261016052610140526102a05160005260206000f3005b60001561116c575b6101e0526101405261016052610180526101a0526101c0526000610160511160006101405111164261018051101516610f6457600080fd5b6006543b610f7157600080fd5b600654301415610f8057600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa610fa657600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c05163fd11c2236102e0526101405161030052610160513031101561100757600080fd5b61016051303103610320526102005161034052610340516103205161030051600658016109ce565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c05260016102c051026103e0526103e05161016051101561108d57600080fd5b6103e05161016051036103c05260006103c05111156110c35760006000600060006103c0516101a0516000f16110c257600080fd5b5b6006543b6110d057600080fd5b6006543014156110df57600080fd5b60206104a0604463a9059cbb610400526101c05161042052610140516104405261041c60006006545af161111257600080fd5b6000506104a05161112257600080fd5b6101405160016102c051026101a0517fcd60aa75dea3072fbc07ae6d7d856b5dc5f4eee88854f5b4abf7b680ef8bc50f60006000a460016102c051026000526000516101e0515650005b636b1d4db760005114156111e95760406004610140376101405161016051632dff394e61018052610140516101a052346101c052610160516101e0523361020052336102205261022051610200516101e0516101c0516101a05160065801610f2c565b6102805261016052610140526102805160005260206000f3005b630b573638600051141561129c576060600461014037604435602051811061121057600080fd5b506000610180511415306101805114151661122a57600080fd5b610140516101605161018051632dff394e6101a052610140516101c052346101e0526101605161020052336102205261018051610240526102405161022051610200516101e0516101c05160065801610f2c565b6102a0526101805261016052610140526102a05160005260206000f3005b6000156114b3575b6101e0526101405261016052610180526101a0526101c05260006101605111600061014051111642610180511015166112dc57600080fd5b6006543b6112e957600080fd5b6006543014156112f857600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa61131e57600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516389f2a8716102e0526101405161030052610200516103205230316103405261034051610320516103005160065801610852565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c05260016102c051026103c052610160516103c05110156113ef57600080fd5b60006000600060006103c0516101c0516000f161140b57600080fd5b6006543b61141857600080fd5b60065430141561142757600080fd5b60206104a060646323b872dd6103e0526101a05161040052306104205261014051610440526103fc60006006545af161145f57600080fd5b6000506104a05161146f57600080fd5b6103c051610140516101a0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46103c0516000526000516101e0515650005b6395e3c50b600051141561154657606060046101403734156114d457600080fd5b61014051610160516101805163fa1bb7be6101a052610140516101c052610160516101e0526101805161020052336102205233610240526102405161022051610200516101e0516101c051600658016112a4565b6102a0526101805261016052610140526102a05160005260206000f3005b637237e031600051141561160f576080600461014037341561156757600080fd5b606435602051811061157857600080fd5b5060006101a0511415306101a05114151661159257600080fd5b6101405161016051610180516101a05163fa1bb7be6101c052610140516101e0526101605161020052610180516102205233610240526101a05161026052610260516102405161022051610200516101e051600658016112a4565b6102c0526101a0526101805261016052610140526102c05160005260206000f3005b600015611813575b6101e0526101405261016052610180526101a0526101c05260006101405111426101805110151661164757600080fd5b6006543b61165457600080fd5b60065430141561166357600080fd5b60206102a060246370a0823161022052306102405261023c6006545afa61168957600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c05163fd11c2236102e05261014051610300526102005161032052303161034052610340516103205161030051600658016109ce565b6103a0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103a0516102c0526102c05161016051101561174f57600080fd5b6000600060006000610140516101c0516000f161176b57600080fd5b6006543b61177857600080fd5b60065430141561178757600080fd5b602061048060646323b872dd6103c0526101a0516103e05230610400526102c051610420526103dc60006006545af16117bf57600080fd5b600050610480516117cf57600080fd5b610140516102c0516101a0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46102c0516000526000516101e0515650005b63013efd8b60005114156118a6576060600461014037341561183457600080fd5b61014051610160516101805163984fe8f66101a052610140516101c052610160516101e0526101805161020052336102205233610240526102405161022051610200516101e0516101c05160065801611617565b6102a0526101805261016052610140526102a05160005260206000f3005b63d4e4841d600051141561196f57608060046101403734156118c757600080fd5b60643560205181106118d857600080fd5b5060006101a0511415306101a0511415166118f257600080fd5b6101405161016051610180516101a05163984fe8f66101c052610140516101e0526101605161020052610180516102205233610240526101a05161026052610260516102405161022051610200516101e05160065801611617565b6102c0526101a0526101805261016052610140526102c05160005260206000f3005b600015611c0a575b610220526101405261016052610180526101a0526101c0526101e0526102005260006101805111600061016051111660006101405111426101a051101516166119bf57600080fd5b600061020051141530610200511415166119d857600080fd5b6006543b6119e557600080fd5b6006543014156119f457600080fd5b60206102e060246370a0823161026052306102805261027c6006545afa611a1a57600080fd5b6000506102e051610240526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e051610300516389f2a871610320526101405161034052610240516103605230316103805261038051610360516103405160065801610852565b6103e052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05161030052600161030051026104005261018051610400511015611afb57600080fd5b6006543b611b0857600080fd5b600654301415611b1757600080fd5b60206104e060646323b872dd610420526101c051610440523061046052610140516104805261043c60006006545af1611b4f57600080fd5b6000506104e051611b5f57600080fd5b610200513b611b6d57600080fd5b61020051301415611b7d57600080fd5b60206105e0606463ad65d76d6105205261016051610540526101a051610560526101e0516105805261053c61040051610200515af1611bbb57600080fd5b6000506105e0516105005261040051610140516101c0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a461050051600052600051610220515650005b63ddf7e1a76000511415611d575760a06004610140373415611c2b57600080fd5b6084356020518110611c3c57600080fd5b506007543b611c4a57600080fd5b600754301415611c5957600080fd5b602061028060246306f2bf62610200526101c0516102205261021c6007545afa611c8257600080fd5b600050610280516101e0526101405161016051610180516101a0516101c0516101e051610200516102205161024051610260516102805163204ea33b6102a052610140516102c052610160516102e05261018051610300526101a05161032052336103405233610360526101e0516103805261038051610360516103405161032051610300516102e0516102c05160065801611977565b6103e05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05160005260206000f3005b63f552d91b6000511415611ec15760c06004610140373415611d7857600080fd5b6084356020518110611d8957600080fd5b5060a4356020518110611d9b57600080fd5b506007543b611da957600080fd5b600754301415611db857600080fd5b60206102a060246306f2bf62610220526101e0516102405261023c6007545afa611de157600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a05163204ea33b6102c052610140516102e052610160516103005261018051610320526101a0516103405233610360526101c05161038052610200516103a0526103a05161038051610360516103405161032051610300516102e05160065801611977565b610400526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104005160005260206000f3005b6000156121d7575b610220526101405261016052610180526101a0526101c0526101e05261020052600061018051116000610140511116426101a051101516611f0957600080fd5b60006102005114153061020051141516611f2257600080fd5b610200513b611f3057600080fd5b61020051301415611f4057600080fd5b60206102e060246359e9486261026052610140516102805261027c610200515afa611f6a57600080fd5b6000506102e051610240526006543b611f8257600080fd5b600654301415611f9157600080fd5b60206103a060246370a0823161032052306103405261033c6006545afa611fb757600080fd5b6000506103a051610300526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051610380516103a0516103c05163fd11c2236103e05261024051610400526103005161042052303161044052610440516104205161040051600658016109ce565b6104a0526103c0526103a05261038052610360526103405261032052610300526102e0526102c0526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104a0516103c052610240516101805110156103c051610160511015166120c857600080fd5b6006543b6120d557600080fd5b6006543014156120e457600080fd5b602061058060646323b872dd6104c0526101c0516104e05230610500526103c051610520526104dc60006006545af161211c57600080fd5b6000506105805161212c57600080fd5b610200513b61213a57600080fd5b6102005130141561214a57600080fd5b60206106806064630b5736386105c052610140516105e0526101a051610600526101e051610620526105dc61024051610200515af161218857600080fd5b600050610680516105a052610240516103c0516101c0517f7f4091b46c33e918a0f3aa42307641d17bb67029427a5369e54b35398423870560006000a46103c051600052600051610220515650005b63b040d54560005114156123245760a060046101403734156121f857600080fd5b608435602051811061220957600080fd5b506007543b61221757600080fd5b60075430141561222657600080fd5b602061028060246306f2bf62610200526101c0516102205261021c6007545afa61224f57600080fd5b600050610280516101e0526101405161016051610180516101a0516101c0516101e0516102005161022051610240516102605161028051631a7b28f26102a052610140516102c052610160516102e05261018051610300526101a05161032052336103405233610360526101e0516103805261038051610360516103405161032051610300516102e0516102c05160065801611ec9565b6103e05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526103e05160005260206000f3005b63f3c0efe9600051141561248e5760c0600461014037341561234557600080fd5b608435602051811061235657600080fd5b5060a435602051811061236857600080fd5b506007543b61237657600080fd5b60075430141561238557600080fd5b60206102a060246306f2bf62610220526101e0516102405261023c6007545afa6123ae57600080fd5b6000506102a051610200526101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a051631a7b28f26102c052610140516102e052610160516103005261018051610320526101a0516103405233610360526101c05161038052610200516103a0526103a05161038051610360516103405161032051610300516102e05160065801611ec9565b610400526102a05261028052610260526102405261022052610200526101e0526101c0526101a0526101805261016052610140526104005160005260206000f3005b63b1cb43bf600051141561255b5760a060046101403734156124af57600080fd5b60843560205181106124c057600080fd5b506101405161016051610180516101a0516101c05163204ea33b6101e0526101405161020052610160516102205261018051610240526101a051610260523361028052336102a0526101c0516102c0526102c0516102a051610280516102605161024051610220516102005160065801611977565b610320526101c0526101a0526101805261016052610140526103205160005260206000f3005b63ec384a3e60005114156126555760c0600461014037341561257c57600080fd5b608435602051811061258d57600080fd5b5060a435602051811061259f57600080fd5b50306101c05114156125b057600080fd5b6101405161016051610180516101a0516101c0516101e05163204ea33b610200526101405161022052610160516102405261018051610260526101a05161028052336102a0526101c0516102c0526101e0516102e0526102e0516102c0516102a0516102805161026051610240516102205160065801611977565b610340526101e0526101c0526101a0526101805261016052610140526103405160005260206000f3005b63ea650c7d60005114156127225760a0600461014037341561267657600080fd5b608435602051811061268757600080fd5b506101405161016051610180516101a0516101c051631a7b28f26101e0526101405161020052610160516102205261018051610240526101a051610260523361028052336102a0526101c0516102c0526102c0516102a051610280516102605161024051610220516102005160065801611ec9565b610320526101c0526101a0526101805261016052610140526103205160005260206000f3005b63981a1327600051141561281c5760c0600461014037341561274357600080fd5b608435602051811061275457600080fd5b5060a435602051811061276657600080fd5b50306101c051141561277757600080fd5b6101405161016051610180516101a0516101c0516101e051631a7b28f2610200526101405161022052610160516102405261018051610260526101a05161028052336102a0526101c0516102c0526101e0516102e0526102e0516102c0516102a0516102805161026051610240516102205160065801611ec9565b610340526101e0526101c0526101a0526101805261016052610140526103405160005260206000f3005b63cd7724c36000511415612918576020600461014037341561283d57600080fd5b6000610140511161284d57600080fd5b6006543b61285a57600080fd5b60065430141561286957600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa61288f57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e051610200516389f2a871610220526101405161024052303161026052610160516102805261028051610260516102405160065801610852565b6102e052610200526101e0526101c0526101a0526101805261016052610140526102e05160005260206000f3005b6359e948626000511415612a27576020600461014037341561293957600080fd5b6000610140511161294957600080fd5b6006543b61295657600080fd5b60065430141561296557600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa61298b57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e051610200516102205163fd11c223610240526101405161026052303161028052610160516102a0526102a0516102805161026051600658016109ce565b6103005261022052610200526101e0526101c0526101a05261018052610160526101405261030051610220526001610220510260005260206000f3005b6395b68fe76000511415612b365760206004610140373415612a4857600080fd5b60006101405111612a5857600080fd5b6006543b612a6557600080fd5b600654301415612a7457600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa612a9a57600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e05161020051610220516389f2a871610240526101405161026052610160516102805230316102a0526102a051610280516102605160065801610852565b6103005261022052610200526101e0526101c0526101a05261018052610160526101405261030051610220526001610220510260005260206000f3005b632640f62c6000511415612c325760206004610140373415612b5757600080fd5b60006101405111612b6757600080fd5b6006543b612b7457600080fd5b600654301415612b8357600080fd5b602061020060246370a0823161018052306101a05261019c6006545afa612ba957600080fd5b60005061020051610160526101405161016051610180516101a0516101c0516101e0516102005163fd11c2236102205261014051610240526101605161026052303161028052610280516102605161024051600658016109ce565b6102e052610200526101e0526101c0526101a0526101805261016052610140526102e05160005260206000f3005b639d76ea586000511415612c58573415612c4b57600080fd5b60065460005260206000f3005b63966dae0e6000511415612c7e573415612c7157600080fd5b60075460005260206000f3005b6370a082316000511415612ccd5760206004610140373415612c9f57600080fd5b6004356020518110612cb057600080fd5b5060046101405160e05260c052604060c0205460005260206000f3005b63a9059cbb6000511415612d985760406004610140373415612cee57600080fd5b6004356020518110612cff57600080fd5b5060043360e05260c052604060c0206101605181541015612d1f57600080fd5b6101605181540381555060046101405160e05260c052604060c0208054610160518254011015612d4e57600080fd5b61016051815401815550610160516101805261014051337fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef6020610180a3600160005260206000f3005b6323b872dd6000511415612eb35760606004610140373415612db957600080fd5b6004356020518110612dca57600080fd5b506024356020518110612ddc57600080fd5b5060046101405160e05260c052604060c0206101805181541015612dff57600080fd5b6101805181540381555060046101605160e05260c052604060c0208054610180518254011015612e2e57600080fd5b6101805181540181555060056101405160e05260c052604060c0203360e05260c052604060c0206101805181541015612e6657600080fd5b61018051815403815550610180516101a05261016051610140517fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60206101a0a3600160005260206000f3005b63095ea7b36000511415612f485760406004610140373415612ed457600080fd5b6004356020518110612ee557600080fd5b506101605160053360e05260c052604060c0206101405160e05260c052604060c02055610160516101805261014051337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9256020610180a3600160005260206000f3005b63dd62ed3e6000511415612fb85760406004610140373415612f6957600080fd5b6004356020518110612f7a57600080fd5b506024356020518110612f8c57600080fd5b5060056101405160e05260c052604060c0206101605160e05260c052604060c0205460005260206000f3005b6306fdde036000511415612fde573415612fd157600080fd5b60005460005260206000f3005b6395d89b416000511415613004573415612ff757600080fd5b60015460005260206000f3005b63313ce567600051141561302a57341561301d57600080fd5b60025460005260206000f3005b6318160ddd600051141561305057341561304357600080fd5b60035460005260206000f3005b638c717a33610140523461016052600161018052426101a052336101c052336101e0526101e0516101c0516101a051610180516101605160065801610bfb565b610240526102405b61000461309c0361000460003961000461309c036000f3" -} \ No newline at end of file diff --git a/lib/uniswap/UniswapFactory.json b/lib/uniswap/UniswapFactory.json deleted file mode 100644 index 1ff3a57f3..000000000 --- a/lib/uniswap/UniswapFactory.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "contractName": "UniswapFactory", - "abi": [ - { - "name": "NewExchange", - "inputs": [ - { - "type": "address", - "name": "token", - "indexed": true - }, - { - "type": "address", - "name": "exchange", - "indexed": true - } - ], - "anonymous": false, - "type": "event" - }, - { - "name": "initializeFactory", - "outputs": [], - "inputs": [ - { - "type": "address", - "name": "template" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "createExchange", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "token" - } - ], - "constant": false, - "payable": false, - "type": "function" - }, - { - "name": "getExchange", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "token" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "getToken", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [ - { - "type": "address", - "name": "exchange" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "getTokenWithId", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [ - { - "type": "uint256", - "name": "token_id" - } - ], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "exchangeTemplate", - "outputs": [ - { - "type": "address", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - }, - { - "name": "tokenCount", - "outputs": [ - { - "type": "uint256", - "name": "out" - } - ], - "inputs": [], - "constant": true, - "payable": false, - "type": "function" - } - ], - "compiler": { - "name": "solc", - "version": "0.5.4+commit.9549d8ff.Emscripten.clang" - }, - "bytecode": "0x6103f056600035601c52740100000000000000000000000000000000000000006020526f7fffffffffffffffffffffffffffffff6040527fffffffffffffffffffffffffffffffff8000000000000000000000000000000060605274012a05f1fffffffffffffffffffffffffdabf41c006080527ffffffffffffffffffffffffed5fa0e000000000000000000000000000000000060a05263538a3f0e60005114156100ed57602060046101403734156100b457600080fd5b60043560205181106100c557600080fd5b50600054156100d357600080fd5b60006101405114156100e457600080fd5b61014051600055005b631648f38e60005114156102bf576020600461014037341561010e57600080fd5b600435602051811061011f57600080fd5b50600061014051141561013157600080fd5b6000600054141561014157600080fd5b60026101405160e05260c052604060c020541561015d57600080fd5b7f602e600c600039602e6000f33660006000376110006000366000730000000000610180526c010000000000000000000000006000540261019b527f5af41558576110006000f30000000000000000000000000000000000000000006101af5260406101806000f0806101cf57600080fd5b61016052610160513b6101e157600080fd5b610160513014156101f157600080fd5b6000600060246366d3820361022052610140516102405261023c6000610160515af161021c57600080fd5b6101605160026101405160e05260c052604060c020556101405160036101605160e05260c052604060c02055600154600160015401101561025c57600080fd5b6001600154016102a0526102a0516001556101405160046102a05160e05260c052604060c0205561016051610140517f9d42cb017eb05bd8944ab536a8b35bc68085931dd5f4356489801453923953f960006000a36101605160005260206000f3005b6306f2bf62600051141561030e57602060046101403734156102e057600080fd5b60043560205181106102f157600080fd5b5060026101405160e05260c052604060c0205460005260206000f3005b6359770438600051141561035d576020600461014037341561032f57600080fd5b600435602051811061034057600080fd5b5060036101405160e05260c052604060c0205460005260206000f3005b63aa65a6c0600051141561039a576020600461014037341561037e57600080fd5b60046101405160e05260c052604060c0205460005260206000f3005b631c2bbd1860005114156103c05734156103b357600080fd5b60005460005260206000f3005b639f181b5e60005114156103e65734156103d957600080fd5b60015460005260206000f3005b60006000fd5b6100046103f0036100046000396100046103f0036000f3" -} \ No newline at end of file diff --git a/lib/uniswapV2/uniswap-v2-core/interfaces/IERC20.sol b/lib/uniswapV2/uniswap-v2-core/interfaces/IERC20.sol deleted file mode 100644 index c1e8c3e65..000000000 --- a/lib/uniswapV2/uniswap-v2-core/interfaces/IERC20.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity >=0.5.0; - -interface IERC20 { - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function decimals() external view returns (uint8); - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function allowance(address owner, address spender) external view returns (uint); - - function approve(address spender, uint value) external returns (bool); - function transfer(address to, uint value) external returns (bool); - function transferFrom(address from, address to, uint value) external returns (bool); -} diff --git a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Callee.sol b/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Callee.sol deleted file mode 100644 index f3910ab6f..000000000 --- a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Callee.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV2Callee { - function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; -} diff --git a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2ERC20.sol b/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2ERC20.sol deleted file mode 100644 index 871893151..000000000 --- a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2ERC20.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV2ERC20 { - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - function name() external pure returns (string memory); - function symbol() external pure returns (string memory); - function decimals() external pure returns (uint8); - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function allowance(address owner, address spender) external view returns (uint); - - function approve(address spender, uint value) external returns (bool); - function transfer(address to, uint value) external returns (bool); - function transferFrom(address from, address to, uint value) external returns (bool); - - function DOMAIN_SEPARATOR() external view returns (bytes32); - function PERMIT_TYPEHASH() external pure returns (bytes32); - function nonces(address owner) external view returns (uint); - - function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; -} diff --git a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Factory.sol b/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Factory.sol deleted file mode 100644 index c3b25f67a..000000000 --- a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Factory.sol +++ /dev/null @@ -1,21 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV2Factory { - event PairCreated(address indexed token0, address indexed token1, address pair, uint); - - function feeTo() external view returns (address); - function feeToSetter() external view returns (address); - - function getPair(address tokenA, address tokenB) external view returns (address pair); - function allPairs(uint) external view returns (address pair); - function allPairsLength() external view returns (uint); - - function createPair(address tokenA, address tokenB) external returns (address pair); - - function setFeeTo(address) external; - function setFeeToSetter(address) external; - - // !! Argent Modification !! - // The following method was added to be able to use the correct UniswapV2Pair init code in UniswapV2Library - function getKeccakOfPairCreationCode() external pure returns (bytes32); -} diff --git a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Pair.sol b/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Pair.sol deleted file mode 100644 index 762dd4d00..000000000 --- a/lib/uniswapV2/uniswap-v2-core/interfaces/IUniswapV2Pair.sol +++ /dev/null @@ -1,52 +0,0 @@ -pragma solidity >=0.5.0; - -interface IUniswapV2Pair { - event Approval(address indexed owner, address indexed spender, uint value); - event Transfer(address indexed from, address indexed to, uint value); - - function name() external pure returns (string memory); - function symbol() external pure returns (string memory); - function decimals() external pure returns (uint8); - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function allowance(address owner, address spender) external view returns (uint); - - function approve(address spender, uint value) external returns (bool); - function transfer(address to, uint value) external returns (bool); - function transferFrom(address from, address to, uint value) external returns (bool); - - function DOMAIN_SEPARATOR() external view returns (bytes32); - function PERMIT_TYPEHASH() external pure returns (bytes32); - function nonces(address owner) external view returns (uint); - - function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; - - event Mint(address indexed sender, uint amount0, uint amount1); - event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); - event Swap( - address indexed sender, - uint amount0In, - uint amount1In, - uint amount0Out, - uint amount1Out, - address indexed to - ); - event Sync(uint112 reserve0, uint112 reserve1); - - function MINIMUM_LIQUIDITY() external pure returns (uint); - function factory() external view returns (address); - function token0() external view returns (address); - function token1() external view returns (address); - function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); - function price0CumulativeLast() external view returns (uint); - function price1CumulativeLast() external view returns (uint); - function kLast() external view returns (uint); - - function mint(address to) external returns (uint liquidity); - function burn(address to) external returns (uint amount0, uint amount1); - function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; - function skim(address to) external; - function sync() external; - - function initialize(address, address) external; -} diff --git a/lib/uniswapV2/uniswap-v2-core/libraries/SafeMath.sol b/lib/uniswapV2/uniswap-v2-core/libraries/SafeMath.sol deleted file mode 100644 index 3697786f8..000000000 --- a/lib/uniswapV2/uniswap-v2-core/libraries/SafeMath.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.5.4; - -// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) - -library SafeMath { - function add(uint x, uint y) internal pure returns (uint z) { - require((z = x + y) >= x, 'ds-math-add-overflow'); - } - - function sub(uint x, uint y) internal pure returns (uint z) { - require((z = x - y) <= x, 'ds-math-sub-underflow'); - } - - function mul(uint x, uint y) internal pure returns (uint z) { - require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); - } -} diff --git a/lib/utils/strings.sol b/lib/utils/strings.sol deleted file mode 100644 index 3db4600a8..000000000 --- a/lib/utils/strings.sol +++ /dev/null @@ -1,717 +0,0 @@ -/* - * @title String & slice utility library for Solidity contracts. - * @author Nick Johnson - * - * @dev Functionality in this library is largely implemented using an - * abstraction called a 'slice'. A slice represents a part of a string - - * anything from the entire string to a single character, or even no - * characters at all (a 0-length slice). Since a slice only has to specify - * an offset and a length, copying and manipulating slices is a lot less - * expensive than copying and manipulating the strings they reference. - * - * To further reduce gas costs, most functions on slice that need to return - * a slice modify the original one instead of allocating a new one; for - * instance, `s.split(".")` will return the text up to the first '.', - * modifying s to only contain the remainder of the string after the '.'. - * In situations where you do not want to modify the original slice, you - * can make a copy first with `.copy()`, for example: - * `s.copy().split(".")`. Try and avoid using this idiom in loops; since - * Solidity has no memory management, it will result in allocating many - * short-lived slices that are later discarded. - * - * Functions that return two slices come in two versions: a non-allocating - * version that takes the second slice as an argument, modifying it in - * place, and an allocating version that allocates and returns the second - * slice; see `nextRune` for example. - * - * Functions that have to copy string data will return strings rather than - * slices; these can be cast back to slices for further processing if - * required. - * - * For convenience, some functions are provided with non-modifying - * variants that create a new slice and return both; for instance, - * `s.splitNew('.')` leaves s unmodified, and returns two values - * corresponding to the left and right parts of the string. - */ - -// pragma solidity ^0.4.14; -pragma solidity ^0.5.4; - -library strings { - struct slice { - uint _len; - uint _ptr; - } - - function memcpy(uint dest, uint src, uint len) private pure { - // Copy word-length chunks while possible - for(; len >= 32; len -= 32) { - assembly { - mstore(dest, mload(src)) - } - dest += 32; - src += 32; - } - - // Copy remaining bytes - uint mask = 256 ** (32 - len) - 1; - assembly { - let srcpart := and(mload(src), not(mask)) - let destpart := and(mload(dest), mask) - mstore(dest, or(destpart, srcpart)) - } - } - - /* - * @dev Returns a slice containing the entire string. - * @param self The string to make a slice from. - * @return A newly allocated slice containing the entire string. - */ - function toSlice(string memory self) internal pure returns (slice memory) { - uint ptr; - assembly { - ptr := add(self, 0x20) - } - return slice(bytes(self).length, ptr); - } - - /* - * @dev Returns the length of a null-terminated bytes32 string. - * @param self The value to find the length of. - * @return The length of the string, from 0 to 32. - */ - function len(bytes32 self) internal pure returns (uint) { - uint ret; - if (self == 0) - return 0; - if (uint256(self) & 0xffffffffffffffffffffffffffffffff == 0) { - ret += 16; - self = bytes32(uint(self) / 0x100000000000000000000000000000000); - } - if (uint256(self) & 0xffffffffffffffff == 0) { - ret += 8; - self = bytes32(uint(self) / 0x10000000000000000); - } - if (uint256(self) & 0xffffffff == 0) { - ret += 4; - self = bytes32(uint(self) / 0x100000000); - } - if (uint256(self) & 0xffff == 0) { - ret += 2; - self = bytes32(uint(self) / 0x10000); - } - if (uint256(self) & 0xff == 0) { - ret += 1; - } - return 32 - ret; - } - - /* - * @dev Returns a slice containing the entire bytes32, interpreted as a - * null-terminated utf-8 string. - * @param self The bytes32 value to convert to a slice. - * @return A new slice containing the value of the input argument up to the - * first null. - */ - function toSliceB32(bytes32 self) internal pure returns (slice memory ret) { - // Allocate space for `self` in memory, copy it there, and point ret at it - assembly { - let ptr := mload(0x40) - mstore(0x40, add(ptr, 0x20)) - mstore(ptr, self) - mstore(add(ret, 0x20), ptr) - } - ret._len = len(self); - } - - /* - * @dev Returns a new slice containing the same data as the current slice. - * @param self The slice to copy. - * @return A new slice containing the same data as `self`. - */ - function copy(slice memory self) internal pure returns (slice memory) { - return slice(self._len, self._ptr); - } - - /* - * @dev Copies a slice to a new string. - * @param self The slice to copy. - * @return A newly allocated string containing the slice's text. - */ - function toString(slice memory self) internal pure returns (string memory) { - string memory ret = new string(self._len); - uint retptr; - assembly { retptr := add(ret, 32) } - - memcpy(retptr, self._ptr, self._len); - return ret; - } - - /* - * @dev Returns the length in runes of the slice. Note that this operation - * takes time proportional to the length of the slice; avoid using it - * in loops, and call `slice.empty()` if you only need to know whether - * the slice is empty or not. - * @param self The slice to operate on. - * @return The length of the slice in runes. - */ - function len(slice memory self) internal pure returns (uint l) { - // Starting at ptr-31 means the LSB will be the byte we care about - uint ptr = self._ptr - 31; - uint end = ptr + self._len; - for (l = 0; ptr < end; l++) { - uint8 b; - assembly { b := and(mload(ptr), 0xFF) } - if (b < 0x80) { - ptr += 1; - } else if(b < 0xE0) { - ptr += 2; - } else if(b < 0xF0) { - ptr += 3; - } else if(b < 0xF8) { - ptr += 4; - } else if(b < 0xFC) { - ptr += 5; - } else { - ptr += 6; - } - } - } - - /* - * @dev Returns true if the slice is empty (has a length of 0). - * @param self The slice to operate on. - * @return True if the slice is empty, False otherwise. - */ - function empty(slice memory self) internal pure returns (bool) { - return self._len == 0; - } - - /* - * @dev Returns a positive number if `other` comes lexicographically after - * `self`, a negative number if it comes before, or zero if the - * contents of the two slices are equal. Comparison is done per-rune, - * on unicode codepoints. - * @param self The first slice to compare. - * @param other The second slice to compare. - * @return The result of the comparison. - */ - function compare(slice memory self, slice memory other) internal pure returns (int) { - uint shortest = self._len; - if (other._len < self._len) - shortest = other._len; - - uint selfptr = self._ptr; - uint otherptr = other._ptr; - for (uint idx = 0; idx < shortest; idx += 32) { - uint a; - uint b; - assembly { - a := mload(selfptr) - b := mload(otherptr) - } - if (a != b) { - // Mask out irrelevant bytes and check again - uint256 mask = uint256(-1); // 0xffff... - if(shortest < 32) { - mask = ~(2 ** (8 * (32 - shortest + idx)) - 1); - } - uint256 diff = (a & mask) - (b & mask); - if (diff != 0) - return int(diff); - } - selfptr += 32; - otherptr += 32; - } - return int(self._len) - int(other._len); - } - - /* - * @dev Returns true if the two slices contain the same text. - * @param self The first slice to compare. - * @param self The second slice to compare. - * @return True if the slices are equal, false otherwise. - */ - function equals(slice memory self, slice memory other) internal pure returns (bool) { - return compare(self, other) == 0; - } - - /* - * @dev Extracts the first rune in the slice into `rune`, advancing the - * slice to point to the next rune and returning `self`. - * @param self The slice to operate on. - * @param rune The slice that will contain the first rune. - * @return `rune`. - */ - function nextRune(slice memory self, slice memory rune) internal pure returns (slice memory) { - rune._ptr = self._ptr; - - if (self._len == 0) { - rune._len = 0; - return rune; - } - - uint l; - uint b; - // Load the first byte of the rune into the LSBs of b - assembly { b := and(mload(sub(mload(add(self, 32)), 31)), 0xFF) } - if (b < 0x80) { - l = 1; - } else if(b < 0xE0) { - l = 2; - } else if(b < 0xF0) { - l = 3; - } else { - l = 4; - } - - // Check for truncated codepoints - if (l > self._len) { - rune._len = self._len; - self._ptr += self._len; - self._len = 0; - return rune; - } - - self._ptr += l; - self._len -= l; - rune._len = l; - return rune; - } - - /* - * @dev Returns the first rune in the slice, advancing the slice to point - * to the next rune. - * @param self The slice to operate on. - * @return A slice containing only the first rune from `self`. - */ - function nextRune(slice memory self) internal pure returns (slice memory ret) { - nextRune(self, ret); - } - - /* - * @dev Returns the number of the first codepoint in the slice. - * @param self The slice to operate on. - * @return The number of the first codepoint in the slice. - */ - function ord(slice memory self) internal pure returns (uint ret) { - if (self._len == 0) { - return 0; - } - - uint word; - uint length; - uint divisor = 2 ** 248; - - // Load the rune into the MSBs of b - assembly { word:= mload(mload(add(self, 32))) } - uint b = word / divisor; - if (b < 0x80) { - ret = b; - length = 1; - } else if(b < 0xE0) { - ret = b & 0x1F; - length = 2; - } else if(b < 0xF0) { - ret = b & 0x0F; - length = 3; - } else { - ret = b & 0x07; - length = 4; - } - - // Check for truncated codepoints - if (length > self._len) { - return 0; - } - - for (uint i = 1; i < length; i++) { - divisor = divisor / 256; - b = (word / divisor) & 0xFF; - if (b & 0xC0 != 0x80) { - // Invalid UTF-8 sequence - return 0; - } - ret = (ret * 64) | (b & 0x3F); - } - - return ret; - } - - /* - * @dev Returns the keccak-256 hash of the slice. - * @param self The slice to hash. - * @return The hash of the slice. - */ - function keccak(slice memory self) internal pure returns (bytes32 ret) { - assembly { - ret := keccak256(mload(add(self, 32)), mload(self)) - } - } - - /* - * @dev Returns true if `self` starts with `needle`. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return True if the slice starts with the provided text, false otherwise. - */ - function startsWith(slice memory self, slice memory needle) internal pure returns (bool) { - if (self._len < needle._len) { - return false; - } - - if (self._ptr == needle._ptr) { - return true; - } - - bool equal; - assembly { - let length := mload(needle) - let selfptr := mload(add(self, 0x20)) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - return equal; - } - - /* - * @dev If `self` starts with `needle`, `needle` is removed from the - * beginning of `self`. Otherwise, `self` is unmodified. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return `self` - */ - function beyond(slice memory self, slice memory needle) internal pure returns (slice memory) { - if (self._len < needle._len) { - return self; - } - - bool equal = true; - if (self._ptr != needle._ptr) { - assembly { - let length := mload(needle) - let selfptr := mload(add(self, 0x20)) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - } - - if (equal) { - self._len -= needle._len; - self._ptr += needle._len; - } - - return self; - } - - /* - * @dev Returns true if the slice ends with `needle`. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return True if the slice starts with the provided text, false otherwise. - */ - function endsWith(slice memory self, slice memory needle) internal pure returns (bool) { - if (self._len < needle._len) { - return false; - } - - uint selfptr = self._ptr + self._len - needle._len; - - if (selfptr == needle._ptr) { - return true; - } - - bool equal; - assembly { - let length := mload(needle) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - - return equal; - } - - /* - * @dev If `self` ends with `needle`, `needle` is removed from the - * end of `self`. Otherwise, `self` is unmodified. - * @param self The slice to operate on. - * @param needle The slice to search for. - * @return `self` - */ - function until(slice memory self, slice memory needle) internal pure returns (slice memory) { - if (self._len < needle._len) { - return self; - } - - uint selfptr = self._ptr + self._len - needle._len; - bool equal = true; - if (selfptr != needle._ptr) { - assembly { - let length := mload(needle) - let needleptr := mload(add(needle, 0x20)) - equal := eq(keccak256(selfptr, length), keccak256(needleptr, length)) - } - } - - if (equal) { - self._len -= needle._len; - } - - return self; - } - - // Returns the memory address of the first byte of the first occurrence of - // `needle` in `self`, or the first byte after `self` if not found. - function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { - uint ptr = selfptr; - uint idx; - - if (needlelen <= selflen) { - if (needlelen <= 32) { - bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); - - bytes32 needledata; - assembly { needledata := and(mload(needleptr), mask) } - - uint end = selfptr + selflen - needlelen; - bytes32 ptrdata; - assembly { ptrdata := and(mload(ptr), mask) } - - while (ptrdata != needledata) { - if (ptr >= end) - return selfptr + selflen; - ptr++; - assembly { ptrdata := and(mload(ptr), mask) } - } - return ptr; - } else { - // For long needles, use hashing - bytes32 hash; - assembly { hash := keccak256(needleptr, needlelen) } - - for (idx = 0; idx <= selflen - needlelen; idx++) { - bytes32 testHash; - assembly { testHash := keccak256(ptr, needlelen) } - if (hash == testHash) - return ptr; - ptr += 1; - } - } - } - return selfptr + selflen; - } - - // Returns the memory address of the first byte after the last occurrence of - // `needle` in `self`, or the address of `self` if not found. - function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) { - uint ptr; - - if (needlelen <= selflen) { - if (needlelen <= 32) { - bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1)); - - bytes32 needledata; - assembly { needledata := and(mload(needleptr), mask) } - - ptr = selfptr + selflen - needlelen; - bytes32 ptrdata; - assembly { ptrdata := and(mload(ptr), mask) } - - while (ptrdata != needledata) { - if (ptr <= selfptr) - return selfptr; - ptr--; - assembly { ptrdata := and(mload(ptr), mask) } - } - return ptr + needlelen; - } else { - // For long needles, use hashing - bytes32 hash; - assembly { hash := keccak256(needleptr, needlelen) } - ptr = selfptr + (selflen - needlelen); - while (ptr >= selfptr) { - bytes32 testHash; - assembly { testHash := keccak256(ptr, needlelen) } - if (hash == testHash) - return ptr + needlelen; - ptr -= 1; - } - } - } - return selfptr; - } - - /* - * @dev Modifies `self` to contain everything from the first occurrence of - * `needle` to the end of the slice. `self` is set to the empty slice - * if `needle` is not found. - * @param self The slice to search and modify. - * @param needle The text to search for. - * @return `self`. - */ - function find(slice memory self, slice memory needle) internal pure returns (slice memory) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); - self._len -= ptr - self._ptr; - self._ptr = ptr; - return self; - } - - /* - * @dev Modifies `self` to contain the part of the string from the start of - * `self` to the end of the first occurrence of `needle`. If `needle` - * is not found, `self` is set to the empty slice. - * @param self The slice to search and modify. - * @param needle The text to search for. - * @return `self`. - */ - function rfind(slice memory self, slice memory needle) internal pure returns (slice memory) { - uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); - self._len = ptr - self._ptr; - return self; - } - - /* - * @dev Splits the slice, setting `self` to everything after the first - * occurrence of `needle`, and `token` to everything before it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and `token` is set to the entirety of `self`. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @param token An output parameter to which the first token is written. - * @return `token`. - */ - function split(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr); - token._ptr = self._ptr; - token._len = ptr - self._ptr; - if (ptr == self._ptr + self._len) { - // Not found - self._len = 0; - } else { - self._len -= token._len + needle._len; - self._ptr = ptr + needle._len; - } - return token; - } - - /* - * @dev Splits the slice, setting `self` to everything after the first - * occurrence of `needle`, and returning everything before it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and the entirety of `self` is returned. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @return The part of `self` up to the first occurrence of `delim`. - */ - function split(slice memory self, slice memory needle) internal pure returns (slice memory token) { - split(self, needle, token); - } - - /* - * @dev Splits the slice, setting `self` to everything before the last - * occurrence of `needle`, and `token` to everything after it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and `token` is set to the entirety of `self`. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @param token An output parameter to which the first token is written. - * @return `token`. - */ - function rsplit(slice memory self, slice memory needle, slice memory token) internal pure returns (slice memory) { - uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr); - token._ptr = ptr; - token._len = self._len - (ptr - self._ptr); - if (ptr == self._ptr) { - // Not found - self._len = 0; - } else { - self._len -= token._len + needle._len; - } - return token; - } - - /* - * @dev Splits the slice, setting `self` to everything before the last - * occurrence of `needle`, and returning everything after it. If - * `needle` does not occur in `self`, `self` is set to the empty slice, - * and the entirety of `self` is returned. - * @param self The slice to split. - * @param needle The text to search for in `self`. - * @return The part of `self` after the last occurrence of `delim`. - */ - function rsplit(slice memory self, slice memory needle) internal pure returns (slice memory token) { - rsplit(self, needle, token); - } - - /* - * @dev Counts the number of nonoverlapping occurrences of `needle` in `self`. - * @param self The slice to search. - * @param needle The text to search for in `self`. - * @return The number of occurrences of `needle` found in `self`. - */ - function count(slice memory self, slice memory needle) internal pure returns (uint cnt) { - uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len; - while (ptr <= self._ptr + self._len) { - cnt++; - ptr = findPtr(self._len - (ptr - self._ptr), ptr, needle._len, needle._ptr) + needle._len; - } - } - - /* - * @dev Returns True if `self` contains `needle`. - * @param self The slice to search. - * @param needle The text to search for in `self`. - * @return True if `needle` is found in `self`, false otherwise. - */ - function contains(slice memory self, slice memory needle) internal pure returns (bool) { - return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr; - } - - /* - * @dev Returns a newly allocated string containing the concatenation of - * `self` and `other`. - * @param self The first slice to concatenate. - * @param other The second slice to concatenate. - * @return The concatenation of the two strings. - */ - function concat(slice memory self, slice memory other) internal pure returns (string memory) { - string memory ret = new string(self._len + other._len); - uint retptr; - assembly { retptr := add(ret, 32) } - memcpy(retptr, self._ptr, self._len); - memcpy(retptr + self._len, other._ptr, other._len); - return ret; - } - - /* - * @dev Joins an array of slices, using `self` as a delimiter, returning a - * newly allocated string. - * @param self The delimiter to use. - * @param parts A list of slices to join. - * @return A newly allocated string containing all the slices in `parts`, - * joined with `self`. - */ - function join(slice memory self, slice[] memory parts) internal pure returns (string memory) { - if (parts.length == 0) - return ""; - - uint length = self._len * (parts.length - 1); - for(uint i = 0; i < parts.length; i++) - length += parts[i]._len; - - string memory ret = new string(length); - uint retptr; - assembly { retptr := add(ret, 32) } - - for(uint i = 0; i < parts.length; i++) { - memcpy(retptr, parts[i]._ptr, parts[i]._len); - retptr += parts[i]._len; - if (i < parts.length - 1) { - memcpy(retptr, self._ptr, self._len); - retptr += self._len; - } - } - - return ret; - } -} \ No newline at end of file diff --git a/lib_0.5/ILido.sol b/lib_0.5/ILido.sol new file mode 100644 index 000000000..480d51bf1 --- /dev/null +++ b/lib_0.5/ILido.sol @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2020 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.5.4; + + +/** +* @title Liquid staking pool implementation +* +* Lido is an Ethereum 2.0 liquid staking protocol solving the problem of frozen staked Ethers +* until transfers become available in Ethereum 2.0. +* Whitepaper: https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf +* +* NOTE: the code below assumes moderate amount of node operators, e.g. up to 50. +* +* Since balances of all token holders change when the amount of total pooled Ether +* changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` +* events upon explicit transfer between holders. In contrast, when Lido oracle reports +* rewards, no Transfer events are generated: doing so would require emitting an event +* for each token holder and thus running an unbounded loop. +*/ +interface ILido { + function submit(address referral) external; + + function balanceOf(address user) external view returns (uint256); + + function approve(address user, uint256 amount) external returns (bool success); +} \ No newline at end of file diff --git a/lib/maker/DS/DSThing.sol b/lib_0.5/balancer/BColor.sol similarity index 70% rename from lib/maker/DS/DSThing.sol rename to lib_0.5/balancer/BColor.sol index d8b3a14e6..02010ae8d 100644 --- a/lib/maker/DS/DSThing.sol +++ b/lib_0.5/balancer/BColor.sol @@ -1,7 +1,3 @@ -// thing.sol - `auth` with handy mixins. your things should be DSThings - -// Copyright (C) 2017 DappHub, LLC - // 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 @@ -15,12 +11,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pragma solidity ^0.5.4; +pragma solidity 0.5.4; -import "./DSAuth.sol"; -import "./DSNote.sol"; -import "./DSMath.sol"; +contract BColor { + function getColor() + external view + returns (bytes32); +} -contract DSThing is DSAuth, DSNote, DSMath { - +contract BBronze is BColor { + function getColor() + external view + returns (bytes32) { + return bytes32("BRONZE"); + } } diff --git a/lib_0.5/balancer/BConst.sol b/lib_0.5/balancer/BConst.sol new file mode 100644 index 000000000..d8be4561b --- /dev/null +++ b/lib_0.5/balancer/BConst.sol @@ -0,0 +1,41 @@ +// 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 . + +pragma solidity 0.5.4; + +import "./BColor.sol"; + +contract BConst is BBronze { + uint public constant BONE = 10**18; + + uint public constant MIN_BOUND_TOKENS = 2; + uint public constant MAX_BOUND_TOKENS = 8; + + uint public constant MIN_FEE = BONE / 10**6; + uint public constant MAX_FEE = BONE / 10; + uint public constant EXIT_FEE = 0; + + uint public constant MIN_WEIGHT = BONE; + uint public constant MAX_WEIGHT = BONE * 50; + uint public constant MAX_TOTAL_WEIGHT = BONE * 50; + uint public constant MIN_BALANCE = BONE / 10**12; + + uint public constant INIT_POOL_SUPPLY = BONE * 100; + + uint public constant MIN_BPOW_BASE = 1 wei; + uint public constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; + uint public constant BPOW_PRECISION = BONE / 10**10; + + uint public constant MAX_IN_RATIO = BONE / 2; + uint public constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; +} diff --git a/lib_0.5/balancer/BFactory.sol b/lib_0.5/balancer/BFactory.sol new file mode 100644 index 000000000..78aedc10a --- /dev/null +++ b/lib_0.5/balancer/BFactory.sol @@ -0,0 +1,79 @@ +// 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 disstributed 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 . + +pragma solidity 0.5.4; + +// Builds new BPools, logging their addresses and providing `isBPool(address) -> (bool)` + +import "./BPool.sol"; + +contract BFactory is BBronze { + event LOG_NEW_POOL( + address indexed caller, + address indexed pool + ); + + event LOG_BLABS( + address indexed caller, + address indexed blabs + ); + + mapping(address=>bool) private _isBPool; + + function isBPool(address b) + external view returns (bool) + { + return _isBPool[b]; + } + + function newBPool() + external + returns (BPool) + { + BPool bpool = new BPool(); + _isBPool[address(bpool)] = true; + emit LOG_NEW_POOL(msg.sender, address(bpool)); + bpool.setController(msg.sender); + return bpool; + } + + address private _blabs; + + constructor() public { + _blabs = msg.sender; + } + + function getBLabs() + external view + returns (address) + { + return _blabs; + } + + function setBLabs(address b) + external + { + require(msg.sender == _blabs, "ERR_NOT_BLABS"); + emit LOG_BLABS(msg.sender, b); + _blabs = b; + } + + function collect(BPool pool) + external + { + require(msg.sender == _blabs, "ERR_NOT_BLABS"); + uint collected = IERC20(pool).balanceOf(address(this)); + bool xfer = pool.transfer(_blabs, collected); + require(xfer, "ERR_ERC20_FAILED"); + } +} diff --git a/lib_0.5/balancer/BMath.sol b/lib_0.5/balancer/BMath.sol new file mode 100644 index 000000000..41d1f48d6 --- /dev/null +++ b/lib_0.5/balancer/BMath.sol @@ -0,0 +1,271 @@ +// 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 . + +pragma solidity 0.5.4; + +import "./BNum.sol"; + +contract BMath is BBronze, BConst, BNum { + /********************************************************************************************** + // calcSpotPrice // + // sP = spotPrice // + // bI = tokenBalanceIn ( bI / wI ) 1 // + // bO = tokenBalanceOut sP = ----------- * ---------- // + // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcSpotPrice( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint swapFee + ) + public pure + returns (uint spotPrice) + { + uint numer = bdiv(tokenBalanceIn, tokenWeightIn); + uint denom = bdiv(tokenBalanceOut, tokenWeightOut); + uint ratio = bdiv(numer, denom); + uint scale = bdiv(BONE, bsub(BONE, swapFee)); + return (spotPrice = bmul(ratio, scale)); + } + + /********************************************************************************************** + // calcOutGivenIn // + // aO = tokenAmountOut // + // bO = tokenBalanceOut // + // bI = tokenBalanceIn / / bI \ (wI / wO) \ // + // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // + // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // + // wO = tokenWeightOut // + // sF = swapFee // + **********************************************************************************************/ + function calcOutGivenIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountIn, + uint swapFee + ) + public pure + returns (uint tokenAmountOut) + { + uint weightRatio = bdiv(tokenWeightIn, tokenWeightOut); + uint adjustedIn = bsub(BONE, swapFee); + adjustedIn = bmul(tokenAmountIn, adjustedIn); + uint y = bdiv(tokenBalanceIn, badd(tokenBalanceIn, adjustedIn)); + uint foo = bpow(y, weightRatio); + uint bar = bsub(BONE, foo); + tokenAmountOut = bmul(tokenBalanceOut, bar); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcInGivenOut // + // aI = tokenAmountIn // + // bO = tokenBalanceOut / / bO \ (wO / wI) \ // + // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // + // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // + // wI = tokenWeightIn -------------------------------------------- // + // wO = tokenWeightOut ( 1 - sF ) // + // sF = swapFee // + **********************************************************************************************/ + function calcInGivenOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint tokenBalanceOut, + uint tokenWeightOut, + uint tokenAmountOut, + uint swapFee + ) + public pure + returns (uint tokenAmountIn) + { + uint weightRatio = bdiv(tokenWeightOut, tokenWeightIn); + uint diff = bsub(tokenBalanceOut, tokenAmountOut); + uint y = bdiv(tokenBalanceOut, diff); + uint foo = bpow(y, weightRatio); + foo = bsub(foo, BONE); + tokenAmountIn = bsub(BONE, swapFee); + tokenAmountIn = bdiv(bmul(tokenBalanceIn, foo), tokenAmountIn); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcPoolOutGivenSingleIn // + // pAo = poolAmountOut / \ // + // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // + // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // + // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // + // tBi = tokenBalanceIn \\ ------------------------------------- / / // + // pS = poolSupply \\ tBi / / // + // sF = swapFee \ / // + **********************************************************************************************/ + function calcPoolOutGivenSingleIn( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint tokenAmountIn, + uint swapFee + ) + public pure + returns (uint poolAmountOut) + { + // Charge the trading fee for the proportion of tokenAi + /// which is implicitly traded to the other pool tokens. + // That proportion is (1- weightTokenIn) + // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); + uint normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + uint tokenAmountInAfterFee = bmul(tokenAmountIn, bsub(BONE, zaz)); + + uint newTokenBalanceIn = badd(tokenBalanceIn, tokenAmountInAfterFee); + uint tokenInRatio = bdiv(newTokenBalanceIn, tokenBalanceIn); + + // uint newPoolSupply = (ratioTi ^ weightTi) * poolSupply; + uint poolRatio = bpow(tokenInRatio, normalizedWeight); + uint newPoolSupply = bmul(poolRatio, poolSupply); + poolAmountOut = bsub(newPoolSupply, poolSupply); + return poolAmountOut; + } + + /********************************************************************************************** + // calcSingleInGivenPoolOut // + // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // + // pS = poolSupply || --------- | ^ | --------- || * bI - bI // + // pAo = poolAmountOut \\ pS / \(wI / tW)// // + // bI = balanceIn tAi = -------------------------------------------- // + // wI = weightIn / wI \ // + // tW = totalWeight | 1 - ---- | * sF // + // sF = swapFee \ tW / // + **********************************************************************************************/ + function calcSingleInGivenPoolOut( + uint tokenBalanceIn, + uint tokenWeightIn, + uint poolSupply, + uint totalWeight, + uint poolAmountOut, + uint swapFee + ) + public pure + returns (uint tokenAmountIn) + { + uint normalizedWeight = bdiv(tokenWeightIn, totalWeight); + uint newPoolSupply = badd(poolSupply, poolAmountOut); + uint poolRatio = bdiv(newPoolSupply, poolSupply); + + //uint newBalTi = poolRatio^(1/weightTi) * balTi; + uint boo = bdiv(BONE, normalizedWeight); + uint tokenInRatio = bpow(poolRatio, boo); + uint newTokenBalanceIn = bmul(tokenInRatio, tokenBalanceIn); + uint tokenAmountInAfterFee = bsub(newTokenBalanceIn, tokenBalanceIn); + // Do reverse order of fees charged in joinswap_ExternAmountIn, this way + // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` + //uint tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; + uint zar = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountIn = bdiv(tokenAmountInAfterFee, bsub(BONE, zar)); + return tokenAmountIn; + } + + /********************************************************************************************** + // calcSingleOutGivenPoolIn // + // tAo = tokenAmountOut / / \\ // + // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // + // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // + // ps = poolSupply \ \\ pS / \(wO / tW)/ // // + // wI = tokenWeightIn tAo = \ \ // // + // tW = totalWeight / / wO \ \ // + // sF = swapFee * | 1 - | 1 - ---- | * sF | // + // eF = exitFee \ \ tW / / // + **********************************************************************************************/ + function calcSingleOutGivenPoolIn( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint poolAmountIn, + uint swapFee + ) + public pure + returns (uint tokenAmountOut) + { + uint normalizedWeight = bdiv(tokenWeightOut, totalWeight); + // charge exit fee on the pool token side + // pAiAfterExitFee = pAi*(1-exitFee) + uint poolAmountInAfterExitFee = bmul(poolAmountIn, bsub(BONE, EXIT_FEE)); + uint newPoolSupply = bsub(poolSupply, poolAmountInAfterExitFee); + uint poolRatio = bdiv(newPoolSupply, poolSupply); + + // newBalTo = poolRatio^(1/weightTo) * balTo; + uint tokenOutRatio = bpow(poolRatio, bdiv(BONE, normalizedWeight)); + uint newTokenBalanceOut = bmul(tokenOutRatio, tokenBalanceOut); + + uint tokenAmountOutBeforeSwapFee = bsub(tokenBalanceOut, newTokenBalanceOut); + + // charge swap fee on the output token side + //uint tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) + uint zaz = bmul(bsub(BONE, normalizedWeight), swapFee); + tokenAmountOut = bmul(tokenAmountOutBeforeSwapFee, bsub(BONE, zaz)); + return tokenAmountOut; + } + + /********************************************************************************************** + // calcPoolInGivenSingleOut // + // pAi = poolAmountIn // / tAo \\ / wO \ \ // + // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // + // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // + // ps = poolSupply \\ -----------------------------------/ / // + // wO = tokenWeightOut pAi = \\ bO / / // + // tW = totalWeight ------------------------------------------------------------- // + // sF = swapFee ( 1 - eF ) // + // eF = exitFee // + **********************************************************************************************/ + function calcPoolInGivenSingleOut( + uint tokenBalanceOut, + uint tokenWeightOut, + uint poolSupply, + uint totalWeight, + uint tokenAmountOut, + uint swapFee + ) + public pure + returns (uint poolAmountIn) + { + + // charge swap fee on the output token side + uint normalizedWeight = bdiv(tokenWeightOut, totalWeight); + //uint tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; + uint zoo = bsub(BONE, normalizedWeight); + uint zar = bmul(zoo, swapFee); + uint tokenAmountOutBeforeSwapFee = bdiv(tokenAmountOut, bsub(BONE, zar)); + + uint newTokenBalanceOut = bsub(tokenBalanceOut, tokenAmountOutBeforeSwapFee); + uint tokenOutRatio = bdiv(newTokenBalanceOut, tokenBalanceOut); + + //uint newPoolSupply = (ratioTo ^ weightTo) * poolSupply; + uint poolRatio = bpow(tokenOutRatio, normalizedWeight); + uint newPoolSupply = bmul(poolRatio, poolSupply); + uint poolAmountInAfterExitFee = bsub(poolSupply, newPoolSupply); + + // charge exit fee on the pool token side + // pAi = pAiAfterExitFee/(1-exitFee) + poolAmountIn = bdiv(poolAmountInAfterExitFee, bsub(BONE, EXIT_FEE)); + return poolAmountIn; + } + + +} diff --git a/lib_0.5/balancer/BNum.sol b/lib_0.5/balancer/BNum.sol new file mode 100644 index 000000000..23c40f723 --- /dev/null +++ b/lib_0.5/balancer/BNum.sol @@ -0,0 +1,163 @@ +// 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 . + +pragma solidity 0.5.4; + +import "./BConst.sol"; + +contract BNum is BConst { + + function btoi(uint a) + internal pure + returns (uint) + { + return a / BONE; + } + + function bfloor(uint a) + internal pure + returns (uint) + { + return btoi(a) * BONE; + } + + function badd(uint a, uint b) + internal pure + returns (uint) + { + uint c = a + b; + require(c >= a, "ERR_ADD_OVERFLOW"); + return c; + } + + function bsub(uint a, uint b) + internal pure + returns (uint) + { + (uint c, bool flag) = bsubSign(a, b); + require(!flag, "ERR_SUB_UNDERFLOW"); + return c; + } + + function bsubSign(uint a, uint b) + internal pure + returns (uint, bool) + { + if (a >= b) { + return (a - b, false); + } else { + return (b - a, true); + } + } + + function bmul(uint a, uint b) + internal pure + returns (uint) + { + uint c0 = a * b; + require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); + uint c1 = c0 + (BONE / 2); + require(c1 >= c0, "ERR_MUL_OVERFLOW"); + uint c2 = c1 / BONE; + return c2; + } + + function bdiv(uint a, uint b) + internal pure + returns (uint) + { + require(b != 0, "ERR_DIV_ZERO"); + uint c0 = a * BONE; + require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow + uint c1 = c0 + (b / 2); + require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require + uint c2 = c1 / b; + return c2; + } + + // DSMath.wpow + function bpowi(uint a, uint n) + internal pure + returns (uint) + { + uint z = n % 2 != 0 ? a : BONE; + + for (n /= 2; n != 0; n /= 2) { + a = bmul(a, a); + + if (n % 2 != 0) { + z = bmul(z, a); + } + } + return z; + } + + // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). + // Use `bpowi` for `b^e` and `bpowK` for k iterations + // of approximation of b^0.w + function bpow(uint base, uint exp) + internal pure + returns (uint) + { + require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); + require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); + + uint whole = bfloor(exp); + uint remain = bsub(exp, whole); + + uint wholePow = bpowi(base, btoi(whole)); + + if (remain == 0) { + return wholePow; + } + + uint partialResult = bpowApprox(base, remain, BPOW_PRECISION); + return bmul(wholePow, partialResult); + } + + function bpowApprox(uint base, uint exp, uint precision) + internal pure + returns (uint) + { + // term 0: + uint a = exp; + (uint x, bool xneg) = bsubSign(base, BONE); + uint term = BONE; + uint sum = term; + bool negative = false; + + + // term(k) = numer / denom + // = (product(a - i - 1, i=1-->k) * x^k) / (k!) + // each iteration, multiply previous term by (a-(k-1)) * x / k + // continue until term is less than precision + for (uint i = 1; term >= precision; i++) { + uint bigK = i * BONE; + (uint c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); + term = bmul(term, bmul(c, x)); + term = bdiv(term, bigK); + if (term == 0) break; + + if (xneg) negative = !negative; + if (cneg) negative = !negative; + if (negative) { + sum = bsub(sum, term); + } else { + sum = badd(sum, term); + } + } + + return sum; + } + +} diff --git a/lib_0.5/balancer/BPool.sol b/lib_0.5/balancer/BPool.sol new file mode 100644 index 000000000..414b4b2ca --- /dev/null +++ b/lib_0.5/balancer/BPool.sol @@ -0,0 +1,739 @@ +// 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 . + +pragma solidity 0.5.4; + +import "./BToken.sol"; +import "./BMath.sol"; + +contract BPool is BBronze, BToken, BMath { + + struct Record { + bool bound; // is token bound to pool + uint index; // private + uint denorm; // denormalized weight + uint balance; + } + + event LOG_SWAP( + address indexed caller, + address indexed tokenIn, + address indexed tokenOut, + uint256 tokenAmountIn, + uint256 tokenAmountOut + ); + + event LOG_JOIN( + address indexed caller, + address indexed tokenIn, + uint256 tokenAmountIn + ); + + event LOG_EXIT( + address indexed caller, + address indexed tokenOut, + uint256 tokenAmountOut + ); + + event LOG_CALL( + bytes4 indexed sig, + address indexed caller, + bytes data + ) anonymous; + + modifier _logs_() { + emit LOG_CALL(msg.sig, msg.sender, msg.data); + _; + } + + modifier _lock_() { + require(!_mutex, "ERR_REENTRY"); + _mutex = true; + _; + _mutex = false; + } + + modifier _viewlock_() { + require(!_mutex, "ERR_REENTRY"); + _; + } + + bool private _mutex; + + address private _factory; // BFactory address to push token exitFee to + address private _controller; // has CONTROL role + bool private _publicSwap; // true if PUBLIC can call SWAP functions + + // `setSwapFee` and `finalize` require CONTROL + // `finalize` sets `PUBLIC can SWAP`, `PUBLIC can JOIN` + uint private _swapFee; + bool private _finalized; + + address[] private _tokens; + mapping(address=>Record) private _records; + uint private _totalWeight; + + constructor() public { + _controller = msg.sender; + _factory = msg.sender; + _swapFee = MIN_FEE; + _publicSwap = false; + _finalized = false; + } + + function isPublicSwap() + external view + returns (bool) + { + return _publicSwap; + } + + function isFinalized() + external view + returns (bool) + { + return _finalized; + } + + function isBound(address t) + external view + returns (bool) + { + return _records[t].bound; + } + + function getNumTokens() + external view + returns (uint) + { + return _tokens.length; + } + + function getCurrentTokens() + external view _viewlock_ + returns (address[] memory tokens) + { + return _tokens; + } + + function getFinalTokens() + external view + _viewlock_ + returns (address[] memory tokens) + { + require(_finalized, "ERR_NOT_FINALIZED"); + return _tokens; + } + + function getDenormalizedWeight(address token) + external view + _viewlock_ + returns (uint) + { + + require(_records[token].bound, "ERR_NOT_BOUND"); + return _records[token].denorm; + } + + function getTotalDenormalizedWeight() + external view + _viewlock_ + returns (uint) + { + return _totalWeight; + } + + function getNormalizedWeight(address token) + external view + _viewlock_ + returns (uint) + { + + require(_records[token].bound, "ERR_NOT_BOUND"); + uint denorm = _records[token].denorm; + return bdiv(denorm, _totalWeight); + } + + function getBalance(address token) + external view + _viewlock_ + returns (uint) + { + + require(_records[token].bound, "ERR_NOT_BOUND"); + return _records[token].balance; + } + + function getSwapFee() + external view + _viewlock_ + returns (uint) + { + return _swapFee; + } + + function getController() + external view + _viewlock_ + returns (address) + { + return _controller; + } + + function setSwapFee(uint swapFee) + external + _logs_ + _lock_ + { + require(!_finalized, "ERR_IS_FINALIZED"); + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + require(swapFee >= MIN_FEE, "ERR_MIN_FEE"); + require(swapFee <= MAX_FEE, "ERR_MAX_FEE"); + _swapFee = swapFee; + } + + function setController(address manager) + external + _logs_ + _lock_ + { + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + _controller = manager; + } + + function setPublicSwap(bool public_) + external + _logs_ + _lock_ + { + require(!_finalized, "ERR_IS_FINALIZED"); + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + _publicSwap = public_; + } + + function finalize() + external + _logs_ + _lock_ + { + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + require(!_finalized, "ERR_IS_FINALIZED"); + require(_tokens.length >= MIN_BOUND_TOKENS, "ERR_MIN_TOKENS"); + + _finalized = true; + _publicSwap = true; + + _mintPoolShare(INIT_POOL_SUPPLY); + _pushPoolShare(msg.sender, INIT_POOL_SUPPLY); + } + + + function bind(address token, uint balance, uint denorm) + external + _logs_ + // _lock_ Bind does not lock because it jumps to `rebind`, which does + { + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + require(!_records[token].bound, "ERR_IS_BOUND"); + require(!_finalized, "ERR_IS_FINALIZED"); + + require(_tokens.length < MAX_BOUND_TOKENS, "ERR_MAX_TOKENS"); + + _records[token] = Record({ + bound: true, + index: _tokens.length, + denorm: 0, // balance and denorm will be validated + balance: 0 // and set by `rebind` + }); + _tokens.push(token); + rebind(token, balance, denorm); + } + + function rebind(address token, uint balance, uint denorm) + public + _logs_ + _lock_ + { + + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + require(_records[token].bound, "ERR_NOT_BOUND"); + require(!_finalized, "ERR_IS_FINALIZED"); + + require(denorm >= MIN_WEIGHT, "ERR_MIN_WEIGHT"); + require(denorm <= MAX_WEIGHT, "ERR_MAX_WEIGHT"); + require(balance >= MIN_BALANCE, "ERR_MIN_BALANCE"); + + // Adjust the denorm and totalWeight + uint oldWeight = _records[token].denorm; + if (denorm > oldWeight) { + _totalWeight = badd(_totalWeight, bsub(denorm, oldWeight)); + require(_totalWeight <= MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); + } else if (denorm < oldWeight) { + _totalWeight = bsub(_totalWeight, bsub(oldWeight, denorm)); + } + _records[token].denorm = denorm; + + // Adjust the balance record and actual token balance + uint oldBalance = _records[token].balance; + _records[token].balance = balance; + if (balance > oldBalance) { + _pullUnderlying(token, msg.sender, bsub(balance, oldBalance)); + } else if (balance < oldBalance) { + // In this case liquidity is being withdrawn, so charge EXIT_FEE + uint tokenBalanceWithdrawn = bsub(oldBalance, balance); + uint tokenExitFee = bmul(tokenBalanceWithdrawn, EXIT_FEE); + _pushUnderlying(token, msg.sender, bsub(tokenBalanceWithdrawn, tokenExitFee)); + _pushUnderlying(token, _factory, tokenExitFee); + } + } + + function unbind(address token) + external + _logs_ + _lock_ + { + + require(msg.sender == _controller, "ERR_NOT_CONTROLLER"); + require(_records[token].bound, "ERR_NOT_BOUND"); + require(!_finalized, "ERR_IS_FINALIZED"); + + uint tokenBalance = _records[token].balance; + uint tokenExitFee = bmul(tokenBalance, EXIT_FEE); + + _totalWeight = bsub(_totalWeight, _records[token].denorm); + + // Swap the token-to-unbind with the last token, + // then delete the last token + uint index = _records[token].index; + uint last = _tokens.length - 1; + _tokens[index] = _tokens[last]; + _records[_tokens[index]].index = index; + _tokens.pop(); + _records[token] = Record({ + bound: false, + index: 0, + denorm: 0, + balance: 0 + }); + + _pushUnderlying(token, msg.sender, bsub(tokenBalance, tokenExitFee)); + _pushUnderlying(token, _factory, tokenExitFee); + } + + // Absorb any tokens that have been sent to this contract into the pool + function gulp(address token) + external + _logs_ + _lock_ + { + require(_records[token].bound, "ERR_NOT_BOUND"); + _records[token].balance = IERC20(token).balanceOf(address(this)); + } + + function getSpotPrice(address tokenIn, address tokenOut) + external view + _viewlock_ + returns (uint spotPrice) + { + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + Record storage inRecord = _records[tokenIn]; + Record storage outRecord = _records[tokenOut]; + return calcSpotPrice(inRecord.balance, inRecord.denorm, outRecord.balance, outRecord.denorm, _swapFee); + } + + function getSpotPriceSansFee(address tokenIn, address tokenOut) + external view + _viewlock_ + returns (uint spotPrice) + { + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + Record storage inRecord = _records[tokenIn]; + Record storage outRecord = _records[tokenOut]; + return calcSpotPrice(inRecord.balance, inRecord.denorm, outRecord.balance, outRecord.denorm, 0); + } + + function joinPool(uint poolAmountOut, uint[] calldata maxAmountsIn) + external + _logs_ + _lock_ + { + require(_finalized, "ERR_NOT_FINALIZED"); + + uint poolTotal = totalSupply(); + uint ratio = bdiv(poolAmountOut, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + + for (uint i = 0; i < _tokens.length; i++) { + address t = _tokens[i]; + uint bal = _records[t].balance; + uint tokenAmountIn = bmul(ratio, bal); + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN"); + _records[t].balance = badd(_records[t].balance, tokenAmountIn); + emit LOG_JOIN(msg.sender, t, tokenAmountIn); + _pullUnderlying(t, msg.sender, tokenAmountIn); + } + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + } + + function exitPool(uint poolAmountIn, uint[] calldata minAmountsOut) + external + _logs_ + _lock_ + { + require(_finalized, "ERR_NOT_FINALIZED"); + + uint poolTotal = totalSupply(); + uint exitFee = bmul(poolAmountIn, EXIT_FEE); + uint pAiAfterExitFee = bsub(poolAmountIn, exitFee); + uint ratio = bdiv(pAiAfterExitFee, poolTotal); + require(ratio != 0, "ERR_MATH_APPROX"); + + _pullPoolShare(msg.sender, poolAmountIn); + _pushPoolShare(_factory, exitFee); + _burnPoolShare(pAiAfterExitFee); + + for (uint i = 0; i < _tokens.length; i++) { + address t = _tokens[i]; + uint bal = _records[t].balance; + uint tokenAmountOut = bmul(ratio, bal); + require(tokenAmountOut != 0, "ERR_MATH_APPROX"); + require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT"); + _records[t].balance = bsub(_records[t].balance, tokenAmountOut); + emit LOG_EXIT(msg.sender, t, tokenAmountOut); + _pushUnderlying(t, msg.sender, tokenAmountOut); + } + + } + + + function swapExactAmountIn( + address tokenIn, + uint tokenAmountIn, + address tokenOut, + uint minAmountOut, + uint maxPrice + ) + external + _logs_ + _lock_ + returns (uint tokenAmountOut, uint spotPriceAfter) + { + + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + require(_publicSwap, "ERR_SWAP_NOT_PUBLIC"); + + Record storage inRecord = _records[address(tokenIn)]; + Record storage outRecord = _records[address(tokenOut)]; + + require(tokenAmountIn <= bmul(inRecord.balance, MAX_IN_RATIO), "ERR_MAX_IN_RATIO"); + + uint spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + tokenAmountOut = calcOutGivenIn( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountIn, + _swapFee + ); + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + inRecord.balance = badd(inRecord.balance, tokenAmountIn); + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + + spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require(spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), "ERR_MATH_APPROX"); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + return (tokenAmountOut, spotPriceAfter); + } + + function swapExactAmountOut( + address tokenIn, + uint maxAmountIn, + address tokenOut, + uint tokenAmountOut, + uint maxPrice + ) + external + _logs_ + _lock_ + returns (uint tokenAmountIn, uint spotPriceAfter) + { + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + require(_publicSwap, "ERR_SWAP_NOT_PUBLIC"); + + Record storage inRecord = _records[address(tokenIn)]; + Record storage outRecord = _records[address(tokenOut)]; + + require(tokenAmountOut <= bmul(outRecord.balance, MAX_OUT_RATIO), "ERR_MAX_OUT_RATIO"); + + uint spotPriceBefore = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceBefore <= maxPrice, "ERR_BAD_LIMIT_PRICE"); + + tokenAmountIn = calcInGivenOut( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + tokenAmountOut, + _swapFee + ); + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + inRecord.balance = badd(inRecord.balance, tokenAmountIn); + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + + spotPriceAfter = calcSpotPrice( + inRecord.balance, + inRecord.denorm, + outRecord.balance, + outRecord.denorm, + _swapFee + ); + require(spotPriceAfter >= spotPriceBefore, "ERR_MATH_APPROX"); + require(spotPriceAfter <= maxPrice, "ERR_LIMIT_PRICE"); + require(spotPriceBefore <= bdiv(tokenAmountIn, tokenAmountOut), "ERR_MATH_APPROX"); + + emit LOG_SWAP(msg.sender, tokenIn, tokenOut, tokenAmountIn, tokenAmountOut); + + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + return (tokenAmountIn, spotPriceAfter); + } + + + function joinswapExternAmountIn(address tokenIn, uint tokenAmountIn, uint minPoolAmountOut) + external + _logs_ + _lock_ + returns (uint poolAmountOut) + + { + require(_finalized, "ERR_NOT_FINALIZED"); + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + require(tokenAmountIn <= bmul(_records[tokenIn].balance, MAX_IN_RATIO), "ERR_MAX_IN_RATIO"); + + Record storage inRecord = _records[tokenIn]; + + poolAmountOut = calcPoolOutGivenSingleIn( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountIn, + _swapFee + ); + + require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT"); + + inRecord.balance = badd(inRecord.balance, tokenAmountIn); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return poolAmountOut; + } + + function joinswapPoolAmountOut(address tokenIn, uint poolAmountOut, uint maxAmountIn) + external + _logs_ + _lock_ + returns (uint tokenAmountIn) + { + require(_finalized, "ERR_NOT_FINALIZED"); + require(_records[tokenIn].bound, "ERR_NOT_BOUND"); + + Record storage inRecord = _records[tokenIn]; + + tokenAmountIn = calcSingleInGivenPoolOut( + inRecord.balance, + inRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountOut, + _swapFee + ); + + require(tokenAmountIn != 0, "ERR_MATH_APPROX"); + require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN"); + + require(tokenAmountIn <= bmul(_records[tokenIn].balance, MAX_IN_RATIO), "ERR_MAX_IN_RATIO"); + + inRecord.balance = badd(inRecord.balance, tokenAmountIn); + + emit LOG_JOIN(msg.sender, tokenIn, tokenAmountIn); + + _mintPoolShare(poolAmountOut); + _pushPoolShare(msg.sender, poolAmountOut); + _pullUnderlying(tokenIn, msg.sender, tokenAmountIn); + + return tokenAmountIn; + } + + function exitswapPoolAmountIn(address tokenOut, uint poolAmountIn, uint minAmountOut) + external + _logs_ + _lock_ + returns (uint tokenAmountOut) + { + require(_finalized, "ERR_NOT_FINALIZED"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + + Record storage outRecord = _records[tokenOut]; + + tokenAmountOut = calcSingleOutGivenPoolIn( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + poolAmountIn, + _swapFee + ); + + require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT"); + + require(tokenAmountOut <= bmul(_records[tokenOut].balance, MAX_OUT_RATIO), "ERR_MAX_OUT_RATIO"); + + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + + uint exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_factory, exitFee); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + return tokenAmountOut; + } + + function exitswapExternAmountOut(address tokenOut, uint tokenAmountOut, uint maxPoolAmountIn) + external + _logs_ + _lock_ + returns (uint poolAmountIn) + { + require(_finalized, "ERR_NOT_FINALIZED"); + require(_records[tokenOut].bound, "ERR_NOT_BOUND"); + require(tokenAmountOut <= bmul(_records[tokenOut].balance, MAX_OUT_RATIO), "ERR_MAX_OUT_RATIO"); + + Record storage outRecord = _records[tokenOut]; + + poolAmountIn = calcPoolInGivenSingleOut( + outRecord.balance, + outRecord.denorm, + _totalSupply, + _totalWeight, + tokenAmountOut, + _swapFee + ); + + require(poolAmountIn != 0, "ERR_MATH_APPROX"); + require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN"); + + outRecord.balance = bsub(outRecord.balance, tokenAmountOut); + + uint exitFee = bmul(poolAmountIn, EXIT_FEE); + + emit LOG_EXIT(msg.sender, tokenOut, tokenAmountOut); + + _pullPoolShare(msg.sender, poolAmountIn); + _burnPoolShare(bsub(poolAmountIn, exitFee)); + _pushPoolShare(_factory, exitFee); + _pushUnderlying(tokenOut, msg.sender, tokenAmountOut); + + return poolAmountIn; + } + + + // == + // 'Underlying' token-manipulation functions make external calls but are NOT locked + // You must `_lock_` or otherwise ensure reentry-safety + + function _pullUnderlying(address erc20, address from, uint amount) + internal + { + bool xfer = IERC20(erc20).transferFrom(from, address(this), amount); + require(xfer, "ERR_ERC20_FALSE"); + } + + function _pushUnderlying(address erc20, address to, uint amount) + internal + { + bool xfer = IERC20(erc20).transfer(to, amount); + require(xfer, "ERR_ERC20_FALSE"); + } + + function _pullPoolShare(address from, uint amount) + internal + { + _pull(from, amount); + } + + function _pushPoolShare(address to, uint amount) + internal + { + _push(to, amount); + } + + function _mintPoolShare(uint amount) + internal + { + _mint(amount); + } + + function _burnPoolShare(uint amount) + internal + { + _burn(amount); + } + +} diff --git a/lib_0.5/balancer/BToken.sol b/lib_0.5/balancer/BToken.sol new file mode 100644 index 000000000..2a96c0418 --- /dev/null +++ b/lib_0.5/balancer/BToken.sol @@ -0,0 +1,140 @@ +// 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 . + +pragma solidity 0.5.4; + +import "./BNum.sol"; + +// Highly opinionated token implementation + +interface IERC20 { + event Approval(address indexed src, address indexed dst, uint amt); + event Transfer(address indexed src, address indexed dst, uint amt); + + function totalSupply() external view returns (uint); + function balanceOf(address whom) external view returns (uint); + function allowance(address src, address dst) external view returns (uint); + + function approve(address dst, uint amt) external returns (bool); + function transfer(address dst, uint amt) external returns (bool); + function transferFrom( + address src, address dst, uint amt + ) external returns (bool); +} + +contract BTokenBase is BNum { + + mapping(address => uint) internal _balance; + mapping(address => mapping(address=>uint)) internal _allowance; + uint internal _totalSupply; + + event Approval(address indexed src, address indexed dst, uint amt); + event Transfer(address indexed src, address indexed dst, uint amt); + + function _mint(uint amt) internal { + _balance[address(this)] = badd(_balance[address(this)], amt); + _totalSupply = badd(_totalSupply, amt); + emit Transfer(address(0), address(this), amt); + } + + function _burn(uint amt) internal { + require(_balance[address(this)] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[address(this)] = bsub(_balance[address(this)], amt); + _totalSupply = bsub(_totalSupply, amt); + emit Transfer(address(this), address(0), amt); + } + + function _move(address src, address dst, uint amt) internal { + require(_balance[src] >= amt, "ERR_INSUFFICIENT_BAL"); + _balance[src] = bsub(_balance[src], amt); + _balance[dst] = badd(_balance[dst], amt); + emit Transfer(src, dst, amt); + } + + function _push(address to, uint amt) internal { + _move(address(this), to, amt); + } + + function _pull(address from, uint amt) internal { + _move(from, address(this), amt); + } +} + +contract BToken is BTokenBase, IERC20 { + + string private _name = "Balancer Pool Token"; + string private _symbol = "BPT"; + uint8 private _decimals = 18; + + function name() public view returns (string memory) { + return _name; + } + + function symbol() public view returns (string memory) { + return _symbol; + } + + function decimals() public view returns(uint8) { + return _decimals; + } + + function allowance(address src, address dst) external view returns (uint) { + return _allowance[src][dst]; + } + + function balanceOf(address whom) external view returns (uint) { + return _balance[whom]; + } + + function totalSupply() public view returns (uint) { + return _totalSupply; + } + + function approve(address dst, uint amt) external returns (bool) { + _allowance[msg.sender][dst] = amt; + emit Approval(msg.sender, dst, amt); + return true; + } + + function increaseApproval(address dst, uint amt) external returns (bool) { + _allowance[msg.sender][dst] = badd(_allowance[msg.sender][dst], amt); + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function decreaseApproval(address dst, uint amt) external returns (bool) { + uint oldValue = _allowance[msg.sender][dst]; + if (amt > oldValue) { + _allowance[msg.sender][dst] = 0; + } else { + _allowance[msg.sender][dst] = bsub(oldValue, amt); + } + emit Approval(msg.sender, dst, _allowance[msg.sender][dst]); + return true; + } + + function transfer(address dst, uint amt) external returns (bool) { + _move(msg.sender, dst, amt); + return true; + } + + function transferFrom(address src, address dst, uint amt) external returns (bool) { + require(msg.sender == src || amt <= _allowance[src][msg.sender], "ERR_BTOKEN_BAD_CALLER"); + _move(src, dst, amt); + if (msg.sender != src && _allowance[src][msg.sender] != uint256(-1)) { + _allowance[src][msg.sender] = bsub(_allowance[src][msg.sender], amt); + emit Approval(msg.sender, dst, _allowance[src][msg.sender]); + } + return true; + } +} diff --git a/lib/compound/CErc20.sol b/lib_0.5/compound/CErc20.sol similarity index 100% rename from lib/compound/CErc20.sol rename to lib_0.5/compound/CErc20.sol diff --git a/lib/compound/CEther.sol b/lib_0.5/compound/CEther.sol similarity index 99% rename from lib/compound/CEther.sol rename to lib_0.5/compound/CEther.sol index a5bad57c2..64c8c902f 100644 --- a/lib/compound/CEther.sol +++ b/lib_0.5/compound/CEther.sol @@ -142,7 +142,7 @@ contract CEther is CToken { function doTransferOut(address payable to, uint amount) internal returns (Error) { /* Send the Ether, with minimal gas and revert on failure */ - to.transfer(amount); + to.call.value(amount)(""); return Error.NO_ERROR; } diff --git a/contracts-legacy/v1.6.0/lib/compound/CToken.sol b/lib_0.5/compound/CToken.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/CToken.sol rename to lib_0.5/compound/CToken.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/CarefulMath.sol b/lib_0.5/compound/CarefulMath.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/CarefulMath.sol rename to lib_0.5/compound/CarefulMath.sol diff --git a/lib/compound/Comptroller.sol b/lib_0.5/compound/Comptroller.sol similarity index 100% rename from lib/compound/Comptroller.sol rename to lib_0.5/compound/Comptroller.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/ComptrollerInterface.sol b/lib_0.5/compound/ComptrollerInterface.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/ComptrollerInterface.sol rename to lib_0.5/compound/ComptrollerInterface.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/ComptrollerStorage.sol b/lib_0.5/compound/ComptrollerStorage.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/ComptrollerStorage.sol rename to lib_0.5/compound/ComptrollerStorage.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/EIP20Interface.sol b/lib_0.5/compound/EIP20Interface.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/EIP20Interface.sol rename to lib_0.5/compound/EIP20Interface.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/EIP20NonStandardInterface.sol b/lib_0.5/compound/EIP20NonStandardInterface.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/EIP20NonStandardInterface.sol rename to lib_0.5/compound/EIP20NonStandardInterface.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/ErrorReporter.sol b/lib_0.5/compound/ErrorReporter.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/ErrorReporter.sol rename to lib_0.5/compound/ErrorReporter.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/Exponential.sol b/lib_0.5/compound/Exponential.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/Exponential.sol rename to lib_0.5/compound/Exponential.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/InterestRateModel.sol b/lib_0.5/compound/InterestRateModel.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/InterestRateModel.sol rename to lib_0.5/compound/InterestRateModel.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/Maximillion.sol b/lib_0.5/compound/Maximillion.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/Maximillion.sol rename to lib_0.5/compound/Maximillion.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/PriceOracle.sol b/lib_0.5/compound/PriceOracle.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/PriceOracle.sol rename to lib_0.5/compound/PriceOracle.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/PriceOracleProxy.sol b/lib_0.5/compound/PriceOracleProxy.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/PriceOracleProxy.sol rename to lib_0.5/compound/PriceOracleProxy.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/ReentrancyGuard.sol b/lib_0.5/compound/ReentrancyGuard.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/ReentrancyGuard.sol rename to lib_0.5/compound/ReentrancyGuard.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/SimplePriceOracle.sol b/lib_0.5/compound/SimplePriceOracle.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/SimplePriceOracle.sol rename to lib_0.5/compound/SimplePriceOracle.sol diff --git a/lib/compound/Unitroller.sol b/lib_0.5/compound/Unitroller.sol similarity index 100% rename from lib/compound/Unitroller.sol rename to lib_0.5/compound/Unitroller.sol diff --git a/contracts-legacy/v1.6.0/lib/compound/WhitePaperInterestRateModel.sol b/lib_0.5/compound/WhitePaperInterestRateModel.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/compound/WhitePaperInterestRateModel.sol rename to lib_0.5/compound/WhitePaperInterestRateModel.sol diff --git a/contracts-legacy/v1.6.0/lib/ens/ENS.sol b/lib_0.5/ens/ENS.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/ens/ENS.sol rename to lib_0.5/ens/ENS.sol diff --git a/contracts-legacy/v1.6.0/lib/ens/ENSRegistry.sol b/lib_0.5/ens/ENSRegistry.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/ens/ENSRegistry.sol rename to lib_0.5/ens/ENSRegistry.sol diff --git a/contracts-legacy/v1.6.0/lib/ens/ENSRegistryWithFallback.sol b/lib_0.5/ens/ENSRegistryWithFallback.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/ens/ENSRegistryWithFallback.sol rename to lib_0.5/ens/ENSRegistryWithFallback.sol diff --git a/contracts-legacy/v1.6.0/lib/ens/ReverseRegistrar.sol b/lib_0.5/ens/ReverseRegistrar.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/ens/ReverseRegistrar.sol rename to lib_0.5/ens/ReverseRegistrar.sol diff --git a/lib/maker/DS/DSAuth.sol b/lib_0.5/maker/DS/DSAuth.sol similarity index 100% rename from lib/maker/DS/DSAuth.sol rename to lib_0.5/maker/DS/DSAuth.sol diff --git a/lib/maker/DS/DSMath.sol b/lib_0.5/maker/DS/DSMath.sol similarity index 100% rename from lib/maker/DS/DSMath.sol rename to lib_0.5/maker/DS/DSMath.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSNote.sol b/lib_0.5/maker/DS/DSNote.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/DS/DSNote.sol rename to lib_0.5/maker/DS/DSNote.sol diff --git a/lib/maker/DS/DSStop.sol b/lib_0.5/maker/DS/DSStop.sol similarity index 100% rename from lib/maker/DS/DSStop.sol rename to lib_0.5/maker/DS/DSStop.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSThing.sol b/lib_0.5/maker/DS/DSThing.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/DS/DSThing.sol rename to lib_0.5/maker/DS/DSThing.sol diff --git a/lib/maker/DS/DSToken.sol b/lib_0.5/maker/DS/DSToken.sol similarity index 100% rename from lib/maker/DS/DSToken.sol rename to lib_0.5/maker/DS/DSToken.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/DS/DSValue.sol b/lib_0.5/maker/DS/DSValue.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/DS/DSValue.sol rename to lib_0.5/maker/DS/DSValue.sol diff --git a/lib/maker/DS/IERC20.sol b/lib_0.5/maker/DS/IERC20.sol similarity index 100% rename from lib/maker/DS/IERC20.sol rename to lib_0.5/maker/DS/IERC20.sol diff --git a/lib/maker/DssCdpManager.sol b/lib_0.5/maker/DssCdpManager.sol similarity index 100% rename from lib/maker/DssCdpManager.sol rename to lib_0.5/maker/DssCdpManager.sol diff --git a/lib/maker/MakerInterfaces.sol b/lib_0.5/maker/MakerInterfaces.sol similarity index 99% rename from lib/maker/MakerInterfaces.sol rename to lib_0.5/maker/MakerInterfaces.sol index 61bd01601..70af53005 100644 --- a/lib/maker/MakerInterfaces.sol +++ b/lib_0.5/maker/MakerInterfaces.sol @@ -1,4 +1,4 @@ -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; interface GemLike { function balanceOf(address) external view returns (uint); diff --git a/lib/maker/MockScdMcdMigration.sol b/lib_0.5/maker/MockScdMcdMigration.sol similarity index 100% rename from lib/maker/MockScdMcdMigration.sol rename to lib_0.5/maker/MockScdMcdMigration.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/SaiTub.sol b/lib_0.5/maker/SaiTub.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/SaiTub.sol rename to lib_0.5/maker/SaiTub.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/SaiVox.sol b/lib_0.5/maker/SaiVox.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/SaiVox.sol rename to lib_0.5/maker/SaiVox.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/ScdMcdMigration.sol b/lib_0.5/maker/ScdMcdMigration.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/ScdMcdMigration.sol rename to lib_0.5/maker/ScdMcdMigration.sol diff --git a/lib/maker/WETH9.sol b/lib_0.5/maker/WETH9.sol similarity index 98% rename from lib/maker/WETH9.sol rename to lib_0.5/maker/WETH9.sol index d4235c5b3..f3854074e 100644 --- a/lib/maker/WETH9.sol +++ b/lib_0.5/maker/WETH9.sol @@ -38,7 +38,7 @@ contract WETH9 { function withdraw(uint wad) public { require(balanceOf[msg.sender] >= wad); balanceOf[msg.sender] -= wad; - msg.sender.transfer(wad); + msg.sender.call.value(wad)(""); emit Withdrawal(msg.sender, wad); } diff --git a/contracts-legacy/v1.6.0/lib/maker/dai.sol b/lib_0.5/maker/dai.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/dai.sol rename to lib_0.5/maker/dai.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/join.sol b/lib_0.5/maker/join.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/join.sol rename to lib_0.5/maker/join.sol diff --git a/lib/maker/jug.sol b/lib_0.5/maker/jug.sol similarity index 100% rename from lib/maker/jug.sol rename to lib_0.5/maker/jug.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/lib.sol b/lib_0.5/maker/lib.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/lib.sol rename to lib_0.5/maker/lib.sol diff --git a/lib/maker/pot.sol b/lib_0.5/maker/pot.sol similarity index 100% rename from lib/maker/pot.sol rename to lib_0.5/maker/pot.sol diff --git a/contracts-legacy/v1.6.0/lib/maker/vat.sol b/lib_0.5/maker/vat.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/maker/vat.sol rename to lib_0.5/maker/vat.sol diff --git a/contracts-legacy/v1.6.0/lib/other/CryptoKitties.sol b/lib_0.5/other/CryptoKitties.sol similarity index 100% rename from contracts-legacy/v1.6.0/lib/other/CryptoKitties.sol rename to lib_0.5/other/CryptoKitties.sol diff --git a/lib/other/ERC20.sol b/lib_0.5/other/ERC20.sol similarity index 95% rename from lib/other/ERC20.sol rename to lib_0.5/other/ERC20.sol index 786043a58..8a497dcc4 100644 --- a/lib/other/ERC20.sol +++ b/lib_0.5/other/ERC20.sol @@ -1,4 +1,4 @@ -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; /** * ERC20 contract interface. diff --git a/lib/other/KyberNetwork.sol b/lib_0.5/other/KyberNetwork.sol similarity index 93% rename from lib/other/KyberNetwork.sol rename to lib_0.5/other/KyberNetwork.sol index 1a5312f17..4802692e3 100644 --- a/lib/other/KyberNetwork.sol +++ b/lib_0.5/other/KyberNetwork.sol @@ -1,4 +1,4 @@ -pragma solidity >=0.5.4 <0.7.0; +pragma solidity >=0.5.4 <0.9.0; import "./ERC20.sol"; interface KyberNetwork { diff --git a/contracts-legacy/v1.6.0/lib/uniswap/UniswapExchange.json b/lib_0.5/uniswap/UniswapExchange.json similarity index 100% rename from contracts-legacy/v1.6.0/lib/uniswap/UniswapExchange.json rename to lib_0.5/uniswap/UniswapExchange.json diff --git a/contracts-legacy/v1.6.0/lib/uniswap/UniswapFactory.json b/lib_0.5/uniswap/UniswapFactory.json similarity index 100% rename from contracts-legacy/v1.6.0/lib/uniswap/UniswapFactory.json rename to lib_0.5/uniswap/UniswapFactory.json diff --git a/lib_0.7/aaveV1/IAToken.sol b/lib_0.7/aaveV1/IAToken.sol new file mode 100644 index 000000000..2bcbd9e95 --- /dev/null +++ b/lib_0.7/aaveV1/IAToken.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Aave ERC20 AToken + * + * @dev Implementation of the interest bearing token for the DLP protocol. + * https://etherscan.io/address/0x3a3A65aAb0dd2A17E3F1947bA16138cd37d08c04#code + * @author Aave + */ + abstract contract IAToken is IERC20 { + /** + * @dev emitted after the redeem action + * @param _from the address performing the redeem + * @param _value the amount to be redeemed + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _fromIndex the last index of the user + **/ + event Redeem( + address indexed _from, + uint256 _value, + uint256 _fromBalanceIncrease, + uint256 _fromIndex + ); + + /** + * @dev redeems aToken for the underlying asset + * @param _amount the amount being redeemed + **/ + function redeem(uint256 _amount) external virtual; +} \ No newline at end of file diff --git a/lib_0.7/aaveV1/IAaveV1LendingPool.sol b/lib_0.7/aaveV1/IAaveV1LendingPool.sol new file mode 100644 index 000000000..d0c31a66d --- /dev/null +++ b/lib_0.7/aaveV1/IAaveV1LendingPool.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.7.5; + +/** +* @title LendingPool contract as in https://etherscan.io/address/0x017788dded30fdd859d295b90d4e41a19393f423#code +* @notice Implements the actions of the LendingPool, and exposes accessory methods to fetch the users and reserve data +* @author Aave + **/ +abstract contract IAaveV1LendingPool { + /** + * @dev emitted on deposit + * @param _reserve the address of the reserve + * @param _user the address of the user + * @param _amount the amount to be deposited + * @param _referral the referral number of the action + * @param _timestamp the timestamp of the action + **/ + event Deposit( + address indexed _reserve, + address indexed _user, + uint256 _amount, + uint16 indexed _referral, + uint256 _timestamp + ); + + /** + * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens) + * is minted. + * @param _reserve the address of the reserve + * @param _amount the amount to be deposited + * @param _referralCode integrators are assigned a referral code and can potentially receive rewards. + **/ + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external payable virtual; + + // Forbidden function + function borrow( + address _reserve, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode + ) external virtual; +} \ No newline at end of file diff --git a/lib_0.7/aaveV1/IUSDCToken.sol b/lib_0.7/aaveV1/IUSDCToken.sol new file mode 100644 index 000000000..ee8f116a1 --- /dev/null +++ b/lib_0.7/aaveV1/IUSDCToken.sol @@ -0,0 +1,28 @@ +pragma solidity ^0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +/** + * @title USDC ERC20 Token + * + * @dev Interface for the USDC token + * https://etherscan.io/address/0xb7277a6e95992041568d9391d09d0122023778a2#code + */ + abstract contract IUSDCToken is IERC20 { + /** + * @dev Function to add/update a new minter + * @param minter The address of the minter + * @param minterAllowedAmount The minting amount allowed for the minter + * @return True if the operation was successful. + */ + function configureMinter(address minter, uint256 minterAllowedAmount) external virtual returns (bool); + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. Must be less than or equal + * to the minterAllowance of the caller. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) external virtual returns (bool); +} \ No newline at end of file diff --git a/lib_0.7/paraswap/V4/AdapterStorage.sol b/lib_0.7/paraswap/V4/AdapterStorage.sol new file mode 100644 index 000000000..633db633a --- /dev/null +++ b/lib_0.7/paraswap/V4/AdapterStorage.sol @@ -0,0 +1,23 @@ +pragma solidity 0.7.5; + +import "./ITokenTransferProxy.sol"; + + +contract AdapterStorage { + + mapping (bytes32 => bool) internal adapterInitialized; + mapping (bytes32 => bytes) internal adapterVsData; + ITokenTransferProxy internal _tokenTransferProxy; + + function isInitialized(bytes32 key) public view returns(bool) { + return adapterInitialized[key]; + } + + function getData(bytes32 key) public view returns(bytes memory) { + return adapterVsData[key]; + } + + function getTokenTransferProxy() public view returns (address) { + return address(_tokenTransferProxy); + } +} diff --git a/lib_0.7/paraswap/V4/AugustusSwapper.md b/lib_0.7/paraswap/V4/AugustusSwapper.md new file mode 100644 index 000000000..1429237fd --- /dev/null +++ b/lib_0.7/paraswap/V4/AugustusSwapper.md @@ -0,0 +1,48 @@ +# AugustusSwapper Contract Specification + +## Introduction + +Paraswap aggregates and structures the liquidity from dozens of decentralized exchanges & lending protocols, such as Uniswap, Kyber, Bancor, Aave etc., in order to facilitate token swaps on the Ethereum blockchain. Paraswap provides the best price and optimal execution, which means computing the prices and splitting orders across exchanges. Our users could end up paying 5 to 10% less than if they would do it themselves. + +## Multi-path Swaps +To understand this better lets take an example. Suppose user wants to swap ETH -> DAI using paraswap.io. + 1. User goes to paraswap.io and chooses to swap 1 ETH for DAI + 2. paraswap.io Algo decides the best route to swap 1 ETH to DAI would be following which will result in 100 DAIs, better then any direct or single path swap-: + 1. Path 01- ETH -> BNT + 1. 30% of ETH(0.3) sent to Uniswap and get 20 BNT in return + 2. 50% of ETH(0.5) sent to Bancor and get 35 BNT in return + 3 20% of ETH(0.2) sent to Kyber and get 18 BNT in return + 2. Overall 73 BNTs are returned after path 01 is executed + 3. Path 02- BNT -> DAI + 1. 100% of BNT to 0x and get 100 DAIs +So this is an example of a multipath swap where a swap between 2 tokens can happen by multiple intermediary swaps via multiple exchanges in order to get the best exchange rate. + +## Single-path Swaps(Simple Swap) +In Multi-path swaps a complex data structure are defined to support multiple intermediary tokens for a particular swap, as described above. Single path swaps or simple swaps can also be executed using multi-path infrastructure. But due to the complexity of the data structures and logic involved +in multi-path swaps the gas costs involved are higher. +The higher gas costs make sense when we are executing swap through multi-path. But in case of single path swap this complexity and has overhead can be +avoided. Hence, we introduced a new mechanism to execute swaps which are going through a single path. + +In single path swaps no intermediary tokens are involved. Suppose user wants to swap ETH -> DAI using paraswap.io. +1. User goes to paraswap.io and chooses to swap 1 ETH for DAI +2. paraswap.io Algo decides the best route to swap 1 ETH to DAI would be following which will result in 100 DAIs, which is a single path swap-: + 1. Path 01- ETH -> DAI + 1. 30% of ETH(0.3) sent to Uniswap and get 20 DAI in return + 2. 50% of ETH(0.5) sent to Bancor and get 62 DAI in return + 3. 20% of ETH(0.2) sent to Kyber and get 18 DAI in return + + + +**IMPORTANT:** One important thing to keep in mind for each swap is that entire amount of source token must be consumed during the swap with no leftover after the transaction is complete. Similarly entire amount of destination token received should also be consumed, sent to the beneficiaries, with no leftover after the transaction is complete. This effectively means contract will never hold any user asset once the transaction is complete. So effectively the token balance of the contract must always be 0. +Any important point to keep in mind is that only whitelisted exchanges can be used in a swap. + +## Usage of Gas Tokens +Gas tokens are used in each transaction to reduce the overall gas consumpation for the user. This will result in the reduced gas usage for the user when they paraswap.io. +The gas tokens are held in the wallet owned by paraswap.io and consumed during the transaction by TokenTransferProxy contract. AugustusSwapper contract will calculate how much of gas tokens should be freed in order to optimize the gas consumption for any transaction and will then pass on that value to the TokenTransferProxy contract in order to free the gas tokens. + +## Pausable +The AugustusSwapper contract must be pausable in nature. This feature is required to ensure that the paraswap.io team is able to pause contract in case any severe attack vectors are found in the contract. This will allow them to render contract unusable for future trades and deploy the fixed version of the contract + + +## Fee +AugustusSwapper conract only supports partner fee for now and no other fee. If and partner is registered against the referral id passed along with the swap then fee will be taken. diff --git a/lib_0.7/paraswap/V4/AugustusSwapperMock.sol b/lib_0.7/paraswap/V4/AugustusSwapperMock.sol new file mode 100644 index 000000000..b60a2aec9 --- /dev/null +++ b/lib_0.7/paraswap/V4/AugustusSwapperMock.sol @@ -0,0 +1,678 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "./IWhitelisted.sol"; +import "./lib/IExchange.sol"; +import "./lib/Utils.sol"; +import "./TokenTransferProxy.sol"; +import "./IPartnerRegistry.sol"; +import "./IPartner.sol"; +import "./lib/TokenFetcherAugustus.sol"; +import "./IWETH.sol"; +import "./IUniswapProxy.sol"; +import "./AdapterStorage.sol"; +import "./ITokenTransferProxy.sol"; + + +contract AugustusSwapperMock is AdapterStorage { + using SafeMath for uint256; + + IWhitelisted private _whitelisted; + + IPartnerRegistry private _partnerRegistry; + + address payable private _feeWallet; + + address private _uniswapProxy; + + event Swapped( + address initiator, + address indexed beneficiary, + address indexed srcToken, + address indexed destToken, + uint256 srcAmount, + uint256 receivedAmount, + uint256 expectedAmount, + string referrer + ); + + event AdapterInitialized(address indexed adapter); + + + receive () payable external { + } + + + function initialize( + address whitelist, + address reduxToken, + address partnerRegistry, + address payable feeWallet, + address uniswapProxy + ) + external + { + require(address(_tokenTransferProxy) == address(0), "Contract already initialized!!"); + _partnerRegistry = IPartnerRegistry(partnerRegistry); + TokenTransferProxy lTokenTransferProxy = new TokenTransferProxy(reduxToken); + _tokenTransferProxy = ITokenTransferProxy(lTokenTransferProxy); + _whitelisted = IWhitelisted(whitelist); + _feeWallet = feeWallet; + _uniswapProxy = uniswapProxy; + } + + function initializeAdapter(address adapter, bytes calldata data) external { + + require( + _whitelisted.hasRole(_whitelisted.WHITELISTED_ROLE(), adapter), + "Exchange not whitelisted" + ); + (bool success,) = adapter.delegatecall(abi.encodeWithSelector(IExchange.initialize.selector, data)); + // require(success, "Failed to initialize adapter"); + if (!success) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + emit AdapterInitialized(adapter); + } + + function getUniswapProxy() external view returns(address) { + return _uniswapProxy; + } + + function changeUniswapProxy(address uniswapProxy) external { + _uniswapProxy = uniswapProxy; + } + + function swapOnUniswap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + uint8 referrer + ) + external + payable + { + //DELEGATING CALL TO THE ADAPTER + (bool success, bytes memory result) = _uniswapProxy.delegatecall( + abi.encodeWithSelector( + IUniswapProxy.swapOnUniswap.selector, + amountIn, + amountOutMin, + path + ) + ); + // require(success, "Call to uniswap proxy failed"); + if (!success) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + + } + + function swapOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + uint8 referrer + ) + external + payable + + { + //DELEGATING CALL TO THE ADAPTER + (bool success, bytes memory result) = _uniswapProxy.delegatecall( + abi.encodeWithSelector( + IUniswapProxy.swapOnUniswapFork.selector, + factory, + initCode, + amountIn, + amountOutMin, + path + ) + ); + // require(success, "Call to uniswap proxy failed"); + if (!success) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + + + } + + function transferTokensFromProxy( + address token, + uint256 amount + ) + private + { + if (token != Utils.ethAddress()) { + _tokenTransferProxy.transferFrom( + token, + msg.sender, + address(this), + amount + ); + } + } + + function performSimpleSwap( + address fromToken, + address toToken, + uint256 fromAmount, + uint256 toAmount, + uint256 expectedAmount, + address[] memory callees, + bytes memory exchangeData, + uint256[] memory startIndexes, + uint256[] memory values, + address payable beneficiary, + string memory referrer, + bool useReduxToken + ) + private + returns (uint256 receivedAmount) + { + require(toAmount > 0, "toAmount is too low"); + require( + callees.length + 1 == startIndexes.length, + "Start indexes must be 1 greater then number of callees" + ); + + uint initialGas = gasleft(); + + //If source token is not ETH than transfer required amount of tokens + //from sender to this contract + transferTokensFromProxy(fromToken, fromAmount); + + for (uint256 i = 0; i < callees.length; i++) { + require( + callees[i] != address(_tokenTransferProxy), + "Can not call TokenTransferProxy Contract" + ); + + bool result = externalCall( + callees[i], //destination + values[i], //value to send + startIndexes[i], // start index of call data + startIndexes[i + 1].sub(startIndexes[i]), // length of calldata + exchangeData// total calldata + ); + // require(result, "External call failed"); + if (!result) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + } + + receivedAmount = Utils.tokenBalance( + toToken, + address(this) + ); + + require( + receivedAmount >= toAmount, + "Received amount of tokens are less then expected" + ); + + takeFeeAndTransferTokens( + toToken, + expectedAmount, + receivedAmount, + beneficiary, + referrer + ); + + if (useReduxToken) { + Utils.refundGas(msg.sender, address(_tokenTransferProxy), initialGas); + } + + return receivedAmount; + } + + /** + * @dev This function sends the WETH returned during the exchange to the user. + * @param token: The WETH Address + */ + function withdrawAllWETH(IWETH token) external { + uint256 amount = token.balanceOf(address(this)); + token.withdraw(amount); + } + + /** + * @dev The function which performs the multi path swap. + * @param data Data required to perform swap. + */ + function multiSwap( + Utils.SellData memory data + ) + public + payable + returns (uint256) + { + uint initialGas = gasleft(); + + address fromToken = data.fromToken; + uint256 fromAmount = data.fromAmount; + uint256 toAmount = data.toAmount; + uint256 expectedAmount = data.expectedAmount; + address payable beneficiary = data.beneficiary == address(0) ? msg.sender : data.beneficiary; + string memory referrer = data.referrer; + Utils.Path[] memory path = data.path; + address toToken = path[path.length - 1].to; + bool useReduxToken = data.useReduxToken; + + //Referral can never be empty + require(bytes(referrer).length > 0, "Invalid referrer"); + + require(toAmount > 0, "To amount can not be 0"); + + //if fromToken is not ETH then transfer tokens from user to this contract + if (fromToken != Utils.ethAddress()) { + _tokenTransferProxy.transferFrom( + fromToken, + msg.sender, + address(this), + fromAmount + ); + } + + performSwap( + fromToken, + fromAmount, + path + ); + + + uint256 receivedAmount = Utils.tokenBalance( + toToken, + address(this) + ); + + require( + receivedAmount >= toAmount, + "Received amount of tokens are less then expected" + ); + + + takeFeeAndTransferTokens( + toToken, + expectedAmount, + receivedAmount, + beneficiary, + referrer + ); + + if (useReduxToken) { + Utils.refundGas(msg.sender, address(_tokenTransferProxy), initialGas); + } + + emit Swapped( + msg.sender, + beneficiary, + fromToken, + toToken, + fromAmount, + receivedAmount, + expectedAmount, + referrer + ); + + return receivedAmount; + } + + /** + * @dev The function which performs the mega path swap. + * @param data Data required to perform swap. + */ + function megaSwap( + Utils.MegaSwapSellData memory data + ) + public + payable + returns (uint256) + { + uint initialGas = gasleft(); + + address fromToken = data.fromToken; + uint256 fromAmount = data.fromAmount; + uint256 toAmount = data.toAmount; + uint256 expectedAmount = data.expectedAmount; + address payable beneficiary = data.beneficiary == address(0) ? msg.sender : data.beneficiary; + string memory referrer = data.referrer; + Utils.MegaSwapPath[] memory path = data.path; + address toToken = path[0].path[path[0].path.length - 1].to; + bool useReduxToken = data.useReduxToken; + + //Referral can never be empty + require(bytes(referrer).length > 0, "Invalid referrer"); + + require(toAmount > 0, "To amount can not be 0"); + + //if fromToken is not ETH then transfer tokens from user to this contract + if (fromToken != Utils.ethAddress()) { + _tokenTransferProxy.transferFrom( + fromToken, + msg.sender, + address(this), + fromAmount + ); + } + + for (uint8 i = 0; i < uint8(path.length); i++) { + uint256 _fromAmount = fromAmount.mul(path[i].fromAmountPercent).div(10000); + if (i == path.length - 1) { + _fromAmount = Utils.tokenBalance(address(fromToken), address(this)); + } + performSwap( + fromToken, + _fromAmount, + path[i].path + ); + } + + uint256 receivedAmount = Utils.tokenBalance( + toToken, + address(this) + ); + + require( + receivedAmount >= toAmount, + "Received amount of tokens are less then expected" + ); + + + takeFeeAndTransferTokens( + toToken, + expectedAmount, + receivedAmount, + beneficiary, + referrer + ); + + if (useReduxToken) { + Utils.refundGas(msg.sender, address(_tokenTransferProxy), initialGas); + } + + emit Swapped( + msg.sender, + beneficiary, + fromToken, + toToken, + fromAmount, + receivedAmount, + expectedAmount, + referrer + ); + + return receivedAmount; + } + + //Helper function to transfer final amount to the beneficiaries + function takeFeeAndTransferTokens( + address toToken, + uint256 expectedAmount, + uint256 receivedAmount, + address payable beneficiary, + string memory referrer + + ) + private + { + uint256 remainingAmount = receivedAmount; + + address partnerContract = _partnerRegistry.getPartnerContract(referrer); + + //Take partner fee + ( uint256 fee ) = _takeFee( + toToken, + receivedAmount, + expectedAmount, + partnerContract + ); + remainingAmount = receivedAmount.sub(fee); + + //If there is a positive slippage after taking partner fee then 50% goes to paraswap and 50% to the user + if ((remainingAmount > expectedAmount) && fee == 0) { + uint256 positiveSlippageShare = remainingAmount.sub(expectedAmount).div(2); + remainingAmount = remainingAmount.sub(positiveSlippageShare); + Utils.transferTokens(toToken, _feeWallet, positiveSlippageShare); + } + + Utils.transferTokens(toToken, beneficiary, remainingAmount); + + + } + + /** + * @dev Source take from GNOSIS MultiSigWallet + * @dev https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol + */ + function externalCall( + address destination, + uint256 value, + uint256 dataOffset, + uint dataLength, + bytes memory data + ) + private + returns (bool) + { + bool result = false; + + assembly { + let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) + + let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that + result := call( + sub(gas(), 34710), // 34710 is the value that solidity is currently emitting + // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + + // callNewAccountGas (25000, in case the destination address does not exist and needs creating) + destination, + value, + add(d, dataOffset), + dataLength, // Size of the input (in bytes) - this is what fixes the padding problem + x, + 0 // Output is ignored, therefore the output size is zero + ) + } + return result; + } + + //Helper function to perform swap + function performSwap( + address fromToken, + uint256 fromAmount, + Utils.Path[] memory path + ) + private + returns(uint256) + { + + require(path.length > 0, "Path not provided for swap"); + + //Assuming path will not be too long to reach out of gas exception + for (uint i = 0; i < path.length; i++) { + //_fromToken will be either fromToken or toToken of the previous path + address _fromToken = i > 0 ? path[i - 1].to : fromToken; + address _toToken = path[i].to; + + uint256 _fromAmount = i > 0 ? Utils.tokenBalance(_fromToken, address(this)) : fromAmount; + if (i > 0 && _fromToken == Utils.ethAddress()) { + _fromAmount = _fromAmount.sub(path[i].totalNetworkFee); + } + + for (uint j = 0; j < path[i].routes.length; j++) { + Utils.Route memory route = path[i].routes[j]; + + //Check if exchange is supported + require( + _whitelisted.hasRole(_whitelisted.WHITELISTED_ROLE(), route.exchange), + "Exchange not whitelisted" + ); + + //Calculating tokens to be passed to the relevant exchange + //percentage should be 200 for 2% + uint fromAmountSlice = _fromAmount.mul(route.percent).div(10000); + uint256 value = route.networkFee; + + if (i > 0 && j == path[i].routes.length.sub(1)) { + uint256 remBal = Utils.tokenBalance(address(_fromToken), address(this)); + + fromAmountSlice = remBal; + + if (address(_fromToken) == Utils.ethAddress()) { + //subtract network fee + fromAmountSlice = fromAmountSlice.sub(value); + } + } + + //DELEGATING CALL TO THE ADAPTER + (bool success,) = route.exchange.delegatecall( + abi.encodeWithSelector( + IExchange.swap.selector, + _fromToken, + _toToken, + fromAmountSlice, + 1, + route.targetExchange, + route.payload + ) + ); + + // require(success, "Call to adapter failed"); + if (!success) { + // solhint-disable-next-line no-inline-assembly + assembly { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + } + } + } + } + function _takeFee( + address toToken, + uint256 receivedAmount, + uint256 expectedAmount, + address partnerContract + ) + private + returns(uint256 fee) + { + return 0; + } + + // + // HACK TO ALLOW COMPILATION OF SIMPLESWAP WITH NO OPTIMIZATION + // + + struct SimpleSwapData { + address fromToken; + address toToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address[] callees; + bytes exchangeData; + uint256[] startIndexes; + uint256[] values; + address payable beneficiary; + string referrer; + bool useReduxToken; + } + + function simpleSwapWithStruct( + SimpleSwapData memory ssd + ) + internal + returns (uint256 receivedAmount) + { + ssd.beneficiary = ssd.beneficiary == address(0) ? msg.sender : ssd.beneficiary; + receivedAmount = performSimpleSwap( + ssd.fromToken, + ssd.toToken, + ssd.fromAmount, + ssd.toAmount, + ssd.expectedAmount, + ssd.callees, + ssd.exchangeData, + ssd.startIndexes, + ssd.values, + ssd.beneficiary, + ssd.referrer, + ssd.useReduxToken + ); + + emit Swapped( + msg.sender, + ssd.beneficiary == address(0)?msg.sender:ssd.beneficiary, + ssd.fromToken, + ssd.toToken, + ssd.fromAmount, + receivedAmount, + ssd.expectedAmount, + ssd.referrer + ); + + return receivedAmount; + } + + bytes4 constant internal SIMPLESWAP = bytes4(keccak256( + "simpleSwap(address,address,uint256,uint256,uint256,address[],bytes,uint256[],uint256[],address,string,bool)" + )); + + function decodeSimpleSwapData1() internal pure returns (address fromToken, address toToken, uint fromAmount, uint toAmount, uint expectedAmount) { + ( + fromToken, + toToken, + fromAmount, + toAmount, + expectedAmount + ) = abi.decode(msg.data[4:], (address, address, uint, uint, uint)); + } + + function decodeSimpleSwapData2() internal pure returns ( + address[] memory callees, + bytes memory exchangeData, + uint256[] memory startIndexes, + uint256[] memory values, + address payable beneficiary, + string memory referrer, + bool useReduxToken + ) { + (, + callees, + exchangeData, + startIndexes, + values, + beneficiary, + referrer, + useReduxToken + ) = abi.decode(msg.data[4:], (uint[5], address[], bytes, uint256[], uint256[], address, string, bool)); + } + + fallback() external payable { + if(msg.sig == SIMPLESWAP) { + SimpleSwapData memory ssd; + (ssd.fromToken, ssd.toToken, ssd.fromAmount, ssd.toAmount, ssd.expectedAmount) = decodeSimpleSwapData1(); + (ssd.callees, ssd.exchangeData, ssd.startIndexes, ssd.values, + ssd.beneficiary, ssd.referrer, ssd.useReduxToken) = decodeSimpleSwapData2(); + simpleSwapWithStruct(ssd); + } + } +} diff --git a/lib_0.7/paraswap/V4/IAugustusSwapper.sol b/lib_0.7/paraswap/V4/IAugustusSwapper.sol new file mode 100644 index 000000000..3daae570f --- /dev/null +++ b/lib_0.7/paraswap/V4/IAugustusSwapper.sol @@ -0,0 +1,143 @@ +pragma solidity >=0.6.12; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +interface IAugustusSwapper { + + struct SellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + Path[] path; + } + + struct MegaSwapSellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + MegaSwapPath[] path; + } + + struct BuyData { + address fromToken; + address toToken; + uint256 fromAmount; + uint256 toAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + BuyRoute[] route; + } + + struct Route { + address payable exchange; + address targetExchange; + uint percent; + bytes payload; + uint256 networkFee;//Network fee is associated with 0xv3 trades + } + + struct MegaSwapPath { + uint256 fromAmountPercent; + Path[] path; + } + + struct Path { + address to; + uint256 totalNetworkFee;//Network fee is associated with 0xv3 trades + Route[] routes; + } + + struct BuyRoute { + address payable exchange; + address targetExchange; + uint256 fromAmount; + uint256 toAmount; + bytes payload; + uint256 networkFee;//Network fee is associated with 0xv3 trades + } + + function getPartnerRegistry() external view returns(address); + + function getWhitelistAddress() external view returns(address); + + function getFeeWallet() external view returns(address); + + function getTokenTransferProxy() external view returns (address); + + function paused() external view returns (bool); + + function changeUniswapProxy(address uniswapProxy) external; + + function initialize( + address whitelist, + address reduxToken, + address partnerRegistry, + address payable feeWallet, + address uniswapProxy + ) + external; + + function initializeAdapter(address adapter, bytes calldata data) external; + + function multiSwap( + SellData memory data + ) + external + payable + returns (uint256); + + function simpleSwap( + address fromToken, + address toToken, + uint256 fromAmount, + uint256 toAmount, + uint256 expectedAmount, + address[] memory callees, + bytes memory exchangeData, + uint256[] memory startIndexes, + uint256[] memory values, + address payable beneficiary, + string memory referrer, + bool useReduxToken + ) + external + payable + returns (uint256 receivedAmount); + + function swapOnUniswap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + uint8 referrer + ) + external + payable; + + function swapOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + uint8 referrer + ) + external + payable; + + function megaSwap( + MegaSwapSellData memory data + ) + external + payable + returns (uint256); +} diff --git a/lib_0.7/paraswap/V4/IPartner.sol b/lib_0.7/paraswap/V4/IPartner.sol new file mode 100644 index 000000000..e4f8b7496 --- /dev/null +++ b/lib_0.7/paraswap/V4/IPartner.sol @@ -0,0 +1,14 @@ +pragma solidity 0.7.5; + + +interface IPartner { + + function getPartnerInfo() external view returns( + address payable feeWallet, + uint256 fee, + uint256 partnerShare, + uint256 paraswapShare, + bool positiveSlippageToUser, + bool noPositiveSlippage + ); +} diff --git a/lib_0.7/paraswap/V4/IPartnerRegistry.sol b/lib_0.7/paraswap/V4/IPartnerRegistry.sol new file mode 100644 index 000000000..c7f4756e2 --- /dev/null +++ b/lib_0.7/paraswap/V4/IPartnerRegistry.sol @@ -0,0 +1,8 @@ +pragma solidity 0.7.5; + + +interface IPartnerRegistry { + + function getPartnerContract(string calldata referralId) external view returns(address); + +} diff --git a/lib/paraswap/IGST2.sol b/lib_0.7/paraswap/V4/IReduxToken.sol similarity index 64% rename from lib/paraswap/IGST2.sol rename to lib_0.7/paraswap/V4/IReduxToken.sol index 5f97f19a6..46ecd08f8 100644 --- a/lib/paraswap/IGST2.sol +++ b/lib_0.7/paraswap/V4/IReduxToken.sol @@ -1,12 +1,10 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; -interface IGST2 { +interface IReduxToken { function freeUpTo(uint256 value) external returns (uint256 freed); function freeFromUpTo(address from, uint256 value) external returns (uint256 freed); - function balanceOf(address who) external view returns (uint256); - function mint(uint256 value) external; -} \ No newline at end of file +} diff --git a/lib/paraswap/ITokenTransferProxy.sol b/lib_0.7/paraswap/V4/ITokenTransferProxy.sol similarity index 63% rename from lib/paraswap/ITokenTransferProxy.sol rename to lib_0.7/paraswap/V4/ITokenTransferProxy.sol index 11771861a..264d4540d 100644 --- a/lib/paraswap/ITokenTransferProxy.sol +++ b/lib_0.7/paraswap/V4/ITokenTransferProxy.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; interface ITokenTransferProxy { @@ -11,5 +11,5 @@ interface ITokenTransferProxy { ) external; - function freeGSTTokens(uint256 tokensToFree) external; -} \ No newline at end of file + function freeReduxTokens(address user, uint256 tokensToFree) external; +} diff --git a/lib_0.7/paraswap/V4/IUniswapProxy.sol b/lib_0.7/paraswap/V4/IUniswapProxy.sol new file mode 100644 index 000000000..3fe389393 --- /dev/null +++ b/lib_0.7/paraswap/V4/IUniswapProxy.sol @@ -0,0 +1,43 @@ +pragma solidity 0.7.5; + + +interface IUniswapProxy { + function swapOnUniswap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + returns (uint256); + + function swapOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + returns (uint256); + + function buyOnUniswap( + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + returns (uint256 tokensSold); + + function buyOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + returns (uint256 tokensSold); + + function setupTokenSpender(address tokenSpender) external; + +} diff --git a/lib_0.7/paraswap/V4/IWETH.sol b/lib_0.7/paraswap/V4/IWETH.sol new file mode 100644 index 000000000..c741eedbb --- /dev/null +++ b/lib_0.7/paraswap/V4/IWETH.sol @@ -0,0 +1,9 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + + +abstract contract IWETH is IERC20 { + function deposit() external virtual payable; + function withdraw(uint256 amount) external virtual; +} diff --git a/lib_0.7/paraswap/V4/IWhitelisted.sol b/lib_0.7/paraswap/V4/IWhitelisted.sol new file mode 100644 index 000000000..47b150f10 --- /dev/null +++ b/lib_0.7/paraswap/V4/IWhitelisted.sol @@ -0,0 +1,15 @@ +pragma solidity 0.7.5; + + +interface IWhitelisted { + + function hasRole( + bytes32 role, + address account + ) + external + view + returns (bool); + + function WHITELISTED_ROLE() external view returns(bytes32); +} diff --git a/lib_0.7/paraswap/V4/Ikarma.sol b/lib_0.7/paraswap/V4/Ikarma.sol new file mode 100644 index 000000000..d2f0c4e24 --- /dev/null +++ b/lib_0.7/paraswap/V4/Ikarma.sol @@ -0,0 +1,55 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + + + +interface IKarma { + + /** + * @dev The function performs on chain tx building and swapping + * @param fromToken Address of the source token + * @param destToken Address of the destination token + * @param fromAmount Amount of source tokens to be swapped + * @param minDestAmount Minimum destination token amount expected out of this swap + * @param beneficiary Beneficiary address + * @param distributions Distribution of fromToken to each supported exchange in basis points + * @param referrer referral id + * @return returnAmount the total amount of destination tokens received + */ + function swap( + IERC20 fromToken, + IERC20 destToken, + uint256 fromAmount, + uint256 minDestAmount, + address payable beneficiary, + uint256[] calldata distributions, + string calldata referrer + ) + external + payable + returns(uint256 returnAmount); + + /** + * @dev The function performs on chain tx building and swapping + * @param tokens Path to be followed to swap token at index 0 with token at last index + * @param fromAmount Amount of source tokens to be swapped + * @param minDestAmount Minimum destination token amount expected out of this swap + * @param beneficiary Beneficiary address + * @param distributions Distribution of tokens to each supported exchange in basis points + * @param referrer referral id + * @return returnAmount the total amount of destination tokens received + */ + function multiSwap( + IERC20[] calldata tokens, + uint256 fromAmount, + uint256 minDestAmount, + address payable beneficiary, + uint256[][] calldata distributions, + string calldata referrer + ) + external + payable + returns(uint256 returnAmount); +} diff --git a/lib_0.7/paraswap/V4/Karma.sol b/lib_0.7/paraswap/V4/Karma.sol new file mode 100644 index 000000000..239c2624c --- /dev/null +++ b/lib_0.7/paraswap/V4/Karma.sol @@ -0,0 +1,299 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-solidity/contracts/access/Ownable.sol"; + +import "./IWhitelisted.sol"; +import "./lib/IExchange.sol"; +import "./lib/Utils.sol"; +import "./KarmaTokenTransferProxy.sol"; + + +contract Karma is Ownable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + KarmaTokenTransferProxy private _tokenTransferProxy; + + bool private _paused; + + string private _version = "1.0.0"; + + event Paused(); + event Unpaused(); + + address[] public exchanges; + + event Swapped( + address initiator, + address indexed beneficiary, + address indexed srcToken, + address indexed destToken, + uint256 srcAmount, + uint256 receivedAmount, + uint256 expectedAmount, + string referrer + ); + + event ExchangeAdded(address indexed exchange, uint256 index); + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!_paused, "Pausable: paused"); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(_paused, "Pausable: not paused"); + _; + } + + constructor() + public + { + _tokenTransferProxy = new KarmaTokenTransferProxy(); + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + function getVersion() external view returns(string memory) { + return _version; + } + + function getTokenTransferProxy() external view returns (address) { + return address(_tokenTransferProxy); + } + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() external view returns (bool) { + return _paused; + } + + /** + * @dev Called by a pauser to pause, triggers stopped state. + */ + function pause() external onlyOwner whenNotPaused { + _paused = true; + emit Paused(); + } + + /** + * @dev Called by a pauser to unpause, returns to normal state. + */ + function unpause() external onlyOwner whenPaused { + _paused = false; + emit Unpaused(); + } + + function addExchange(address exchange) external onlyOwner { + exchanges.push(exchange); + emit ExchangeAdded(exchange, exchanges.length - 1); + } + + /** + * @dev The function performs on chain tx building and swapping + * @param tokens Path to be followed to swap token at index 0 with token at last index + * @param fromAmount Amount of source tokens to be swapped + * @param minDestAmount Minimum destination token amount expected out of this swap + * @param beneficiary Beneficiary address + * @param distributions Distribution of tokens to each supported exchange in basis points + * @param referrer referral id + * @return Returns the total amount of destination tokens received + */ + function multiSwap( + IERC20[] calldata tokens, + uint256 fromAmount, + uint256 minDestAmount, + address payable beneficiary, + uint256[][] calldata distributions, + string calldata referrer + ) + external + payable + returns(uint256) + { + require( + tokens.length - 1 == distributions.length, + "Number of distribution should be one less than total tokens in path" + ); + + if (address(tokens[0]) != Utils.ethAddress()) { + _tokenTransferProxy.transferFrom( + address(tokens[0]), + msg.sender, + address(this), + fromAmount + ); + } + + uint receivedAmount = fromAmount; + for (uint256 i = 0; i < tokens.length - 1; i++) { + IERC20 fromToken = tokens[i]; + IERC20 toToken = tokens[i + 1]; + + receivedAmount = _swap( + fromToken, + toToken, + receivedAmount, + distributions[i] + ); + } + + require( + receivedAmount >= minDestAmount, + "Received amount of tokens are less then expected" + ); + + Utils.transferTokens( + address(tokens[tokens.length - 1]), + beneficiary == address(0) ? msg.sender : beneficiary, + receivedAmount + ); + + emit Swapped( + msg.sender, + beneficiary == address(0)?msg.sender:beneficiary, + address(tokens[0]), + address(tokens[tokens.length - 1]), + fromAmount, + receivedAmount, + minDestAmount, + referrer + ); + + return receivedAmount; + + } + + /** + * @dev The function performs on chain tx building and swapping + * @param fromToken Address of the source token + * @param destToken Address of the destination token + * @param fromAmount Amount of source tokens to be swapped + * @param minDestAmount Minimum destination token amount expected out of this swap + * @param beneficiary Beneficiary address + * @param distributions Distribution of fromToken to each supported exchange in basis points + */ + function swap( + IERC20 fromToken, + IERC20 destToken, + uint256 fromAmount, + uint256 minDestAmount, + address payable beneficiary, + uint256[] memory distributions, + string calldata referrer + ) + external + payable + returns(uint256) + { + require( + distributions.length <= exchanges.length, + "Distributions exceeding number of exchanges" + ); + + if (address(fromToken) != Utils.ethAddress()) { + _tokenTransferProxy.transferFrom( + address(fromToken), + msg.sender, + address(this), + fromAmount + ); + } + + uint256 receivedAmount = _swap( + fromToken, + destToken, + fromAmount, + distributions + ); + require( + receivedAmount >= minDestAmount, + "Received amount of tokens are less then expected" + ); + + Utils.transferTokens( + address(destToken), + beneficiary == address(0) ? msg.sender : beneficiary, + receivedAmount + ); + + emit Swapped( + msg.sender, + beneficiary == address(0)?msg.sender:beneficiary, + address(fromToken), + address(destToken), + fromAmount, + receivedAmount, + minDestAmount, + referrer + ); + + return receivedAmount; + } + + function _swap( + IERC20 fromToken, + IERC20 destToken, + uint256 fromAmount, + uint256[] memory distributions + ) + private + returns(uint256) + { + uint256 totalPercent = 0; + for (uint256 i = 0; i < distributions.length; i++) { + + if(distributions[i] == 0) { + continue; + } + + totalPercent = totalPercent + distributions[i]; + + uint256 _fromAmount = fromAmount.mul(distributions[i]).div(10000); + if (totalPercent == 10000) { + _fromAmount = Utils.tokenBalance(address(fromToken), address(this)); + } + + if (address(fromToken) != Utils.ethAddress()) { + fromToken.safeTransfer(exchanges[i], _fromAmount); + IExchange(exchanges[i]).onChainSwap{ + value: 0 + }(fromToken, destToken, _fromAmount, 1); + } + + else { + IExchange(exchanges[i]).onChainSwap{ + value: _fromAmount + }(fromToken, destToken, _fromAmount, 1); + } + + } + + require( + totalPercent == 10000, + "Total distribution basis points should be equal to 10000" + ); + + uint256 receivedAmount = Utils.tokenBalance( + address(destToken), + address(this) + ); + + return receivedAmount; + } + +} diff --git a/lib_0.7/paraswap/V4/KarmaTokenTransferProxy.sol b/lib_0.7/paraswap/V4/KarmaTokenTransferProxy.sol new file mode 100644 index 000000000..b639b670a --- /dev/null +++ b/lib_0.7/paraswap/V4/KarmaTokenTransferProxy.sol @@ -0,0 +1,38 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/access/Ownable.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +import "./lib/SafeERC20.sol"; + + +/** +* @dev Allows owner of the contract to transfer tokens on behalf of user. +* Contracts will need to approve this contract to spend tokens on their behalf +*/ +contract KarmaTokenTransferProxy is Ownable { + using SafeERC20 for IERC20; + + + /** + * @dev Allows owner of the contract to transfer tokens on user's behalf + * @dev Swapper contract will be the owner of this contract + * @param token Address of the token + * @param from Address from which tokens will be transferred + * @param to Receipent address of the tokens + * @param amount Amount of tokens to transfer + */ + function transferFrom( + address token, + address from, + address to, + uint256 amount + ) + external + onlyOwner + { + IERC20(token).safeTransferFrom(from, to, amount); + } + +} diff --git a/lib_0.7/paraswap/V4/Migrations.sol b/lib_0.7/paraswap/V4/Migrations.sol new file mode 100755 index 000000000..92f249d8c --- /dev/null +++ b/lib_0.7/paraswap/V4/Migrations.sol @@ -0,0 +1,24 @@ +pragma solidity >=0.4.21 <=0.7.5; + + +contract Migrations { + address public owner; + uint public last_completed_migration; + + constructor() public { + owner = msg.sender; + } + + modifier restricted() { + if (msg.sender == owner) _; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/lib_0.7/paraswap/V4/Partner.md b/lib_0.7/paraswap/V4/Partner.md new file mode 100644 index 000000000..5c5f746cb --- /dev/null +++ b/lib_0.7/paraswap/V4/Partner.md @@ -0,0 +1,18 @@ +# Partner contract Specifications + +## Introduction +paraswap.io wants to allows it's partners to monetize the protocol using their referral id. + + 1. The contract should allow ParaSwap's partners to monetise the transactions. + 2. Each partner should be registered on a smart contract and tracked using the referrer parameter which is a string. + 3. Each partner should have a specific contract instance + 4. Fees slices for Partner should be set when registering the partner (default = 20% for ParaSwap, 80% for the Partner). + 5. The slice distribution could not be changed by both parties + 6. The partner should have the freedom to set the fees they want + 7. The partner should be able to set their fee wallet + 8. The partner should be able to transfer ownership + 9. ParaSwap should not have any control of over the partner fee, partner admin and wallet + 10. ParaSwap could terminate a partnership by removing the referral from the partners list. + 11. After each swap, if the partner fee > 0, the partner's slice should be redirected to their wallet (80% for instance) and the rest is sent to a ParaSwap wallet + 12. The ParaSwap wallet is controlled by ParaSwap deployer wallet + 13. Each partner contract will be controlled by an admin wallet. Partner will hold key to that wallet \ No newline at end of file diff --git a/lib/paraswap/Partner.sol b/lib_0.7/paraswap/V4/Partner.sol similarity index 70% rename from lib/paraswap/Partner.sol rename to lib_0.7/paraswap/V4/Partner.sol index 739751abd..cf1c046a2 100644 --- a/lib/paraswap/Partner.sol +++ b/lib_0.7/paraswap/V4/Partner.sol @@ -1,17 +1,19 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/access/Ownable.sol"; +import "./IPartner.sol"; -contract Partner is Ownable { +contract Partner is Ownable, IPartner { using SafeMath for uint256; - enum ChangeType { _, FEE, WALLET } + enum ChangeType { _, FEE, WALLET, SLIPPAGE } struct ChangeRequest { uint256 fee; address payable wallet; + bool slippageToUser; bool completed; uint256 requestedBlockNumber; } @@ -22,7 +24,7 @@ contract Partner is Ownable { address payable private _feeWallet; - //It should be in percentage. For 1% it should be 100 + //It should be in basis points. For 1% it should be 100 uint256 private _fee; //Paraswap share in the fee. For 20% it should 2000 @@ -37,6 +39,11 @@ contract Partner is Ownable { uint256 private _maxFee; + //Whether positive slippage will go to user + bool private _positiveSlippageToUser; + + bool private _noPositiveSlippage; + event FeeWalletChanged(address indexed feeWallet); event FeeChanged(uint256 fee); @@ -44,18 +51,21 @@ contract Partner is Ownable { ChangeType changeType, uint256 fee, address wallet, + bool positiveSlippageToUser, uint256 requestedBlockNumber ); event ChangeRequestCancelled( ChangeType changeType, uint256 fee, address wallet, + bool positiveSlippageToUser, uint256 requestedBlockNumber ); event ChangeRequestFulfilled( ChangeType changeType, uint256 fee, address wallet, + bool positiveSlippageToUser, uint256 requestedBlockNumber, uint256 fulfilledBlockNumber ); @@ -68,7 +78,9 @@ contract Partner is Ownable { uint256 partnerShare, address owner, uint256 timelock, - uint256 maxFee + uint256 maxFee, + bool noPositiveSlippage, + bool positiveSlippageToUser ) public { @@ -79,6 +91,9 @@ contract Partner is Ownable { _partnerShare = partnerShare; _timelock = timelock; _maxFee = maxFee; + _positiveSlippageToUser = positiveSlippageToUser; + _noPositiveSlippage = noPositiveSlippage; + transferOwnership(owner); } @@ -110,6 +125,33 @@ contract Partner is Ownable { return _maxFee; } + function getNoPositiveSlippage() external view returns(bool) { + return _noPositiveSlippage; + } + + function getPositiveSlippageToUser() external view returns(bool) { + return _positiveSlippageToUser; + } + + function getPartnerInfo() external override view returns( + address payable feeWallet, + uint256 fee, + uint256 partnerShare, + uint256 paraswapShare, + bool positiveSlippageToUser, + bool noPositiveSlippage + ) + { + return( + _feeWallet, + _fee, + _partnerShare, + _paraswapShare, + _positiveSlippageToUser, + _noPositiveSlippage + ); + } + function getChangeRequest( ChangeType changeType ) @@ -142,10 +184,12 @@ contract Partner is Ownable { changeRequest.fee = fee; changeRequest.requestedBlockNumber = block.number; + changeRequest.completed = false; emit ChangeRequested( ChangeType.FEE, fee, address(0), + false, block.number ); } @@ -161,10 +205,32 @@ contract Partner is Ownable { changeRequest.wallet = wallet; changeRequest.requestedBlockNumber = block.number; + changeRequest.completed = false; emit ChangeRequested( ChangeType.WALLET, 0, wallet, + false, + block.number + ); + } + + function changePositiveSlippageToUser(bool slippageToUser) external onlyOwner { + ChangeRequest storage changeRequest = _typeVsChangeRequest[uint256(ChangeType.SLIPPAGE)]; + + require( + changeRequest.requestedBlockNumber == 0 || changeRequest.completed, + "Previous slippage change request pending" + ); + + changeRequest.slippageToUser = slippageToUser; + changeRequest.requestedBlockNumber = block.number; + changeRequest.completed = false; + emit ChangeRequested( + ChangeType.WALLET, + 0, + address(0), + slippageToUser, block.number ); } @@ -188,14 +254,18 @@ contract Partner is Ownable { _fee = changeRequest.fee; } - else { + else if(changeType == ChangeType.WALLET) { _feeWallet = changeRequest.wallet; } + else { + _positiveSlippageToUser = changeRequest.slippageToUser; + } emit ChangeRequestFulfilled( changeType, changeRequest.fee, changeRequest.wallet, + changeRequest.slippageToUser, changeRequest.requestedBlockNumber, block.number ); @@ -214,9 +284,10 @@ contract Partner is Ownable { changeType, changeRequest.fee, changeRequest.wallet, + changeRequest.slippageToUser, changeRequest.requestedBlockNumber ); } -} \ No newline at end of file +} diff --git a/lib/paraswap/PartnerRegistry.sol b/lib_0.7/paraswap/V4/PartnerRegistry.sol similarity index 70% rename from lib/paraswap/PartnerRegistry.sol rename to lib_0.7/paraswap/V4/PartnerRegistry.sol index 0a73e1cc5..b7e9fe4e2 100644 --- a/lib/paraswap/PartnerRegistry.sol +++ b/lib_0.7/paraswap/V4/PartnerRegistry.sol @@ -1,6 +1,6 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/access/Ownable.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "./deployer/IPartnerDeployer.sol"; @@ -10,6 +10,7 @@ contract PartnerRegistry is Ownable { using SafeMath for uint256; mapping(bytes32 => address) private _referralVsPartner; + mapping(bytes32 => address) private _removedPartners; IPartnerDeployer private _partnerDeployer; @@ -31,8 +32,7 @@ contract PartnerRegistry is Ownable { emit PartnerDeployerChanged(partnerDeployer); } - function getPartnerContract(string calldata referralId) external view returns(address) { - + function getPartnerContract(string calldata referralId) public view returns(address) { return _referralVsPartner[keccak256(abi.encodePacked(referralId))]; } @@ -44,7 +44,9 @@ contract PartnerRegistry is Ownable { uint256 partnerShare, address owner, uint256 timelock, - uint256 maxFee + uint256 maxFee, + bool positiveSlippageToUser, + bool noPositiveSlippage ) external onlyOwner @@ -53,7 +55,10 @@ contract PartnerRegistry is Ownable { require(owner != address(0), "Invalid owner for partner"); require(fee <= 10000, "Invalid fee passed"); require(paraswapShare.add(partnerShare) == 10000, "Invalid shares"); - require(bytes(referralId).length > 0, "Empty refferalid"); + require(bytes(referralId).length > 0, "Empty referralId"); + + require(getPartnerContract(referralId) == address(0), "Partner already exists"); + require(_removedPartners[keccak256(abi.encodePacked(referralId))] == address(0), "Partner was removed before"); address partner = _partnerDeployer.deploy( referralId, @@ -63,7 +68,9 @@ contract PartnerRegistry is Ownable { partnerShare, owner, timelock, - maxFee + maxFee, + positiveSlippageToUser, + noPositiveSlippage ); _referralVsPartner[keccak256(abi.encodePacked(referralId))] = address(partner); @@ -72,8 +79,14 @@ contract PartnerRegistry is Ownable { } function removePartner(string calldata referralId) external onlyOwner { + address partner = getPartnerContract(referralId); + + require(partner != address(0), "Partner doesn't exist"); + _referralVsPartner[keccak256(abi.encodePacked(referralId))] = address(0); + _removedPartners[keccak256(abi.encodePacked(referralId))] = partner; + emit PartnerRemoved(referralId); } -} \ No newline at end of file +} diff --git a/lib/paraswap/TokenTransferProxy.sol b/lib_0.7/paraswap/V4/TokenTransferProxy.sol similarity index 51% rename from lib/paraswap/TokenTransferProxy.sol rename to lib_0.7/paraswap/V4/TokenTransferProxy.sol index 109c76801..5aa7c528a 100644 --- a/lib/paraswap/TokenTransferProxy.sol +++ b/lib_0.7/paraswap/V4/TokenTransferProxy.sol @@ -1,9 +1,10 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/access/Ownable.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; -import "./IGST2.sol"; +import "./lib/SafeERC20.sol"; +import "./IReduxToken.sol"; +import "./ITokenTransferProxy.sol"; /** @@ -11,29 +12,13 @@ import "./IGST2.sol"; * User will need to approve this contract to spend tokens on his/her behalf * on Paraswap platform */ -contract TokenTransferProxy is Ownable { +contract TokenTransferProxy is Ownable, ITokenTransferProxy { using SafeERC20 for IERC20; - IGST2 private _gst2; - - address private _gstHolder; - - constructor(address gst2, address gstHolder) public { - _gst2 = IGST2(gst2); - _gstHolder = gstHolder; - } - - function getGSTHolder() external view returns(address) { - return _gstHolder; - } - - function getGST() external view returns(address) { - return address(_gst2); - } - - function changeGSTTokenHolder(address gstHolder) external onlyOwner { - _gstHolder = gstHolder; + IReduxToken public reduxToken; + constructor(address _reduxToken) public { + reduxToken = IReduxToken(_reduxToken); } /** @@ -51,13 +36,14 @@ contract TokenTransferProxy is Ownable { uint256 amount ) external + override onlyOwner { IERC20(token).safeTransferFrom(from, to, amount); } - function freeGSTTokens(uint256 tokensToFree) external onlyOwner { - _gst2.freeFromUpTo(_gstHolder, tokensToFree); + function freeReduxTokens(address user, uint256 tokensToFree) external override onlyOwner { + reduxToken.freeFromUpTo(user, tokensToFree); } -} \ No newline at end of file +} diff --git a/lib_0.7/paraswap/V4/UniswapProxy.sol b/lib_0.7/paraswap/V4/UniswapProxy.sol new file mode 100644 index 000000000..0ce2934f1 --- /dev/null +++ b/lib_0.7/paraswap/V4/UniswapProxy.sol @@ -0,0 +1,261 @@ +pragma solidity =0.7.5; + +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +import "./ITokenTransferProxy.sol"; +import './lib/UniswapV3Lib.sol'; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import './IWETH.sol'; +import "./AdapterStorage.sol"; + +contract UniswapProxy is AdapterStorage { + using SafeMath for uint; + + address public constant UNISWAP_FACTORY = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); + address public constant WETH = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + address public constant ETH_IDENTIFIER = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + bytes32 public constant UNISWAP_INIT_CODE = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f; + + function swapOnUniswap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + payable + returns (uint256 tokensBought) + { + tokensBought = _swap( + UNISWAP_FACTORY, + UNISWAP_INIT_CODE, + amountIn, + path + ); + + require(tokensBought >= amountOutMin, "UniswapProxy: INSUFFICIENT_OUTPUT_AMOUNT"); + } + + function swapOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + payable + returns (uint256 tokensBought) + { + tokensBought = _swap( + factory, + initCode, + amountIn, + path + ); + require(tokensBought >= amountOutMin, "UniswapProxy: INSUFFICIENT_OUTPUT_AMOUNT"); + } + + function buyOnUniswap( + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + payable + returns (uint256 tokensSold) + { + tokensSold = _buy( + UNISWAP_FACTORY, + UNISWAP_INIT_CODE, + amountOut, + path + ); + + require(tokensSold <= amountInMax, "UniswapProxy: INSUFFICIENT_INPUT_AMOUNT"); + } + + function buyOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + payable + returns (uint256 tokensSold) + { + tokensSold = _buy( + factory, + initCode, + amountOut, + path + ); + + require(tokensSold <= amountInMax, "UniswapProxy: INSUFFICIENT_INPUT_AMOUNT"); + } + + function transferTokens( + address token, + address from, + address to, + uint256 amount + ) + private + { + ITokenTransferProxy(getTokenTransferProxy()).transferFrom( + token, from, to, amount + ); + } + + function _swap( + address factory, + bytes32 initCode, + uint256 amountIn, + address[] calldata path + ) + private + returns (uint256 tokensBought) + { + require(path.length > 1, "More than 1 token required"); + uint8 pairs = uint8(path.length - 1); + bool tokensBoughtEth; + tokensBought = amountIn; + address receiver; + + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + + if (i == pairs - 1) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + uint256 amount = msg.value; + IWETH(WETH).deposit{value: amount}(); + assert(IWETH(WETH).transfer(currentPair, amount)); + } + else { + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + transferTokens( + tokenSold, msg.sender, currentPair, amountIn + ); + } + } + + //AmountIn for this hop is amountOut of previous hop + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + if ( tokensBoughtEth ) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = UniswapV3Lib.pairFor(factory, tokenBought, path[i+2] == ETH_IDENTIFIER ? WETH : path[i+2], initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + } + if (tokensBoughtEth) { + IWETH(WETH).withdraw(tokensBought); + TransferHelper.safeTransferETH(msg.sender, tokensBought); + } + } + + function _buy( + address factory, + bytes32 initCode, + uint256 amountOut, + address[] calldata path + ) + private + returns (uint256 tokensSold) + { + require(path.length > 1, "More than 1 token required"); + bool tokensBoughtEth; + uint8 length = uint8(path.length); + + uint256[] memory amounts = new uint256[](length); + address[] memory pairs = new address[](length - 1); + + amounts[length - 1] = amountOut; + + for (uint8 i = length - 1; i > 0; i--) { + (amounts[i - 1], pairs[i - 1]) = UniswapV3Lib.getAmountInAndPair( + factory, + amounts[i], + path[i-1], + path[i], + initCode + ); + } + + tokensSold = amounts[0]; + + for(uint8 i = 0; i < length - 1; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + if (i == length - 2) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + TransferHelper.safeTransferETH(msg.sender, msg.value.sub(tokensSold)); + IWETH(WETH).deposit{value: tokensSold}(); + assert(IWETH(WETH).transfer(pairs[i], tokensSold)); + } + else { + transferTokens( + tokenSold, msg.sender, pairs[i], tokensSold + ); + } + } + + address receiver; + + if (i == length - 2) { + if (tokensBoughtEth) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = pairs[i+1]; + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), amounts[i+1]) : (amounts[i+1], uint256(0)); + IUniswapV2Pair(pairs[i]).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + if (tokensBoughtEth) { + IWETH(WETH).withdraw(amountOut); + TransferHelper.safeTransferETH(msg.sender, amountOut); + } + } +} diff --git a/lib_0.7/paraswap/V4/UniswapProxyTest.sol b/lib_0.7/paraswap/V4/UniswapProxyTest.sol new file mode 100644 index 000000000..940c23184 --- /dev/null +++ b/lib_0.7/paraswap/V4/UniswapProxyTest.sol @@ -0,0 +1,267 @@ +pragma solidity =0.7.5; + +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +import "./ITokenTransferProxy.sol"; +import './lib/UniswapV3Lib.sol'; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import './IWETH.sol'; +import "./AdapterStorage.sol"; + +contract UniswapProxyTest is AdapterStorage { + using SafeMath for uint; + + address public constant ETH_IDENTIFIER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address public immutable UNISWAP_FACTORY; + address public immutable WETH; + bytes32 public immutable UNISWAP_INIT_CODE; + + constructor(address _weth, address _factory, bytes32 _initCode) public { + WETH = _weth; + UNISWAP_FACTORY = _factory; + UNISWAP_INIT_CODE = _initCode; + } + + function swapOnUniswap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + payable + returns (uint256 tokensBought) + { + tokensBought = _swap( + UNISWAP_FACTORY, + UNISWAP_INIT_CODE, + amountIn, + path + ); + + require(tokensBought >= amountOutMin, "UniswapProxy: INSUFFICIENT_OUTPUT_AMOUNT"); + } + + function swapOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + payable + returns (uint256 tokensBought) + { + tokensBought = _swap( + factory, + initCode, + amountIn, + path + ); + require(tokensBought >= amountOutMin, "UniswapProxy: INSUFFICIENT_OUTPUT_AMOUNT"); + } + + function buyOnUniswap( + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + payable + returns (uint256 tokensSold) + { + tokensSold = _buy( + UNISWAP_FACTORY, + UNISWAP_INIT_CODE, + amountOut, + path + ); + + require(tokensSold <= amountInMax, "UniswapProxy: INSUFFICIENT_INPUT_AMOUNT"); + } + + function buyOnUniswapFork( + address factory, + bytes32 initCode, + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + payable + returns (uint256 tokensSold) + { + tokensSold = _buy( + factory, + initCode, + amountOut, + path + ); + + require(tokensSold <= amountInMax, "UniswapProxy: INSUFFICIENT_INPUT_AMOUNT"); + } + + function transferTokens( + address token, + address from, + address to, + uint256 amount + ) + private + { + ITokenTransferProxy(getTokenTransferProxy()).transferFrom( + token, from, to, amount + ); + } + + function _swap( + address factory, + bytes32 initCode, + uint256 amountIn, + address[] calldata path + ) + private + returns (uint256 tokensBought) + { + require(path.length > 1, "More than 1 token required"); + uint8 pairs = uint8(path.length - 1); + bool tokensBoughtEth; + tokensBought = amountIn; + address receiver; + + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + + if (i == pairs - 1) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + uint256 amount = msg.value; + IWETH(WETH).deposit{value: amount}(); + assert(IWETH(WETH).transfer(currentPair, amount)); + } + else { + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + transferTokens( + tokenSold, msg.sender, currentPair, amountIn + ); + } + } + + //AmountIn for this hop is amountOut of previous hop + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + if ( tokensBoughtEth ) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = UniswapV3Lib.pairFor(factory, tokenBought, path[i+2] == ETH_IDENTIFIER ? WETH : path[i+2], initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + } + if (tokensBoughtEth) { + IWETH(WETH).withdraw(tokensBought); + TransferHelper.safeTransferETH(msg.sender, tokensBought); + } + } + + function _buy( + address factory, + bytes32 initCode, + uint256 amountOut, + address[] calldata path + ) + private + returns (uint256 tokensSold) + { + require(path.length > 1, "More than 1 token required"); + bool tokensBoughtEth; + uint8 length = uint8(path.length); + + uint256[] memory amounts = new uint256[](length); + address[] memory pairs = new address[](length - 1); + + amounts[length - 1] = amountOut; + + for (uint8 i = length - 1; i > 0; i--) { + (amounts[i - 1], pairs[i - 1]) = UniswapV3Lib.getAmountInAndPair( + factory, + amounts[i], + path[i-1], + path[i], + initCode + ); + } + + tokensSold = amounts[0]; + + for(uint8 i = 0; i < length - 1; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + if (i == length - 2) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + TransferHelper.safeTransferETH(msg.sender, msg.value.sub(tokensSold)); + IWETH(WETH).deposit{value: tokensSold}(); + assert(IWETH(WETH).transfer(pairs[i], tokensSold)); + } + else { + transferTokens( + tokenSold, msg.sender, pairs[i], tokensSold + ); + } + } + + address receiver; + + if (i == length - 2) { + if (tokensBoughtEth) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = pairs[i+1]; + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), amounts[i+1]) : (amounts[i+1], uint256(0)); + IUniswapV2Pair(pairs[i]).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + if (tokensBoughtEth) { + IWETH(WETH).withdraw(amountOut); + TransferHelper.safeTransferETH(msg.sender, amountOut); + } + } +} diff --git a/lib_0.7/paraswap/V4/UniswapV3Router.sol b/lib_0.7/paraswap/V4/UniswapV3Router.sol new file mode 100644 index 000000000..806c71470 --- /dev/null +++ b/lib_0.7/paraswap/V4/UniswapV3Router.sol @@ -0,0 +1,183 @@ +pragma solidity =0.7.5; + +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +import './lib/UniswapV3Lib.sol'; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import './IWETH.sol'; + +contract UniswapV3Router { + using SafeMath for uint; + + address public immutable factory; + address public immutable WETH; + address public constant ETH_IDENTIFIER = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + bytes32 public initCode; + + constructor(address _factory, address _WETH, bytes32 _initCode) public { + factory = _factory; + WETH = _WETH; + initCode = _initCode; + } + + receive() external payable { + } + + function swap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) + external + payable + returns (uint256 tokensBought) + { + require(path.length > 1, "More than 1 token required"); + uint8 pairs = uint8(path.length - 1); + bool tokensBoughtEth; + tokensBought = amountIn; + address receiver; + + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + + if (i == pairs - 1) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + uint256 amount = msg.value; + IWETH(WETH).deposit{value: amount}(); + assert(IWETH(WETH).transfer(currentPair, amount)); + } + else { + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + TransferHelper.safeTransferFrom( + tokenSold, msg.sender, currentPair, amountIn + ); + } + } + + //AmountIn for this hop is amountOut of previous hop + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + if ( tokensBoughtEth ) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = UniswapV3Lib.pairFor(factory, tokenBought, path[i+2] == ETH_IDENTIFIER ? WETH : path[i+2], initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + + if (tokensBoughtEth) { + IWETH(WETH).withdraw(tokensBought); + TransferHelper.safeTransferETH(msg.sender, tokensBought); + } + + require(tokensBought >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); + + } + + function buy( + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) + external + payable + returns (uint256 tokensSold) + { + require(path.length > 1, "More than 1 token required"); + bool tokensBoughtEth; + uint8 length = uint8(path.length); + + uint256[] memory amounts = new uint256[](length); + address[] memory pairs = new address[](length - 1); + + amounts[length - 1] = amountOut; + + for (uint8 i = length - 1; i > 0; i--) { + (amounts[i - 1], pairs[i - 1]) = UniswapV3Lib.getAmountInAndPair( + factory, + amounts[i], + path[i-1], + path[i], + initCode + ); + } + + tokensSold = amounts[0]; + require(tokensSold <= amountInMax, "UniswapV3Router: INSUFFICIENT_INPUT_AMOUNT"); + + for(uint8 i = 0; i < length - 1; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + if (i == length - 2) { + if (tokenBought == ETH_IDENTIFIER) { + tokenBought = WETH; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_IDENTIFIER) { + tokenSold = WETH; + TransferHelper.safeTransferETH(msg.sender, msg.value.sub(tokensSold)); + IWETH(WETH).deposit{value: tokensSold}(); + assert(IWETH(WETH).transfer(pairs[i], tokensSold)); + } + else { + TransferHelper.safeTransferFrom( + tokenSold, msg.sender, pairs[i], tokensSold + ); + } + } + + address receiver; + + if (i == length - 2) { + if (tokensBoughtEth) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = pairs[i+1]; + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), amounts[i+1]) : (amounts[i+1], uint256(0)); + IUniswapV2Pair(pairs[i]).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + + if (tokensBoughtEth) { + IWETH(WETH).withdraw(amountOut); + TransferHelper.safeTransferETH(msg.sender, amountOut); + } + } +} diff --git a/lib_0.7/paraswap/V4/Whitelisted.sol b/lib_0.7/paraswap/V4/Whitelisted.sol new file mode 100644 index 000000000..aaf29e1ac --- /dev/null +++ b/lib_0.7/paraswap/V4/Whitelisted.sol @@ -0,0 +1,14 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/access/AccessControl.sol"; + + +contract Whitelisted is AccessControl { + + bytes32 public constant WHITELISTED_ROLE = keccak256("WHITELISTED_ROLE"); + + constructor() public { + _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); + } + +} diff --git a/lib/paraswap/deployer/IPartnerDeployer.sol b/lib_0.7/paraswap/V4/deployer/IPartnerDeployer.sol similarity index 72% rename from lib/paraswap/deployer/IPartnerDeployer.sol rename to lib_0.7/paraswap/V4/deployer/IPartnerDeployer.sol index b84c8959a..76f1921ff 100644 --- a/lib/paraswap/deployer/IPartnerDeployer.sol +++ b/lib_0.7/paraswap/V4/deployer/IPartnerDeployer.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; interface IPartnerDeployer { @@ -11,8 +11,10 @@ interface IPartnerDeployer { uint256 partnerShare, address owner, uint256 timelock, - uint256 maxFee + uint256 maxFee, + bool positiveSlippageToUser, + bool noPositiveSlippage ) external returns(address); -} \ No newline at end of file +} diff --git a/lib/paraswap/deployer/PartnerDeployer.sol b/lib_0.7/paraswap/V4/deployer/PartnerDeployer.sol similarity index 73% rename from lib/paraswap/deployer/PartnerDeployer.sol rename to lib_0.7/paraswap/V4/deployer/PartnerDeployer.sol index 3b93bbada..695dea60f 100644 --- a/lib/paraswap/deployer/PartnerDeployer.sol +++ b/lib_0.7/paraswap/V4/deployer/PartnerDeployer.sol @@ -1,8 +1,9 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "./IPartnerDeployer.sol"; import "../Partner.sol"; + contract PartnerDeployer is IPartnerDeployer { function deploy( @@ -13,9 +14,12 @@ contract PartnerDeployer is IPartnerDeployer { uint256 partnerShare, address owner, uint256 timelock, - uint256 maxFee + uint256 maxFee, + bool positiveSlippageToUser, + bool noPositiveSlippage ) external + override returns(address) { Partner partner = new Partner( @@ -26,8 +30,10 @@ contract PartnerDeployer is IPartnerDeployer { partnerShare, owner, timelock, - maxFee + maxFee, + positiveSlippageToUser, + noPositiveSlippage ); return address(partner); } -} \ No newline at end of file +} diff --git a/lib/paraswap/lib/IExchange.sol b/lib_0.7/paraswap/V4/lib/IExchange.sol similarity index 63% rename from lib/paraswap/lib/IExchange.sol rename to lib_0.7/paraswap/V4/lib/IExchange.sol index d5c64c8c2..c65d72c03 100644 --- a/lib/paraswap/lib/IExchange.sol +++ b/lib_0.7/paraswap/V4/lib/IExchange.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; @@ -19,15 +19,16 @@ interface IExchange { * @param payload Any exchange specific data which is required can be passed in this argument in encoded format which * will be decoded by the exchange. Each exchange will publish it's own decoding/encoding mechanism */ + //TODO: REMOVE RETURN STATEMENT function swap( IERC20 fromToken, IERC20 toToken, uint256 fromAmount, uint256 toAmount, address exchange, - bytes calldata payload) external payable returns (uint256); + bytes calldata payload) external payable; -/** + /** * @dev The function which performs the swap on an exchange. * Exchange needs to implement this method in order to support swapping of tokens through it * @param fromToken Address of the source token @@ -44,6 +45,33 @@ interface IExchange { uint256 fromAmount, uint256 toAmount, address exchange, - bytes calldata payload) external payable returns (uint256); + bytes calldata payload) external payable; + + /** + * @dev This function is used to perform onChainSwap. It build all the parameters onchain. Basically the information + * encoded in payload param of swap will calculated in this case + * Exchange needs to implement this method in order to support swapping of tokens through it + * @param fromToken Address of the source token + * @param toToken Address of the destination token + * @param fromAmount Amount of source tokens to be swapped + * @param toAmount Minimum destination token amount expected out of this swap + */ + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) external payable returns (uint256); + + /** + * @dev Certain adapters/exchanges needs to be initialized. + * This method will be called from Augustus + */ + function initialize(bytes calldata data) external; + + /** + * @dev Returns unique identifier for the adapter + */ + function getKey() external view returns(bytes32); } diff --git a/lib_0.7/paraswap/V4/lib/IUniswapV2Pair.sol b/lib_0.7/paraswap/V4/lib/IUniswapV2Pair.sol new file mode 100644 index 000000000..a50a006de --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/IUniswapV2Pair.sol @@ -0,0 +1,13 @@ +pragma solidity 0.7.5; + +interface IUniswapV2Pair { + + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function swap( + uint amount0Out, + uint amount1Out, + address to, + bytes calldata data + ) + external; +} diff --git a/lib_0.7/paraswap/V4/lib/ReduxToken.sol b/lib_0.7/paraswap/V4/lib/ReduxToken.sol new file mode 100644 index 000000000..2588ca301 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/ReduxToken.sol @@ -0,0 +1,250 @@ +pragma solidity 0.7.5; + + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../IReduxToken.sol"; + + +contract ReduxToken is IERC20, IReduxToken { + using SafeMath for uint256; + + string constant public name = "REDUX"; + string constant public symbol = "REDUX"; + uint8 constant public decimals = 0; + + mapping(address => uint256) private s_balances; + mapping(address => mapping(address => uint256)) private s_allowances; + + uint256 public totalReduxMinted; + uint256 public totalReduxBurned; + + //The EIP-712 typehash for the contract's domain + bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); + + //The EIP-712 typehash for the permit struct used by the contract + bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + + //A record of states for signing / validating signatures + mapping (address => uint) public nonces; + + function totalSupply() external view override returns(uint256) { + return totalReduxMinted.sub(totalReduxBurned); + } + + function mint(uint256 value) external override { + uint256 offset = totalReduxMinted; + + assembly { + + // EVM assembler of runtime portion of child contract: + // ;; Pseudocode: if (msg.sender != 0x000000000000cb2d80a37898be43579c7b616844) { throw; } + // ;; suicide(msg.sender) + // PUSH14 0xcb2d80a37898be43579c7b616856 ;; hardcoded address of this contract + // CALLER + // XOR + // JUMP + // JUMPDEST + // CALLER + // SELFDESTRUCT + // Or in binary: 6dcb2d80a37898be43579c7b6168563318565b33ff + // Since the binary is so short (21 bytes), we can get away + // with a very simple initcode: + // PUSH21 0x6dcb2d80a37898be43579c7b6168573318565b33ff + // PUSH1 0 + // MSTORE ;; at this point, memory locations mem[10] through + // ;; mem[30] contain the runtime portion of the child + // ;; contract. all that's left to do is to RETURN this + // ;; chunk of memory. + // PUSH1 21 ;; length + // PUSH1 11 ;; offset + // RETURN + // Or in binary: 746dcb2d80a37898be43579c7b6168563318565b33ff6000526015600bf30000 + // Almost done! All we have to do is put this short (30 bytes) blob into + // memory and call CREATE with the appropriate offsets. + + let end := add(offset, value) + mstore(callvalue(), 0x746dcb2d80a37898be43579c7b6168563318565b33ff6000526015600bf30000) + + for {let i := div(value, 32)} i {i := sub(i, 1)} { + pop(create2(callvalue(), callvalue(), 30, add(offset, 0))) pop(create2(callvalue(), callvalue(), 30, add(offset, 1))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 2))) pop(create2(callvalue(), callvalue(), 30, add(offset, 3))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 4))) pop(create2(callvalue(), callvalue(), 30, add(offset, 5))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 6))) pop(create2(callvalue(), callvalue(), 30, add(offset, 7))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 8))) pop(create2(callvalue(), callvalue(), 30, add(offset, 9))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 10))) pop(create2(callvalue(), callvalue(), 30, add(offset, 11))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 12))) pop(create2(callvalue(), callvalue(), 30, add(offset, 13))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 14))) pop(create2(callvalue(), callvalue(), 30, add(offset, 15))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 16))) pop(create2(callvalue(), callvalue(), 30, add(offset, 17))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 18))) pop(create2(callvalue(), callvalue(), 30, add(offset, 19))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 20))) pop(create2(callvalue(), callvalue(), 30, add(offset, 21))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 22))) pop(create2(callvalue(), callvalue(), 30, add(offset, 23))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 24))) pop(create2(callvalue(), callvalue(), 30, add(offset, 25))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 26))) pop(create2(callvalue(), callvalue(), 30, add(offset, 27))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 28))) pop(create2(callvalue(), callvalue(), 30, add(offset, 29))) + pop(create2(callvalue(), callvalue(), 30, add(offset, 30))) pop(create2(callvalue(), callvalue(), 30, add(offset, 31))) + offset := add(offset, 32) + } + + for { } lt(offset, end) { offset := add(offset, 1) } { + pop(create2(callvalue(), callvalue(), 30, offset)) + } + } + + _mint(msg.sender, value); + totalReduxMinted = offset; + } + + function free(uint256 value) external { + _burn(msg.sender, value); + _destroyChildren(value); + } + + function freeUpTo(uint256 value) external override returns (uint256) { + uint256 fromBalance = s_balances[msg.sender]; + if (value > fromBalance) { + value = fromBalance; + } + _burn(msg.sender, value); + _destroyChildren(value); + + return value; + } + + function freeFromUpTo(address from, uint256 value) external override returns (uint256) { + uint256 fromBalance = s_balances[from]; + if (value > fromBalance) { + value = fromBalance; + } + + uint256 userAllowance = s_allowances[from][msg.sender]; + if (value > userAllowance) { + value = userAllowance; + } + _burnFrom(from, value); + _destroyChildren(value); + + return value; + } + + function freeFrom(address from, uint256 value) external { + _burnFrom(from, value); + _destroyChildren(value); + } + + function allowance(address owner, address spender) external view override returns (uint256) { + return s_allowances[owner][spender]; + } + + function transfer(address recipient, uint256 amount) external override returns (bool) { + _transfer(msg.sender, recipient, amount); + return true; + } + + function approve(address spender, uint256 amount) external override returns (bool) { + _approve(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, msg.sender, s_allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @notice Triggers an approval from owner to spends + * @param owner The address to approve from + * @param spender The address to be approved + * @param amount The number of tokens that are approved + * @param deadline The time at which to expire the signature + * @param v The recovery byte of the signature + * @param r Half of the ECDSA signature pair + * @param s Half of the ECDSA signature pair + */ + function permit( + address owner, + address spender, + uint256 amount, + uint deadline, + uint8 v, + bytes32 r, + bytes32 s + ) + external + { + + bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this))); + bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline)); + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + address signatory = ecrecover(digest, v, r, s); + require(signatory != address(0), "permit: invalid signature"); + require(signatory == owner, "permit: unauthorized"); + require(block.timestamp <= deadline, "permit: signature expired"); + + _approve(owner, spender, amount); + } + + function balanceOf(address account) public view override returns (uint256) { + return s_balances[account]; + } + + function _transfer(address sender, address recipient, uint256 amount) private { + s_balances[sender] = s_balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + s_balances[recipient] = s_balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + function _approve(address owner, address spender, uint256 amount) private { + s_allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _mint(address account, uint256 amount) private { + s_balances[account] = s_balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + function _burn(address account, uint256 amount) private { + s_balances[account] = s_balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + emit Transfer(account, address(0), amount); + } + + function _burnFrom(address account, uint256 amount) private { + _burn(account, amount); + _approve(account, msg.sender, s_allowances[account][msg.sender].sub(amount, "ERC20: burn amount exceeds allowance")); + } + + function computeAddress2(uint256 salt) public pure returns (address child) { + assembly { + let data := mload(0x40) + mstore(data, 0xff000000000000cb2d80a37898be43579c7b6168440000000000000000000000) + mstore(add(data, 21), salt) + mstore(add(data, 53), 0xe4135d085e66541f164ddfd4dd9d622a50176c98e7bcdbbc6634d80cd31e9421) + child := and(keccak256(data, 85), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + } + + function _destroyChildren(uint256 value) internal { + assembly { + let i := sload(totalReduxBurned.slot) + let end := add(i, value) + sstore(totalReduxBurned.slot, end) + + let data := mload(0x40) + mstore(data, 0xff000000000000cb2d80a37898be43579c7b6168440000000000000000000000) + mstore(add(data, 53), 0xe4135d085e66541f164ddfd4dd9d622a50176c98e7bcdbbc6634d80cd31e9421) + let ptr := add(data, 21) + for { } lt(i, end) { i := add(i, 1) } { + mstore(ptr, i) + pop(call(gas(), keccak256(data, 85), callvalue(), callvalue(), callvalue(), callvalue(), callvalue())) + } + } + } + + function getChainId() internal pure returns (uint) { + uint256 chainId; + assembly { chainId := chainid() } + return chainId; + } +} diff --git a/lib_0.7/paraswap/V4/lib/SafeERC20.sol b/lib_0.7/paraswap/V4/lib/SafeERC20.sol new file mode 100644 index 000000000..a35715e42 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/SafeERC20.sol @@ -0,0 +1,106 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + + +library Address { + + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + 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"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // 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 + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/TokenFetcherAugustus.sol b/lib_0.7/paraswap/V4/lib/TokenFetcherAugustus.sol new file mode 100644 index 000000000..a697b9c60 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/TokenFetcherAugustus.sol @@ -0,0 +1,65 @@ +pragma solidity 0.7.5; + +import "./Utils.sol"; + + +contract TokenFetcherAugustus { + + address internal _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == msg.sender, "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 onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + /** + * @dev Allows owner of the contract to transfer any tokens which are assigned to the contract + * This method is for safety if by any chance tokens or ETHs are assigned to the contract by mistake + * @dev token Address of the token to be transferred + * @dev destination Recepient of the token + * @dev amount Amount of tokens to be transferred + */ + function transferTokens( + address token, + address payable destination, + uint256 amount + ) + external + onlyOwner + { + Utils.transferTokens(token, destination, amount); + } +} diff --git a/lib_0.7/paraswap/V4/lib/UniswapAugustusLib.sol b/lib_0.7/paraswap/V4/lib/UniswapAugustusLib.sol new file mode 100644 index 000000000..eb03e6d3b --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/UniswapAugustusLib.sol @@ -0,0 +1,94 @@ +pragma solidity 0.7.5; + + +import "./UniswapV3Lib.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "../IWETH.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../ITokenTransferProxy.sol"; +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +library UniswapAugustusLib { + using SafeMath for uint256; + + address constant ETH_ADDRESS = address( + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + ); + + address constant WETH_ADDRESS = address( + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ); + + function swap( + address factory, + bytes32 initCode, + uint256 amountIn, + address[] memory path, + address tokenTransferProxy + ) + internal + returns (uint256 tokensBought) + { + uint8 pairs = uint8(path.length - 1); + require(pairs > 0, "More than 1 token required"); + bool tokensBoughtEth; + tokensBought = amountIn; + address receiver; + + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + + if (i == pairs - 1) { + if (tokenBought == ETH_ADDRESS) { + tokenBought = WETH_ADDRESS; + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == ETH_ADDRESS) { + tokenSold = WETH_ADDRESS; + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + uint256 amount = msg.value; + IWETH(WETH_ADDRESS).deposit{value: amount}(); + assert(IWETH(WETH_ADDRESS).transfer(currentPair, amount)); + } + else { + currentPair = UniswapV3Lib.pairFor(factory, tokenSold, tokenBought, initCode); + ITokenTransferProxy(tokenTransferProxy).transferFrom( + tokenSold, msg.sender, currentPair, amountIn + ); + } + } + + //AmountIn for this hop is amountOut of previous hop + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + if ( tokensBoughtEth ) { + receiver = address(this); + } + else { + receiver = msg.sender; + } + } + else { + receiver = UniswapV3Lib.pairFor(factory, tokenBought, path[i+2] == ETH_ADDRESS ? WETH_ADDRESS : path[i+2], initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + + if (tokensBoughtEth) { + IWETH(WETH_ADDRESS).withdraw(tokensBought); + TransferHelper.safeTransferETH(msg.sender, tokensBought); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/UniswapV3Lib.sol b/lib_0.7/paraswap/V4/lib/UniswapV3Lib.sol new file mode 100644 index 000000000..e75f9a811 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/UniswapV3Lib.sol @@ -0,0 +1,68 @@ +pragma solidity >=0.5.0; + +import "./IUniswapV2Pair.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + + +library UniswapV3Lib { + using SafeMath for uint256; + + function checkAndConvertETHToWETH(address token) internal pure returns(address) { + + if(token == address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)) { + return address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + } + return token; + } + + // returns sorted token addresses, used to handle return values from pairs sorted in this order + function sortTokens(address tokenA, address tokenB) internal pure returns (address, address) { + + return(tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA)); + } + + // calculates the CREATE2 address for a pair without making any external calls + function pairFor(address factory, address tokenA, address tokenB, bytes32 initCode) internal pure returns (address) { + (address token0, address token1) = sortTokens(tokenA, tokenB); + return(address(uint(keccak256(abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encodePacked(token0, token1)), + initCode // init code hash + ))))); + } + + function getReservesByPair(address pair, address tokenA, address tokenB) internal view returns (uint256 reserveA, uint256 reserveB) { + (address token0,) = sortTokens(tokenA, tokenB); + (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountOut) { + require(amountIn > 0, "UniswapV3Library: INSUFFICIENT_INPUT_AMOUNT"); + uint256 amountInWithFee = amountIn.mul(997); + uint256 numerator = amountInWithFee.mul(reserveOut); + uint256 denominator = reserveIn.mul(1000).add(amountInWithFee); + amountOut = uint256(numerator / denominator); + } + + // given an output amount of an asset and pair reserves, returns a required input amount of the other asset + function getAmountInAndPair(address factory, uint amountOut, address tokenA, address tokenB, bytes32 initCode) internal view returns (uint256 amountIn, address pair) { + tokenA = checkAndConvertETHToWETH(tokenA); + tokenB = checkAndConvertETHToWETH(tokenB); + + pair = pairFor(factory, tokenA, tokenB, initCode); + (uint256 reserveIn, uint256 reserveOut) = getReservesByPair(pair, tokenA, tokenB); + require(amountOut > 0, "UniswapV3Library: INSUFFICIENT_OUTPUT_AMOUNT"); + require(reserveOut > amountOut, "UniswapV3Library: reserveOut should be greater than amountOut"); + uint numerator = reserveIn.mul(amountOut).mul(1000); + uint denominator = reserveOut.sub(amountOut).mul(997); + amountIn = (numerator / denominator).add(1); + } + + function getAmountOutByPair(uint256 amountIn, address pair, address tokenA, address tokenB) internal view returns(uint256 amountOut) { + (uint256 reserveIn, uint256 reserveOut) = getReservesByPair(pair, tokenA, tokenB); + return (getAmountOut(amountIn, reserveIn, reserveOut)); + } +} diff --git a/lib_0.7/paraswap/V4/lib/Utils.sol b/lib_0.7/paraswap/V4/lib/Utils.sol new file mode 100644 index 000000000..3622b4e70 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/Utils.sol @@ -0,0 +1,193 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "./SafeERC20.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../ITokenTransferProxy.sol"; + + +library Utils { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + address constant ETH_ADDRESS = address( + 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE + ); + + address constant WETH_ADDRESS = address( + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + ); + + uint256 constant MAX_UINT = 2 ** 256 - 1; + + /** + * @param fromToken Address of the source token + * @param toToken Address of the destination token + * @param fromAmount Amount of source tokens to be swapped + * @param toAmount Minimum destination token amount expected out of this swap + * @param expectedAmount Expected amount of destination tokens without slippage + * @param beneficiary Beneficiary address + * 0 then 100% will be transferred to beneficiary. Pass 10000 for 100% + * @param referrer referral id + * @param path Route to be taken for this swap to take place + + */ + struct SellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + Utils.Path[] path; + + } + + struct MegaSwapSellData { + address fromToken; + uint256 fromAmount; + uint256 toAmount; + uint256 expectedAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + Utils.MegaSwapPath[] path; + } + + struct BuyData { + address fromToken; + address toToken; + uint256 fromAmount; + uint256 toAmount; + address payable beneficiary; + string referrer; + bool useReduxToken; + Utils.BuyRoute[] route; + } + + struct Route { + address payable exchange; + address targetExchange; + uint percent; + bytes payload; + uint256 networkFee;//Network fee is associated with 0xv3 trades + } + + struct MegaSwapPath { + uint256 fromAmountPercent; + Path[] path; + } + + struct Path { + address to; + uint256 totalNetworkFee;//Network fee is associated with 0xv3 trades + Route[] routes; + } + + struct BuyRoute { + address payable exchange; + address targetExchange; + uint256 fromAmount; + uint256 toAmount; + bytes payload; + uint256 networkFee;//Network fee is associated with 0xv3 trades + } + + function ethAddress() internal pure returns (address) {return ETH_ADDRESS;} + + function wethAddress() internal pure returns (address) {return WETH_ADDRESS;} + + function maxUint() internal pure returns (uint256) {return MAX_UINT;} + + function approve( + address addressToApprove, + address token, + uint256 amount + ) internal { + if (token != ETH_ADDRESS) { + IERC20 _token = IERC20(token); + + uint allowance = _token.allowance(address(this), addressToApprove); + + if (allowance < amount) { + _token.safeApprove(addressToApprove, 0); + _token.safeIncreaseAllowance(addressToApprove, MAX_UINT); + } + } + } + + function transferTokens( + address token, + address payable destination, + uint256 amount + ) + internal + { + if (amount > 0) { + if (token == ETH_ADDRESS) { + (bool result, ) = destination.call{value: amount}(""); + require(result, "Failed to transfer Ether"); + } + else { + IERC20(token).safeTransfer(destination, amount); + } + } + + } + + function tokenBalance( + address token, + address account + ) + internal + view + returns (uint256) + { + if (token == ETH_ADDRESS) { + return account.balance; + } else { + return IERC20(token).balanceOf(account); + } + } + + /** + * @dev Helper method to refund gas using gas tokens + */ + function refundGas( + address account, + address tokenTransferProxy, + uint256 initialGas + ) + internal + { + uint256 freeBase = 14154; + uint256 freeToken = 6870; + uint256 reimburse = 24000; + + uint256 tokens = initialGas.sub( + gasleft()).add(freeBase).div(reimburse.mul(2).sub(freeToken) + ); + + freeGasTokens(account, tokenTransferProxy, tokens); + } + + /** + * @dev Helper method to free gas tokens + */ + function freeGasTokens(address account, address tokenTransferProxy, uint256 tokens) internal { + + uint256 tokensToFree = tokens; + uint256 safeNumTokens = 0; + uint256 gas = gasleft(); + + if (gas >= 27710) { + safeNumTokens = gas.sub(27710).div(1148 + 5722 + 150); + } + + if (tokensToFree > safeNumTokens) { + tokensToFree = safeNumTokens; + } + ITokenTransferProxy(tokenTransferProxy).freeReduxTokens(account, tokensToFree); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/Curve.sol b/lib_0.7/paraswap/V4/lib/curve/Curve.sol new file mode 100644 index 000000000..231a75520 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/Curve.sol @@ -0,0 +1,197 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + +import "../../AdapterStorage.sol"; + + +contract Curve is IExchange, AdapterStorage { + + struct CurveData { + int128 i; + int128 j; + uint256 deadline; + bool underlyingSwap; + bool v3; + } + + struct LocalData { + address dai; + address usdc; + address cDAI; + address cUSDC; + address curveCompoundExchange; + } + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + CurveData memory curveData = abi.decode(payload, (CurveData)); + + Utils.approve(address(exchange), address(fromToken), fromAmount); + + if (curveData.underlyingSwap) { + if (curveData.v3){ + require( + IPoolV3(exchange).underlying_coins(uint256(curveData.i)) == address(fromToken), + "Invalid from token" + ); + require( + IPoolV3(exchange).underlying_coins(uint256(curveData.j)) == address(toToken), + "Invalid to token" + ); + } + else { + require( + IPool(exchange).underlying_coins(curveData.i) == address(fromToken), + "Invalid from token" + ); + require( + IPool(exchange).underlying_coins(curveData.j) == address(toToken), + "Invalid to token" + ); + } + ICurvePool(exchange).exchange_underlying(curveData.i, curveData.j, fromAmount, toAmount); + + } + else { + if (curveData.v3) { + require( + IPoolV3(exchange).coins(uint256(curveData.i)) == address(fromToken), + "Invalid from token" + ); + require( + IPoolV3(exchange).coins(uint256(curveData.j)) == address(toToken), + "Invalid to token" + ); + } + else { + require( + IPool(exchange).coins(curveData.i) == address(fromToken), + "Invalid from token" + ); + require( + IPool(exchange).coins(curveData.j) == address(toToken), + "Invalid to token" + ); + } + if (address(fromToken) == Utils.ethAddress()) { + ICurveEthPool(exchange).exchange{value: fromAmount}(curveData.i, curveData.j, fromAmount, toAmount); + } + else { + ICurvePool(exchange).exchange(curveData.i, curveData.j, fromAmount, toAmount); + } + + } + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory lData = abi.decode(localData, (LocalData)); + + Utils.approve( + address(lData.curveCompoundExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == lData.cDAI && address(toToken) == lData.cUSDC) || (address(fromToken) == lData.cUSDC && address(toToken) == lData.cDAI) + ) + { + int128 i = address(fromToken) == lData.cDAI ? 0 : 1; + int128 j = address(toToken) == lData.cDAI ? 0 : 1; + + ICurvePool(lData.curveCompoundExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == lData.dai && address(toToken) == lData.usdc) || (address(fromToken) == lData.usdc && address(toToken) == lData.dai) + ) + { + int128 i = address(fromToken) == lData.dai ? 0 : 1; + int128 j = address(toToken) == lData.dai ? 0 : 1; + + ICurvePool(lData.curveCompoundExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVE", "1.0.0")); + } + +} + diff --git a/lib_0.7/paraswap/V4/lib/curve/Curve3Pool.sol b/lib_0.7/paraswap/V4/lib/curve/Curve3Pool.sol new file mode 100644 index 000000000..b689212db --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/Curve3Pool.sol @@ -0,0 +1,120 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract Curve3Pool is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public curve3PoolExchange; + + constructor ( + address curve3PoolExchange_, + address dai_, + address usdc_, + address usdt_ + ) + public + { + curve3PoolExchange = curve3PoolExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curve3PoolExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : 2; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : 2; + + ICurvePool(curve3PoolExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVE3POOL", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurveCompound.sol b/lib_0.7/paraswap/V4/lib/curve/CurveCompound.sol new file mode 100644 index 000000000..5698c5eab --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurveCompound.sol @@ -0,0 +1,136 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract Curve3Compound is IExchange { + + address public dai; + address public usdc; + address public cDAI; + address public cUSDC; + address public curveCompoundExchange; + + constructor ( + address curveCompoundExchange_, + address dai_, + address usdc_, + address cDAI_, + address cUSDC_ + ) + public + { + curveCompoundExchange = curveCompoundExchange_; + dai = dai_; + usdc = usdc_; + cDAI = cDAI_; + cUSDC = cUSDC_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + + Utils.approve( + address(curveCompoundExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == cDAI && address(toToken) == cUSDC) || (address(fromToken) == cUSDC && address(toToken) == cDAI) + ) + { + int128 i = address(fromToken) == cDAI ? 0 : 1; + int128 j = address(toToken) == cDAI ? 0 : 1; + + ICurvePool(curveCompoundExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == usdc && address(toToken) == dai) + ) + { + int128 i = address(fromToken) == dai ? 0 : 1; + int128 j = address(toToken) == dai ? 0 : 1; + + ICurvePool(curveCompoundExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVECOMPOUND", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurveIearn.sol b/lib_0.7/paraswap/V4/lib/curve/CurveIearn.sol new file mode 100644 index 000000000..616135372 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurveIearn.sol @@ -0,0 +1,154 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract CurveIearn is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public tusd; + address public yDAI; + address public yUSDC; + address public yUSDT; + address public yTUSD; + address public curveIearnExchange; + + constructor ( + address curveIearnExchange_, + address dai_, + address usdc_, + address usdt_, + address tusd_, + address yDAI_, + address yUSDC_, + address yUSDT_, + address yTUSD_ + ) + public + { + curveIearnExchange = curveIearnExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + tusd = tusd_; + yDAI = yDAI_; + yUSDC = yUSDC_; + yUSDT = yUSDT_; + yTUSD = yTUSD_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curveIearnExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == yDAI && address(toToken) == yUSDC) || (address(fromToken) == yDAI && address(toToken) == yUSDT) || (address(fromToken) == yDAI && address(toToken) == yTUSD) || + (address(fromToken) == yUSDC && address(toToken) == yDAI) || (address(fromToken) == yUSDC && address(toToken) == yUSDT) || (address(fromToken) == yUSDC && address(toToken) == yTUSD) || + (address(fromToken) == yUSDT && address(toToken) == yDAI) || (address(fromToken) == yUSDT && address(toToken) == yUSDC) || (address(fromToken) == yUSDT && address(toToken) == yTUSD) || + (address(fromToken) == yTUSD && address(toToken) == yDAI) || (address(fromToken) == yTUSD && address(toToken) == yUSDC) || (address(fromToken) == yTUSD && address(toToken) == yUSDT) + ) + { + int128 i = address(fromToken) == yDAI ? 0 : address(fromToken) == yUSDC ? 1 : address(fromToken) == yUSDT ? 2 : 3; + int128 j = address(toToken) == yDAI ? 0 : address(toToken) == yUSDC ? 1 : address(toToken) == yUSDT ? 2 : 3; + + ICurvePool(curveIearnExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || (address(fromToken) == dai && address(toToken) == tusd) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || (address(fromToken) == usdc && address(toToken) == tusd) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) || (address(fromToken) == usdt && address(toToken) == tusd) || + (address(fromToken) == tusd && address(toToken) == dai) || (address(fromToken) == tusd && address(toToken) == usdc) || (address(fromToken) == tusd && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : address(fromToken) == usdt ? 2 : 3; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : address(toToken) == usdt ? 2 : 3; + + ICurvePool(curveIearnExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVEIEARN", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurvePax.sol b/lib_0.7/paraswap/V4/lib/curve/CurvePax.sol new file mode 100644 index 000000000..0f32739b3 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurvePax.sol @@ -0,0 +1,151 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract CurvePax is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public pax; + address public ycDAI; + address public ycUSDC; + address public ycUSDT; + address public curvePAXExchange; + + constructor ( + address curvePAXExchange_, + address dai_, + address usdc_, + address usdt_, + address pax_, + address ycDAI_, + address ycUSDC_, + address ycUSDT_ + ) + public + { + curvePAXExchange = curvePAXExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + pax = pax_; + ycDAI = ycDAI_; + ycUSDC = ycUSDC_; + ycUSDT = ycUSDT_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curvePAXExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == ycDAI && address(toToken) == ycUSDC) || (address(fromToken) == ycDAI && address(toToken) == ycUSDT) || (address(fromToken) == ycDAI && address(toToken) == pax) || + (address(fromToken) == ycUSDC && address(toToken) == ycDAI) || (address(fromToken) == ycUSDC && address(toToken) == ycUSDT) || (address(fromToken) == ycUSDC && address(toToken) == pax) || + (address(fromToken) == ycUSDT && address(toToken) == ycDAI) || (address(fromToken) == ycUSDT && address(toToken) == ycUSDC) || (address(fromToken) == ycUSDT && address(toToken) == pax) || + (address(fromToken) == pax && address(toToken) == ycDAI) || (address(fromToken) == pax && address(toToken) == ycUSDC) || (address(fromToken) == pax && address(toToken) == ycUSDT) + ) + { + int128 i = address(fromToken) == ycDAI ? 0 : address(fromToken) == ycUSDC ? 1 : address(fromToken) == ycUSDT ? 2 : 3; + int128 j = address(toToken) == ycDAI ? 0 : address(toToken) == ycUSDC ? 1 : address(toToken) == ycUSDT ? 2 : 3; + + ICurvePool(curvePAXExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || (address(fromToken) == dai && address(toToken) == pax) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || (address(fromToken) == usdc && address(toToken) == pax) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) || (address(fromToken) == usdt && address(toToken) == pax) || + (address(fromToken) == pax && address(toToken) == dai) || (address(fromToken) == pax && address(toToken) == usdc) || (address(fromToken) == pax && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : address(fromToken) == usdt ? 2 : 3; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : address(toToken) == usdt ? 2 : 3; + + ICurvePool(curvePAXExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVEPAX", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurvePoolMock.sol b/lib_0.7/paraswap/V4/lib/curve/CurvePoolMock.sol new file mode 100644 index 000000000..64157f6c4 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurvePoolMock.sol @@ -0,0 +1,6 @@ +pragma solidity 0.7.5; + +contract CurvePoolMock { + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external {} + function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external payable {} +} \ No newline at end of file diff --git a/lib_0.7/paraswap/V4/lib/curve/CurveSynthetix.sol b/lib_0.7/paraswap/V4/lib/curve/CurveSynthetix.sol new file mode 100644 index 000000000..b22b53ca3 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurveSynthetix.sol @@ -0,0 +1,124 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract CurveSynthetix is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public susd; + address public curveSynthetixExchange; + + constructor ( + address curveSynthetixExchange_, + address dai_, + address usdc_, + address usdt_, + address susd_ + ) + public + { + curveSynthetixExchange = curveSynthetixExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + susd = susd_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curveSynthetixExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || (address(fromToken) == dai && address(toToken) == susd) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || (address(fromToken) == usdc && address(toToken) == susd) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) || (address(fromToken) == usdt && address(toToken) == susd) || + (address(fromToken) == susd && address(toToken) == dai) || (address(fromToken) == susd && address(toToken) == usdc) || (address(fromToken) == susd && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : address(fromToken) == usdt ? 2 : 3; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : address(toToken) == usdt ? 2 : 3; + + ICurvePool(curveSynthetixExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVESYNTHETIX", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurveUSDT.sol b/lib_0.7/paraswap/V4/lib/curve/CurveUSDT.sol new file mode 100644 index 000000000..ea8fcfa16 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurveUSDT.sol @@ -0,0 +1,145 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract CurveUSDT is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public cDAI; + address public cUSDC; + address public cUSDT; + address public curveUSDTExchange; + + constructor ( + address curveUSDTExchange_, + address dai_, + address usdc_, + address usdt_, + address cDAI_, + address cUSDC_, + address cUSDT_ + ) + public + { + curveUSDTExchange = curveUSDTExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + cDAI = cDAI_; + cUSDC = cUSDC_; + cUSDT = cUSDT_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curveUSDTExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == cDAI && address(toToken) == cUSDC) || (address(fromToken) == cDAI && address(toToken) == cUSDT) || + (address(fromToken) == cUSDC && address(toToken) == cDAI) || (address(fromToken) == cUSDC && address(toToken) == cUSDT) || + (address(fromToken) == cUSDT && address(toToken) == cDAI) || (address(fromToken) == cUSDT && address(toToken) == cUSDC) + ) + { + int128 i = address(fromToken) == cDAI ? 0 : address(fromToken) == cUSDC ? 1 : 2; + int128 j = address(toToken) == cDAI ? 0 : address(toToken) == cUSDC ? 1 : 2; + + ICurvePool(curveUSDTExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : 2; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : 2; + + ICurvePool(curveUSDTExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVEUSDT", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/CurveiEarnUSDB.sol b/lib_0.7/paraswap/V4/lib/curve/CurveiEarnUSDB.sol new file mode 100644 index 000000000..8cdbf6c90 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/CurveiEarnUSDB.sol @@ -0,0 +1,154 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./ICurve.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + + + +contract CurveiEARNUSDB is IExchange { + + address public dai; + address public usdc; + address public usdt; + address public busd; + address public yDAIv3; + address public yUSDCv3; + address public yUSDTv3; + address public yBUSD; + address public curveiEarnUSDBExchange; + + constructor ( + address curveiEarnUSDBExchange_, + address dai_, + address usdc_, + address usdt_, + address busd_, + address yDAIv3_, + address yUSDCv3_, + address yUSDTv3_, + address yBUSD_ + ) + public + { + curveiEarnUSDBExchange = curveiEarnUSDBExchange_; + dai = dai_; + usdc = usdc_; + usdt = usdt_; + busd = busd_; + yDAIv3 = yDAIv3_; + yUSDCv3 = yUSDCv3_; + yUSDTv3 = yUSDTv3_; + yBUSD = yBUSD_; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + revert("METHOD NOT IMPLEMENTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + + } + + //Swap on Curve Compound + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + Utils.approve( + address(curveiEarnUSDBExchange), + address(fromToken), fromAmount + ); + if ( + (address(fromToken) == yDAIv3 && address(toToken) == yUSDCv3) || (address(fromToken) == yDAIv3 && address(toToken) == yUSDTv3) || (address(fromToken) == yDAIv3 && address(toToken) == yBUSD) || + (address(fromToken) == yUSDCv3 && address(toToken) == yDAIv3) || (address(fromToken) == yUSDCv3 && address(toToken) == yUSDTv3) || (address(fromToken) == yUSDCv3 && address(toToken) == yBUSD) || + (address(fromToken) == yUSDTv3 && address(toToken) == yDAIv3) || (address(fromToken) == yUSDTv3 && address(toToken) == yUSDCv3) || (address(fromToken) == yUSDTv3 && address(toToken) == yBUSD) || + (address(fromToken) == yBUSD && address(toToken) == yDAIv3) || (address(fromToken) == yBUSD && address(toToken) == yUSDCv3) || (address(fromToken) == yBUSD && address(toToken) == yUSDTv3) + ) + { + int128 i = address(fromToken) == yDAIv3 ? 0 : address(fromToken) == yUSDCv3 ? 1 : address(fromToken) == yUSDTv3 ? 2 : 3; + int128 j = address(toToken) == yDAIv3 ? 0 : address(toToken) == yUSDCv3 ? 1 : address(toToken) == yUSDTv3 ? 2 : 3; + + ICurvePool(curveiEarnUSDBExchange).exchange( + i, + j, + fromAmount, + 1 + ); + } + else if ( + (address(fromToken) == dai && address(toToken) == usdc) || (address(fromToken) == dai && address(toToken) == usdt) || (address(fromToken) == dai && address(toToken) == busd) || + (address(fromToken) == usdc && address(toToken) == dai) || (address(fromToken) == usdc && address(toToken) == usdt) || (address(fromToken) == usdc && address(toToken) == busd) || + (address(fromToken) == usdt && address(toToken) == dai) || (address(fromToken) == usdt && address(toToken) == usdc) || (address(fromToken) == usdt && address(toToken) == busd) || + (address(fromToken) == busd && address(toToken) == dai) || (address(fromToken) == busd && address(toToken) == usdc) || (address(fromToken) == busd && address(toToken) == usdc) + ) + { + int128 i = address(fromToken) == dai ? 0 : address(fromToken) == usdc ? 1 : address(fromToken) == usdt ? 2 : 3; + int128 j = address(toToken) == dai ? 0 : address(toToken) == usdc ? 1 : address(toToken) == usdt ? 2 : 3; + + ICurvePool(curveiEarnUSDBExchange).exchange_underlying( + i, + j, + fromAmount, + 1 + ); + } + else { + revert("TOKEN NOT SUPPORTED"); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("CURVEIEARNUSDB", "1.0.0")); + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } +} diff --git a/lib_0.7/paraswap/V4/lib/curve/ICurve.sol b/lib_0.7/paraswap/V4/lib/curve/ICurve.sol new file mode 100644 index 000000000..e0c4c866f --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/curve/ICurve.sol @@ -0,0 +1,33 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +interface IPool { + function underlying_coins(int128 index) external view returns (address); + + function coins(int128 index) external view returns (address); +} + +interface IPoolV3 { + function underlying_coins(uint256 index) external view returns(address); + + function coins(uint256 index) external view returns(address); +} + +interface ICurvePool { + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy) external; + + function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external; + +} + +interface ICurveEthPool { + + function exchange(int128 i, int128 j, uint256 dx, uint256 minDy) external payable; +} + +interface ICompoundPool { + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 minDy, uint256 deadline) external; + + function exchange(int128 i, int128 j, uint256 dx, uint256 minDy, uint256 deadline) external; +} diff --git a/lib_0.7/paraswap/V4/lib/kyber/IKyberHint.sol b/lib_0.7/paraswap/V4/lib/kyber/IKyberHint.sol new file mode 100644 index 000000000..40afb6ddc --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/kyber/IKyberHint.sol @@ -0,0 +1,35 @@ +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + + +interface IKyberHint { + + enum TradeType {BestOfAll, MaskIn, MaskOut, Split} + + function buildTokenToEthHint( + IERC20 tokenSrc, + TradeType tokenToEthType, + bytes32[] calldata tokenToEthReserveIds, + uint256[] calldata tokenToEthSplits + ) external view returns (bytes memory hint); + + function buildEthToTokenHint( + IERC20 tokenDest, + TradeType ethToTokenType, + bytes32[] calldata ethToTokenReserveIds, + uint256[] calldata ethToTokenSplits + ) external view returns (bytes memory hint); + + function buildTokenToTokenHint( + IERC20 tokenSrc, + TradeType tokenToEthType, + bytes32[] calldata tokenToEthReserveIds, + uint256[] calldata tokenToEthSplits, + IERC20 tokenDest, + TradeType ethToTokenType, + bytes32[] calldata ethToTokenReserveIds, + uint256[] calldata ethToTokenSplits + ) external view returns (bytes memory hint); + +} diff --git a/lib_0.7/paraswap/V4/lib/kyber/IKyberNetwork.sol b/lib_0.7/paraswap/V4/lib/kyber/IKyberNetwork.sol new file mode 100644 index 000000000..308e664f8 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/kyber/IKyberNetwork.sol @@ -0,0 +1,17 @@ +pragma solidity 0.7.5; + +interface IKyberNetwork { + function maxGasPrice() external view returns(uint); + + function tradeWithHintAndFee( + address src, + uint256 srcAmount, + address dest, + address payable destAddress, + uint256 maxDestAmount, + uint256 minConversionRate, + address payable platformWallet, + uint256 platformFeeBps, + bytes calldata hint + ) external payable returns (uint256 destAmount); +} diff --git a/lib_0.7/paraswap/V4/lib/kyber/Kyber.sol b/lib_0.7/paraswap/V4/lib/kyber/Kyber.sol new file mode 100644 index 000000000..8c83c15a1 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/kyber/Kyber.sol @@ -0,0 +1,216 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./IKyberNetwork.sol"; +import "../IExchange.sol"; +import "../Utils.sol"; + +import "./IKyberHint.sol"; +import "../../AdapterStorage.sol"; + + +contract Kyber is IExchange, AdapterStorage { + + struct KyberData { + uint256 minConversionRateForBuy; + bytes hint; + } + + struct LocalData { + address payable feeWallet; + uint256 platformFeeBps; + address kyberProxy; + address kyberHint; + bytes32[] brigedReserves; + } + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function maxGasPrice(address kyberAddress) external view returns (uint) { + return IKyberNetwork(kyberAddress).maxGasPrice(); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes calldata payload + ) + external + payable + override + + { + KyberData memory data = abi.decode(payload, (KyberData)); + LocalData memory lData = abi.decode(adapterVsData[getKey()], (LocalData)); + + _swap( + address(fromToken), + address(toToken), + fromAmount, + toAmount, + kyberAddress, + data.hint, + lData.feeWallet, + lData.platformFeeBps + ); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes calldata payload + ) + external + payable + override + + { + KyberData memory data = abi.decode(payload, (KyberData)); + + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory lData = abi.decode(localData, (LocalData)); + + Utils.approve(address(kyberAddress), address(fromToken), fromAmount); + + if (address(fromToken) == Utils.ethAddress()) { + IKyberNetwork(kyberAddress).tradeWithHintAndFee{value: fromAmount}( + address(fromToken), + fromAmount, + address(toToken), + payable(address(this)), + toAmount, + data.minConversionRateForBuy, + lData.feeWallet, + lData.platformFeeBps, + data.hint + ); + } + else { + IKyberNetwork(kyberAddress).tradeWithHintAndFee( + address(fromToken), + fromAmount, + address(toToken), + payable(address(this)), + toAmount, + data.minConversionRateForBuy, + lData.feeWallet, + lData.platformFeeBps, + data.hint + ); + } + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + bytes memory hint; + uint256[] memory emptyArray = new uint256[](0); + + LocalData memory lData = abi.decode(adapterVsData[getKey()], (LocalData)); + + + if (address(fromToken) == Utils.ethAddress()) { + hint = IKyberHint(lData.kyberHint).buildEthToTokenHint(toToken, IKyberHint.TradeType.MaskOut, lData.brigedReserves, emptyArray); + } + else if (address(toToken) == Utils.ethAddress()) { + hint = IKyberHint(lData.kyberHint).buildTokenToEthHint(fromToken, IKyberHint.TradeType.MaskOut, lData.brigedReserves, emptyArray); + } + else { + hint = IKyberHint(lData.kyberHint).buildTokenToTokenHint( + fromToken, + IKyberHint.TradeType.MaskOut, + lData.brigedReserves, + emptyArray, + toToken, + IKyberHint.TradeType.MaskOut, + lData.brigedReserves, + emptyArray + ); + } + + return _swap( + address(fromToken), + address(toToken), + fromAmount, + toAmount, + lData.kyberProxy, + hint, + lData.feeWallet, + lData.platformFeeBps + ); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("KYBER", "1.0.0")); + } + + function _swap( + address fromToken, + address toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes memory hint, + address payable feeWallet, + uint256 platformFeeBps + + ) + private + returns(uint256) + { + Utils.approve(kyberAddress, fromToken, fromAmount); + + uint256 receivedAmount = 0; + + if (fromToken == Utils.ethAddress()) { + receivedAmount = IKyberNetwork(kyberAddress).tradeWithHintAndFee{value: fromAmount}( + fromToken, + fromAmount, + toToken, + payable(address(this)), + Utils.maxUint(), + toAmount, + feeWallet, + platformFeeBps, + hint + ); + } + else { + receivedAmount = IKyberNetwork(kyberAddress).tradeWithHintAndFee( + fromToken, + fromAmount, + toToken, + payable(address(this)), + Utils.maxUint(), + toAmount, + feeWallet, + platformFeeBps, + hint + ); + } + return receivedAmount; + } +} diff --git a/lib_0.7/paraswap/V4/lib/kyber/KyberOnchain.sol b/lib_0.7/paraswap/V4/lib/kyber/KyberOnchain.sol new file mode 100644 index 000000000..183ddc04f --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/kyber/KyberOnchain.sol @@ -0,0 +1,196 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/access/Ownable.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "./IKyberNetwork.sol"; +import "../IExchange.sol"; +import "../Utils.sol"; + +import "./IKyberHint.sol"; + + +contract KyberOnchain is IExchange, Ownable { + + struct KyberData { + uint256 minConversionRateForBuy; + bytes hint; + } + + address payable public feeWallet; + uint256 public platformFeeBps; + address public kyberProxy; + address public kyberHint; + bytes32[] private brigedReserves; + + constructor( + address payable _feeWallet, + uint256 _platformFeeBps, + address _kyberProxy, + address _kyberHint + ) + public + { + feeWallet = _feeWallet; + platformFeeBps = _platformFeeBps; + kyberProxy = _kyberProxy; + kyberHint = _kyberHint; + brigedReserves.push(bytes32(0xbb4f617369730000000000000000000000000000000000000000000000000000));//OASIS + brigedReserves.push(bytes32(0xbb756e6973776170563100000000000000000000000000000000000000000000));//UNISWAP + brigedReserves.push(bytes32(0xbb756e6973776170563200000000000000000000000000000000000000000000));//UNISWAPV2 + brigedReserves.push(bytes32(0xbb42414e434f5230305632000000000000000000000000000000000000000000));//BANCOR + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + function setFeeWallet(address payable _feeWallet) external onlyOwner { + feeWallet = _feeWallet; + } + + function setPlatformFeeBps(uint256 _platformFeeBps) external onlyOwner { + platformFeeBps = _platformFeeBps; + } + + + function initialize(bytes calldata data) external override { + revert("METHOD NOT SUPPORTED"); + } + + function maxGasPrice(address kyberAddress) external view returns (uint) { + return IKyberNetwork(kyberAddress).maxGasPrice(); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + bytes memory hint; + uint256[] memory emptyArray = new uint256[](0); + + + if (address(fromToken) == Utils.ethAddress()) { + hint = IKyberHint(kyberHint).buildEthToTokenHint(toToken, IKyberHint.TradeType.MaskOut, brigedReserves, emptyArray); + } + else if (address(toToken) == Utils.ethAddress()) { + hint = IKyberHint(kyberHint).buildTokenToEthHint(fromToken, IKyberHint.TradeType.MaskOut, brigedReserves, emptyArray); + } + else { + hint = IKyberHint(kyberHint).buildTokenToTokenHint( + fromToken, + IKyberHint.TradeType.MaskOut, + brigedReserves, + emptyArray, + toToken, + IKyberHint.TradeType.MaskOut, + brigedReserves, + emptyArray + ); + } + + return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + kyberProxy, + hint + ); + } + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address kyberAddress, + bytes memory hint + + ) + private + returns(uint256) + { + Utils.approve(address(kyberAddress), address(fromToken), fromAmount); + + uint256 receivedAmount = 0; + + if (address(fromToken) == Utils.ethAddress()) { + receivedAmount = IKyberNetwork(kyberAddress).tradeWithHintAndFee{value: fromAmount}( + address(fromToken), + fromAmount, + address(toToken), + address(this), + Utils.maxUint(), + toAmount, + feeWallet, + platformFeeBps, + hint + ); + } + else { + receivedAmount = IKyberNetwork(kyberAddress).tradeWithHintAndFee( + address(fromToken), + fromAmount, + address(toToken), + address(this), + Utils.maxUint(), + toAmount, + feeWallet, + platformFeeBps, + hint + ); + } + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("KYBERONCHAIN", "1.0.0")); + } + +} diff --git a/lib/paraswap/lib/libraries/LibBytes.sol b/lib_0.7/paraswap/V4/lib/libraries/LibBytes.sol similarity index 96% rename from lib/paraswap/lib/libraries/LibBytes.sol rename to lib_0.7/paraswap/V4/lib/libraries/LibBytes.sol index b1f20f3f9..e50eebe49 100644 --- a/lib/paraswap/lib/libraries/LibBytes.sol +++ b/lib_0.7/paraswap/V4/lib/libraries/LibBytes.sol @@ -11,7 +11,7 @@ limitations under the License. */ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "./LibBytesRichErrors.sol"; import "./LibRichErrors.sol"; @@ -24,7 +24,7 @@ library LibBytes { /// @dev Reads an address from a position in a byte array. /// @param b Byte array containing an address. /// @param index Index in byte array of address. - /// @return address from byte array. + /// @return result address from byte array. function readAddress( bytes memory b, uint256 index @@ -56,4 +56,4 @@ library LibBytes { return result; } -} \ No newline at end of file +} diff --git a/lib/paraswap/lib/libraries/LibBytesRichErrors.sol b/lib_0.7/paraswap/V4/lib/libraries/LibBytesRichErrors.sol similarity index 98% rename from lib/paraswap/lib/libraries/LibBytesRichErrors.sol rename to lib_0.7/paraswap/V4/lib/libraries/LibBytesRichErrors.sol index 1ddea1084..82d2e280e 100644 --- a/lib/paraswap/lib/libraries/LibBytesRichErrors.sol +++ b/lib_0.7/paraswap/V4/lib/libraries/LibBytesRichErrors.sol @@ -11,7 +11,7 @@ limitations under the License. */ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; library LibBytesRichErrors { @@ -48,4 +48,4 @@ library LibBytesRichErrors { required ); } -} \ No newline at end of file +} diff --git a/lib/paraswap/lib/libraries/LibRichErrors.sol b/lib_0.7/paraswap/V4/lib/libraries/LibRichErrors.sol similarity index 98% rename from lib/paraswap/lib/libraries/LibRichErrors.sol rename to lib_0.7/paraswap/V4/lib/libraries/LibRichErrors.sol index 28a69e88b..8bb085e6f 100644 --- a/lib/paraswap/lib/libraries/LibRichErrors.sol +++ b/lib_0.7/paraswap/V4/lib/libraries/LibRichErrors.sol @@ -11,7 +11,7 @@ limitations under the License. */ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; library LibRichErrors { @@ -49,4 +49,4 @@ library LibRichErrors { revert(add(errorData, 0x20), mload(errorData)) } } -} \ No newline at end of file +} diff --git a/lib/paraswap/lib/uniswap/IUniswapExchange.sol b/lib_0.7/paraswap/V4/lib/uniswap/IUniswapExchange.sol similarity index 98% rename from lib/paraswap/lib/uniswap/IUniswapExchange.sol rename to lib_0.7/paraswap/V4/lib/uniswap/IUniswapExchange.sol index 5a839bfbd..f833d7ad0 100644 --- a/lib/paraswap/lib/uniswap/IUniswapExchange.sol +++ b/lib_0.7/paraswap/V4/lib/uniswap/IUniswapExchange.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; interface IUniswapExchange { function ethToTokenSwapInput( diff --git a/lib/paraswap/lib/uniswap/IUniswapFactory.sol b/lib_0.7/paraswap/V4/lib/uniswap/IUniswapFactory.sol similarity index 85% rename from lib/paraswap/lib/uniswap/IUniswapFactory.sol rename to lib_0.7/paraswap/V4/lib/uniswap/IUniswapFactory.sol index 92ea1175b..625b4c563 100644 --- a/lib/paraswap/lib/uniswap/IUniswapFactory.sol +++ b/lib_0.7/paraswap/V4/lib/uniswap/IUniswapFactory.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "./IUniswapExchange.sol"; diff --git a/lib_0.7/paraswap/V4/lib/uniswap/Uniswap.sol b/lib_0.7/paraswap/V4/lib/uniswap/Uniswap.sol new file mode 100644 index 000000000..f1ed7bd22 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswap/Uniswap.sol @@ -0,0 +1,164 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../IExchange.sol"; +import "../Utils.sol"; +import "./IUniswapExchange.sol"; +import "./IUniswapFactory.sol"; + +import "../../AdapterStorage.sol"; + + +contract Uniswap is IExchange, AdapterStorage { + using SafeMath for uint256; + + struct LocalData { + address factory; + } + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address factoryAddress, + bytes calldata payload + ) + external + payable + override + + { + + _swap( + factoryAddress, + fromToken, + toToken, + fromAmount, + toAmount + ); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address factoryAddress, + bytes calldata payload + ) + external + payable + override + + { + + address exchange = getExchange(fromToken, toToken, factoryAddress); + + Utils.approve(address(exchange), address(fromToken), fromAmount); + + if (address(fromToken) == Utils.ethAddress()) { + IUniswapExchange(exchange).ethToTokenSwapOutput{value: fromAmount}(toAmount, block.timestamp); + } + else if (address(toToken) == Utils.ethAddress()) { + IUniswapExchange(exchange).tokenToEthSwapOutput(toAmount, fromAmount, block.timestamp); + } + else { + IUniswapExchange(exchange).tokenToTokenSwapOutput( + toAmount, + fromAmount, + Utils.maxUint(), + block.timestamp, + address(toToken) + ); + } + + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory lData = abi.decode(localData, (LocalData)); + + return _swap( + lData.factory, + fromToken, + toToken, + fromAmount, + toAmount + ); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UNISWAP", "1.0.0")); + } + + function getExchange( + IERC20 fromToken, + IERC20 toToken, + address factoryAddress + ) + private + view + returns (address) + { + address exchangeAddress = address(fromToken) == Utils.ethAddress() ? address(toToken) : address(fromToken); + + return IUniswapFactory(factoryAddress).getExchange(exchangeAddress); + } + + function _swap( + address factoryAddress, + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + private + returns(uint256) + { + address exchange = getExchange(fromToken, toToken, factoryAddress); + + Utils.approve( + exchange, + address(fromToken), + fromAmount + ); + + uint256 receivedAmount = 0; + + if (address(fromToken) == Utils.ethAddress()) { + receivedAmount = IUniswapExchange(exchange).ethToTokenSwapInput{value: fromAmount}(toAmount, block.timestamp); + } + else if (address(toToken) == Utils.ethAddress()) { + receivedAmount = IUniswapExchange(exchange).tokenToEthSwapInput(fromAmount, toAmount, block.timestamp); + } + else { + receivedAmount = IUniswapExchange(exchange).tokenToTokenSwapInput(fromAmount, toAmount, 1, block.timestamp, address(toToken)); + } + + return receivedAmount; + } + +} + diff --git a/lib_0.7/paraswap/V4/lib/uniswap/UniswapOnchain.sol b/lib_0.7/paraswap/V4/lib/uniswap/UniswapOnchain.sol new file mode 100644 index 000000000..3f1cf4b45 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswap/UniswapOnchain.sol @@ -0,0 +1,142 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../IExchange.sol"; +import "../Utils.sol"; +import "./IUniswapExchange.sol"; +import "./IUniswapFactory.sol"; + +import "../../AdapterStorage.sol"; + + +contract UniswapOnchain is IExchange, AdapterStorage { + using SafeMath for uint256; + + address public factory; + + constructor(address _factory) public { + factory = _factory; + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + + function initialize(bytes calldata data) external override { + revert("METHOD NOT SUPPORTED"); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address factoryAddress, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address factoryAddress, + bytes calldata payload + ) + external + payable + override + + { + revert("METHOD NOT SUPPORTED"); + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + bytes32 key = getKey(); + + return _swap( + factory, + fromToken, + toToken, + fromAmount, + toAmount + ); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UNISWAPONCHAIN", "1.0.0")); + } + + function getExchange( + IERC20 fromToken, + IERC20 toToken, + address factoryAddress + ) + private + view + returns (address) + { + address exchangeAddress = address(fromToken) == Utils.ethAddress() ? address(toToken) : address(fromToken); + + return IUniswapFactory(factoryAddress).getExchange(exchangeAddress); + } + + function _swap( + address factoryAddress, + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + private + returns(uint256) + { + address exchange = getExchange(fromToken, toToken, factoryAddress); + + Utils.approve( + exchange, + address(fromToken), + fromAmount + ); + + uint256 receivedAmount = 0; + + if (address(fromToken) == Utils.ethAddress()) { + receivedAmount = IUniswapExchange(exchange).ethToTokenSwapInput{value: fromAmount}(toAmount, block.timestamp); + } + else if (address(toToken) == Utils.ethAddress()) { + receivedAmount = IUniswapExchange(exchange).tokenToEthSwapInput(fromAmount, toAmount, block.timestamp); + } + else { + receivedAmount = IUniswapExchange(exchange).tokenToTokenSwapInput(fromAmount, toAmount, 1, block.timestamp, address(toToken)); + } + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } + +} + diff --git a/lib/paraswap/lib/uniswapv2/IUniswapRouter.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapRouter.sol similarity index 98% rename from lib/paraswap/lib/uniswapv2/IUniswapRouter.sol rename to lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapRouter.sol index b7a2c403d..dd174e0e8 100644 --- a/lib/paraswap/lib/uniswapv2/IUniswapRouter.sol +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapRouter.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; interface IUniswapRouter { diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapV3Router.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapV3Router.sol new file mode 100644 index 000000000..42bb66269 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/IUniswapV3Router.sol @@ -0,0 +1,18 @@ +pragma solidity 0.7.5; + + +interface IUniswapV3Router { + + function swap( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path + ) external payable returns (uint256 tokensBought); + + function buy( + uint256 amountInMax, + uint256 amountOut, + address[] calldata path + ) external payable returns (uint256 tokensSold); + +} diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2.sol new file mode 100644 index 000000000..d17bbd05d --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2.sol @@ -0,0 +1,261 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +import '../UniswapV3Lib.sol'; + +import "../IExchange.sol"; +import "../Utils.sol"; + +import "../../AdapterStorage.sol"; +import '../../IWETH.sol'; + + +contract UniswapV2 is IExchange, AdapterStorage { + using SafeMath for uint256; + + struct UniswapV2Data { + address[] path; + } + + struct LocalData { + address uinswapV2Router; + address factory; + bytes32 initCode; + } + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); + _swap( + fromAmount, + data.path + ); + + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); + + _buy( + fromAmount, + toAmount, + data.path + ); + } + + + + //PATH Token -> Token + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256 receivedAmount) + { + + address[] memory path = new address[](2); + + path[0] = address(fromToken) == Utils.ethAddress() ? Utils.wethAddress() : address(fromToken); + path[1] = address(toToken) == Utils.ethAddress() ? Utils.wethAddress() : address(toToken); + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + + //TODO removed token transfer to msg.sender for delegatecall. Fix this onchain swap + /**return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + data.uinswapV2Router, + path + );*/ + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UniswapV2", "1.0.0")); + } + + function _buy( + uint256 amountInMax, + uint256 amountOut, + address[] memory path + ) + private + returns (uint256 tokensSold) + { + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + + require(path.length > 1, "More than 1 token required"); + bool tokensBoughtEth; + uint8 length = uint8(path.length); + + uint256[] memory amounts = new uint256[](length); + address[] memory pairs = new address[](length - 1); + + amounts[length - 1] = amountOut; + + for (uint8 i = length - 1; i > 0; i--) { + (amounts[i - 1], pairs[i - 1]) = UniswapV3Lib.getAmountInAndPair( + data.factory, + amounts[i], + path[i-1], + path[i], + data.initCode + ); + } + + tokensSold = amounts[0]; + require(tokensSold <= amountInMax, "UniswapV3Router: INSUFFICIENT_INPUT_AMOUNT"); + + for(uint8 i = 0; i < length - 1; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + if (i == length - 2) { + if (tokenBought == Utils.ethAddress()) { + tokenBought = Utils.wethAddress(); + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == Utils.ethAddress()) { + tokenSold = Utils.wethAddress(); + IWETH(Utils.wethAddress()).deposit{value: tokensSold}(); + assert(IWETH(Utils.wethAddress()).transfer(pairs[i], tokensSold)); + } + else { + TransferHelper.safeTransferFrom( + tokenSold, msg.sender, pairs[i], tokensSold + ); + } + } + + address receiver; + + if (i == length - 2) { + + receiver = address(this); + + } + else { + receiver = pairs[i+1]; + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), amounts[i+1]) : (amounts[i+1], uint256(0)); + IUniswapV2Pair(pairs[i]).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + if (tokensBoughtEth) { + IWETH(Utils.wethAddress()).withdraw(amountOut); + } + } + + function _swap( + uint256 fromAmount, + address[] memory path + ) + private + returns(uint256 tokensBought) + { + require(path.length > 1, "More than 1 token required"); + uint8 pairs = uint8(path.length - 1); + bool tokensBoughtEth; + tokensBought = fromAmount; + address receiver; + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + if (i == pairs - 1) { + if (tokenBought == Utils.ethAddress()) { + tokenBought = Utils.wethAddress(); + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == Utils.ethAddress()) { + tokenSold = Utils.wethAddress(); + currentPair = UniswapV3Lib.pairFor(data.factory, tokenSold, tokenBought, data.initCode); + IWETH(Utils.wethAddress()).deposit{value: fromAmount}(); + assert(IWETH(Utils.wethAddress()).transfer(currentPair, fromAmount)); + } + else { + currentPair = UniswapV3Lib.pairFor(data.factory, tokenSold, tokenBought, data.initCode); + TransferHelper.safeTransfer( + tokenSold, currentPair, fromAmount + ); + } + } + + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + receiver = address(this); + } + else { + receiver = UniswapV3Lib.pairFor(data.factory, tokenBought, path[i+2] == Utils.ethAddress() ? Utils.wethAddress() : path[i+2], data.initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + + if (tokensBoughtEth) { + IWETH(Utils.wethAddress()).withdraw(tokensBought); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2DaiMid.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2DaiMid.sol new file mode 100644 index 000000000..9090a6f6d --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2DaiMid.sol @@ -0,0 +1,186 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../IExchange.sol"; +import "../Utils.sol"; +import "./IUniswapRouter.sol"; + + + +contract UniswapV2DaiMid is IExchange { + using SafeMath for uint256; + + struct UniswapV2Data { + address[] path; + } + + address public weth; + + address public dai; + + address public uinswapV2Router; + + constructor(address _weth, address uinswapV2Router_, address dai_) public { + weth = _weth; + uinswapV2Router = uinswapV2Router_; + dai = dai_; + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UNISWAPV2DAIMID", "1.0.0")); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + } + + //PATH Token -> WETH -> Token + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + + address[] memory path = new address[](3); + path[0] = address(fromToken) == Utils.ethAddress() ? weth : address(fromToken); + path[1] = dai; + path[2] = address(toToken) == Utils.ethAddress() ? weth : address(toToken); + + return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + uinswapV2Router, + path + ); + + + } + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + address[] memory path + ) + private + returns(uint256) + { + Utils.approve(address(exchange), address(fromToken), fromAmount); + + if (address(fromToken) == Utils.ethAddress()) { + require( + path[0] == weth, + "First element in path must be WETH" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + + IUniswapRouter(exchange).swapExactETHForTokens{value: fromAmount}( + toAmount, + path, + address(this), + block.timestamp + ); + } + else if (address(toToken) == Utils.ethAddress()) { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == weth, + "last element in path must be weth" + ); + IUniswapRouter(exchange).swapExactTokensForETH( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + else { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + IUniswapRouter(exchange).swapExactTokensForTokens( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } +} + diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2Mock.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2Mock.sol new file mode 100644 index 000000000..3576351d1 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2Mock.sol @@ -0,0 +1,271 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "@uniswap/lib/contracts/libraries/TransferHelper.sol"; + +import "../UniswapV3Lib.sol"; + +import "../IExchange.sol"; +import "../Utils.sol"; + +import "../../AdapterStorage.sol"; +import "../../IWETH.sol"; + + +contract UniswapV2Mock is IExchange, AdapterStorage { + using SafeMath for uint256; + + struct UniswapV2Data { + address[] path; + } + + struct LocalData { + address uinswapV2Router; + address factory; + bytes32 initCode; + } + + ////// Argent addition ///////////////// + address public immutable weth; + bytes32 public immutable exchangeName; + function wethAddress() internal view returns (address) { return weth; } + constructor(address _weth, bytes32 _exchangeName) { + weth = _weth; + exchangeName = _exchangeName; + } + //////////////////////////////////////// + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); + _swap( + fromAmount, + data.path + ); + + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + UniswapV2Data memory data = abi.decode(payload, (UniswapV2Data)); + + _buy( + fromAmount, + toAmount, + data.path + ); + } + + + + //PATH Token -> Token + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256 receivedAmount) + { + + address[] memory path = new address[](2); + + path[0] = address(fromToken) == Utils.ethAddress() ? wethAddress() : address(fromToken); + path[1] = address(toToken) == Utils.ethAddress() ? wethAddress() : address(toToken); + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + + //TODO removed token transfer to msg.sender for delegatecall. Fix this onchain swap + /**return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + data.uinswapV2Router, + path + );*/ + } + + function getKey() public override view returns(bytes32) { + return keccak256(abi.encodePacked(exchangeName, "1.0.0")); + } + + function _buy( + uint256 amountInMax, + uint256 amountOut, + address[] memory path + ) + private + returns (uint256 tokensSold) + { + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + + require(path.length > 1, "More than 1 token required"); + bool tokensBoughtEth; + uint8 length = uint8(path.length); + + uint256[] memory amounts = new uint256[](length); + address[] memory pairs = new address[](length - 1); + + amounts[length - 1] = amountOut; + + for (uint8 i = length - 1; i > 0; i--) { + (amounts[i - 1], pairs[i - 1]) = UniswapV3Lib.getAmountInAndPair( + data.factory, + amounts[i], + path[i-1], + path[i], + data.initCode + ); + } + + tokensSold = amounts[0]; + require(tokensSold <= amountInMax, "UniswapV3Router: INSUFFICIENT_INPUT_AMOUNT"); + + for(uint8 i = 0; i < length - 1; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + if (i == length - 2) { + if (tokenBought == Utils.ethAddress()) { + tokenBought = wethAddress(); + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == Utils.ethAddress()) { + tokenSold = wethAddress(); + IWETH(wethAddress()).deposit{value: tokensSold}(); + assert(IWETH(wethAddress()).transfer(pairs[i], tokensSold)); + } + else { + TransferHelper.safeTransferFrom( + tokenSold, msg.sender, pairs[i], tokensSold + ); + } + } + + address receiver; + + if (i == length - 2) { + + receiver = address(this); + + } + else { + receiver = pairs[i+1]; + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), amounts[i+1]) : (amounts[i+1], uint256(0)); + IUniswapV2Pair(pairs[i]).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + if (tokensBoughtEth) { + IWETH(wethAddress()).withdraw(amountOut); + } + } + + function _swap( + uint256 fromAmount, + address[] memory path + ) + private + returns(uint256 tokensBought) + { + require(path.length > 1, "More than 1 token required"); + uint8 pairs = uint8(path.length - 1); + bool tokensBoughtEth; + tokensBought = fromAmount; + address receiver; + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory data = abi.decode(localData, (LocalData)); + // + for(uint8 i = 0; i < pairs; i++) { + address tokenSold = path[i]; + address tokenBought = path[i+1]; + + address currentPair = receiver; + if (i == pairs - 1) { + if (tokenBought == Utils.ethAddress()) { + tokenBought = wethAddress(); + tokensBoughtEth = true; + } + } + if (i == 0) { + if (tokenSold == Utils.ethAddress()) { + tokenSold = wethAddress(); + currentPair = UniswapV3Lib.pairFor(data.factory, tokenSold, tokenBought, data.initCode); + IWETH(wethAddress()).deposit{value: fromAmount}(); + assert(IWETH(wethAddress()).transfer(currentPair, fromAmount)); + } + else { + currentPair = UniswapV3Lib.pairFor(data.factory, tokenSold, tokenBought, data.initCode); + TransferHelper.safeTransfer( + tokenSold, currentPair, fromAmount + ); + } + } + + tokensBought = UniswapV3Lib.getAmountOutByPair(tokensBought, currentPair, tokenSold, tokenBought); + + if ((i + 1) == pairs) { + receiver = address(this); + } + else { + receiver = UniswapV3Lib.pairFor(data.factory, tokenBought, path[i+2] == Utils.ethAddress() ? wethAddress() : path[i+2], data.initCode); + } + + (address token0,) = UniswapV3Lib.sortTokens(tokenSold, tokenBought); + (uint256 amount0Out, uint256 amount1Out) = tokenSold == token0 ? (uint256(0), tokensBought) : (tokensBought, uint256(0)); + IUniswapV2Pair(currentPair).swap( + amount0Out, amount1Out, receiver, new bytes(0) + ); + + } + + if (tokensBoughtEth) { + IWETH(wethAddress()).withdraw(tokensBought); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2TokenToToken.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2TokenToToken.sol new file mode 100644 index 000000000..c65c51c96 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2TokenToToken.sol @@ -0,0 +1,185 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import '@uniswap/lib/contracts/libraries/TransferHelper.sol'; + +import '../UniswapV3Lib.sol'; + +import "../IExchange.sol"; +import "../Utils.sol"; +import "./IUniswapRouter.sol"; + +import '../../IWETH.sol'; + + +contract UniswapV2TokenToToken is IExchange { + using SafeMath for uint256; + + struct UniswapV2Data { + address[] path; + } + + address public uinswapV2Router; + + constructor(address uinswapV2Router_) public { + uinswapV2Router = uinswapV2Router_; + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT SUPPORTED"); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + } + + + + //PATH Token -> Token + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256 receivedAmount) + { + + address[] memory path = new address[](2); + + path[0] = address(fromToken) == Utils.ethAddress() ? Utils.wethAddress() : address(fromToken); + path[1] = address(toToken) == Utils.ethAddress() ? Utils.wethAddress() : address(toToken); + + return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + uinswapV2Router, + path + ); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UniswapV2TokenToToken", "1.0.0")); + } + + + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + address[] memory path + ) + private + returns(uint256) + { + Utils.approve(address(exchange), address(fromToken), fromAmount); + + if (address(fromToken) == Utils.ethAddress()) { + require( + path[0] == Utils.wethAddress(), + "First element in path must be WETH" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + + IUniswapRouter(exchange).swapExactETHForTokens{value: fromAmount}( + toAmount, + path, + address(this), + block.timestamp + ); + } + else if (address(toToken) == Utils.ethAddress()) { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == Utils.wethAddress(), + "last element in path must be Utils.wethAddress()" + ); + IUniswapRouter(exchange).swapExactTokensForETH( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + else { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + IUniswapRouter(exchange).swapExactTokensForTokens( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } +} + diff --git a/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2WethMid.sol b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2WethMid.sol new file mode 100644 index 000000000..57a416663 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/uniswapv2/UniswapV2WethMid.sol @@ -0,0 +1,184 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../IExchange.sol"; +import "../Utils.sol"; +import "./IUniswapRouter.sol"; + + + +contract UniswapV2WethMid is IExchange { + using SafeMath for uint256; + + struct UniswapV2Data { + address[] path; + } + + address public weth; + + address public uinswapV2Router; + + constructor(address _weth, address uinswapV2Router_) public { + weth = _weth; + uinswapV2Router = uinswapV2Router_; + } + + /** + * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap + */ + receive() external payable { + } + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("UNISWAPV2WETHMID", "1.0.0")); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + + revert("METHOD NOT SUPPORTED"); + } + + //PATH Token -> WETH -> Token + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + + //Since this method is called hence I am assuming fromToken or toToken cannot be ETH + address[] memory path = new address[](3); + + path[0] = address(fromToken); + path[1] = weth; + path[2] = address(toToken); + return _swap( + fromToken, + toToken, + fromAmount, + toAmount, + uinswapV2Router, + path + ); + + + } + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + address[] memory path + ) + private + returns(uint256) + { + Utils.approve(address(exchange), address(fromToken), fromAmount); + + if (address(fromToken) == Utils.ethAddress()) { + require( + path[0] == weth, + "First element in path must be WETH" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + + IUniswapRouter(exchange).swapExactETHForTokens{value: fromAmount}( + toAmount, + path, + address(this), + block.timestamp + ); + } + else if (address(toToken) == Utils.ethAddress()) { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == weth, + "last element in path must be weth" + ); + IUniswapRouter(exchange).swapExactTokensForETH( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + else { + require( + path[0] == address(fromToken), + "First element in path must be fromToken" + ); + + require( + path[path.length - 1] == address(toToken), + "last element in path must be toToken" + ); + IUniswapRouter(exchange).swapExactTokensForTokens( + fromAmount, + toAmount, + path, + address(this), + block.timestamp + ); + } + + uint256 receivedAmount = Utils.tokenBalance( + address(toToken), + address(this) + ); + + Utils.transferTokens(address(toToken), msg.sender, receivedAmount); + + return receivedAmount; + } +} + diff --git a/lib/paraswap/lib/bdai/BdaiExchange.sol b/lib_0.7/paraswap/V4/lib/weth/WethExchange.sol similarity index 53% rename from lib/paraswap/lib/bdai/BdaiExchange.sol rename to lib_0.7/paraswap/V4/lib/weth/WethExchange.sol index 5b92a9823..5e0b9e3a9 100644 --- a/lib/paraswap/lib/bdai/BdaiExchange.sol +++ b/lib_0.7/paraswap/V4/lib/weth/WethExchange.sol @@ -1,17 +1,18 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "./IBdai.sol"; +import "../../IWETH.sol"; import "../Utils.sol"; import "../IExchange.sol"; -import "../TokenFetcher.sol"; -contract BdaiExchange is IExchange, TokenFetcher { - address public constant BDAI = address(0x6a4FFAafa8DD400676Df8076AD6c724867b0e2e8); - address public constant DAI = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); +contract WethExchange is IExchange { + + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); + } function swap( IERC20 fromToken, @@ -23,10 +24,11 @@ contract BdaiExchange is IExchange, TokenFetcher { ) external payable - returns (uint256) + override + { - return _swap( + _swap( fromToken, toToken, fromAmount, @@ -46,10 +48,11 @@ contract BdaiExchange is IExchange, TokenFetcher { ) external payable - returns (uint256) + override + { - return _swap( + _swap( fromToken, toToken, fromAmount, @@ -59,6 +62,24 @@ contract BdaiExchange is IExchange, TokenFetcher { ); } + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + revert("METHOD NOT SUPPORTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("WETH", "1.0.0")); + } + function _swap( IERC20 fromToken, IERC20 toToken, @@ -68,28 +89,21 @@ contract BdaiExchange is IExchange, TokenFetcher { bytes memory payload ) private - returns (uint256) { + address weth = Utils.wethAddress(); - Utils.approve(address(BDAI), address(fromToken)); - - if (address(fromToken) == BDAI){ - require(address(toToken) == DAI, "Destination token should be DAI"); - IBdai(BDAI).exit(fromAmount); + if (address(fromToken) == weth){ + require(address(toToken) == Utils.ethAddress(), "Destination token should be ETH"); + IWETH(weth).withdraw(fromAmount); } - else if (address(fromToken) == DAI) { - require(address(toToken) == BDAI, "Destination token should be BDAI"); - IBdai(BDAI).join(fromAmount); + else if (address(fromToken) == Utils.ethAddress()) { + require(address(toToken) == weth, "Destination token should be weth"); + IWETH(weth).deposit{value: fromAmount}(); } else { revert("Invalid fromToken"); } - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; } } diff --git a/lib/paraswap/lib/weth/WethExchange.sol b/lib_0.7/paraswap/V4/lib/weth/WethExchangeMock.sol similarity index 61% rename from lib/paraswap/lib/weth/WethExchange.sol rename to lib_0.7/paraswap/V4/lib/weth/WethExchangeMock.sol index 992d3838f..911c5142c 100644 --- a/lib/paraswap/lib/weth/WethExchange.sol +++ b/lib_0.7/paraswap/V4/lib/weth/WethExchangeMock.sol @@ -1,33 +1,22 @@ -pragma solidity ^0.5.4; +pragma solidity 0.7.5; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-solidity/contracts/utils/Address.sol"; import "../../IWETH.sol"; import "../Utils.sol"; import "../IExchange.sol"; -import "../TokenFetcher.sol"; -contract WethExchange is IExchange, TokenFetcher { - using Address for address; - address public weth; +contract WethExchangeMock is IExchange { - constructor(address _weth) public { + address public immutable weth; + constructor(address _weth) { weth = _weth; } - /** - * @dev Fallback method to allow exchanges to transfer back ethers for a particular swap - * It will only allow contracts to send funds to it - */ - function() external payable { - address account = msg.sender; - require( - account.isContract(), - "Sender is not a contract" - ); + function initialize(bytes calldata data) external override { + revert("METHOD NOT IMPLEMENTED"); } function swap( @@ -40,10 +29,11 @@ contract WethExchange is IExchange, TokenFetcher { ) external payable - returns (uint256) + override + { - return _swap( + _swap( fromToken, toToken, fromAmount, @@ -63,10 +53,11 @@ contract WethExchange is IExchange, TokenFetcher { ) external payable - returns (uint256) + override + { - return _swap( + _swap( fromToken, toToken, fromAmount, @@ -76,6 +67,24 @@ contract WethExchange is IExchange, TokenFetcher { ); } + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + revert("METHOD NOT SUPPORTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("WETH", "1.0.0")); + } + function _swap( IERC20 fromToken, IERC20 toToken, @@ -85,27 +94,19 @@ contract WethExchange is IExchange, TokenFetcher { bytes memory payload ) private - returns (uint256) { - - Utils.approve(address(weth), address(fromToken)); - if (address(fromToken) == weth){ require(address(toToken) == Utils.ethAddress(), "Destination token should be ETH"); IWETH(weth).withdraw(fromAmount); } else if (address(fromToken) == Utils.ethAddress()) { require(address(toToken) == weth, "Destination token should be weth"); - IWETH(weth).deposit.value(fromAmount)(); + IWETH(weth).deposit{value: fromAmount}(); } else { revert("Invalid fromToken"); } - uint256 receivedAmount = Utils.tokenBalance(address(toToken), address(this)); - - Utils.transferTokens(address(toToken), msg.sender, receivedAmount); - - return receivedAmount; } + } diff --git a/lib_0.7/paraswap/V4/lib/zeroxv2/IZeroxV2.sol b/lib_0.7/paraswap/V4/lib/zeroxv2/IZeroxV2.sol new file mode 100644 index 000000000..7d643c781 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv2/IZeroxV2.sol @@ -0,0 +1,16 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "./LibOrderV2.sol"; + + +interface IZeroxV2 { + + function marketSellOrdersNoThrow( + LibOrderV2.Order[] calldata orders, + uint256 takerAssetFillAmount, + bytes[] calldata signatures + ) + external + returns(LibOrderV2.FillResults memory); +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv2/LibOrderV2.sol b/lib_0.7/paraswap/V4/lib/zeroxv2/LibOrderV2.sol new file mode 100644 index 000000000..6e0a258fb --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv2/LibOrderV2.sol @@ -0,0 +1,30 @@ +/* solium-disable */ + +pragma solidity 0.7.5; + +//Taken from 0x exchange +library LibOrderV2{ + + + struct Order { + address makerAddress; // Address that created the order. + address takerAddress; // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + address feeRecipientAddress; // Address that will recieve fees when order is filled. + address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0. + uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0. + uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted. + uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted. + uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires. + uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash. + bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The last byte references the id of this proxy. + bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The last byte references the id of this proxy. + } + + struct FillResults { + uint256 makerAssetFilledAmount; // Total amount of makerAsset(s) filled. + uint256 takerAssetFilledAmount; // Total amount of takerAsset(s) filled. + uint256 makerFeePaid; // Total amount of ZRX paid by maker(s) to feeRecipient(s). + uint256 takerFeePaid; // Total amount of ZRX paid by taker to feeRecipients(s). + } +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2.sol b/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2.sol new file mode 100644 index 000000000..9a8853183 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2.sol @@ -0,0 +1,171 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../../IWETH.sol"; +import "./IZeroxV2.sol"; +import "./LibOrderV2.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; + +import "../libraries/LibBytes.sol"; +import "../../AdapterStorage.sol"; + + +contract ZeroxV2 is IExchange, AdapterStorage { + using LibBytes for bytes; + + struct ZeroxData { + LibOrderV2.Order[] orders; + bytes[] signatures; + } + + struct LocalData { + address erc20Proxy; + } + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + address _fromToken = address(fromToken); + + if (address(fromToken) == Utils.ethAddress()) { + IWETH(Utils.wethAddress()).deposit{value: fromAmount}(); + } + + _swap( + fromToken, + toToken, + fromAmount, + toAmount, + exchange, + payload + ); + + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + + { + + address _fromToken = address(fromToken); + + if (address(fromToken) == Utils.ethAddress()) { + IWETH(Utils.wethAddress()).deposit{value: fromAmount}(); + _fromToken = Utils.wethAddress(); + } + + _swap( + fromToken, + toToken, + fromAmount, + toAmount, + exchange, + payload + ); + + if (address(fromToken) == Utils.ethAddress()) { + uint256 remainingAmount = Utils.tokenBalance(address(_fromToken), address(this)); + if (remainingAmount > 0) { + IWETH(Utils.wethAddress()).withdraw(remainingAmount); + } + } + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + revert("METHOD NOT SUPPORTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("0XV2", "1.0.0")); + } + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes memory payload + ) + private + { + + address _fromToken = address(fromToken); + address _toToken = address(toToken); + + if (_fromToken == Utils.ethAddress()) { + _fromToken = Utils.wethAddress(); + } + + else if (_toToken == Utils.ethAddress()) { + _toToken = Utils.wethAddress(); + } + + ZeroxData memory data = abi.decode(payload, (ZeroxData)); + + bytes32 key = getKey(); + bytes memory localData = adapterVsData[key]; + LocalData memory lData = abi.decode(localData, (LocalData)); + + for (uint256 i = 0; i < data.orders.length; i++) { + address srcToken = data.orders[i].takerAssetData.readAddress(16); + require(srcToken == address(_fromToken), "Invalid from token!!"); + + address destToken = data.orders[i].makerAssetData.readAddress(16); + require(destToken == address(_toToken), "Invalid to token!!"); + } + + Utils.approve(lData.erc20Proxy, address(_fromToken), fromAmount); + + IZeroxV2(exchange).marketSellOrdersNoThrow( + data.orders, + fromAmount, + data.signatures + ); + + if (address(toToken) == Utils.ethAddress()) { + uint256 receivedAmount = Utils.tokenBalance(Utils.wethAddress(), address(this)); + IWETH(Utils.wethAddress()).withdraw(receivedAmount); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2TargetExchangeMock.sol b/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2TargetExchangeMock.sol new file mode 100644 index 000000000..97f3b084c --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv2/ZeroxV2TargetExchangeMock.sol @@ -0,0 +1,19 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "./IZeroxV2.sol"; + +contract ZeroxV2TargetExchangeMock is IZeroxV2 { + + function marketSellOrdersNoThrow( + LibOrderV2.Order[] calldata orders, + uint256 takerAssetFillAmount, + bytes[] calldata signatures + ) + external + override + returns(LibOrderV2.FillResults memory) + { + // empty mock + } +} \ No newline at end of file diff --git a/lib_0.7/paraswap/V4/lib/zeroxv4/IZeroxV4.sol b/lib_0.7/paraswap/V4/lib/zeroxv4/IZeroxV4.sol new file mode 100644 index 000000000..a2fedaeb3 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv4/IZeroxV4.sol @@ -0,0 +1,21 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "./LibOrderV4.sol"; + + +interface IZeroxV4 { + + function fillRfqOrder( + // The order + LibOrderV4.Order calldata order, + // The signature + LibOrderV4.Signature calldata signature, + // How much taker token to fill the order with + uint128 takerTokenFillAmount + ) + external + payable + // How much maker token from the order the taker received. + returns (uint128, uint128); +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv4/LibOrderV4.sol b/lib_0.7/paraswap/V4/lib/zeroxv4/LibOrderV4.sol new file mode 100644 index 000000000..955d7862f --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv4/LibOrderV4.sol @@ -0,0 +1,39 @@ +/* solium-disable */ + +pragma solidity 0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + + +library LibOrderV4 { + struct Order { + IERC20 makerToken; + IERC20 takerToken; + uint128 makerAmount; + uint128 takerAmount; + address maker; + address taker; + address txOrigin; + bytes32 pool; + uint64 expiry; + uint256 salt; + } + + enum SignatureType { + ILLEGAL, + INVALID, + EIP712, + ETHSIGN + } + + struct Signature { + // How to validate the signature. + SignatureType signatureType; + // EC Signature data. + uint8 v; + // EC Signature data. + bytes32 r; + // EC Signature data. + bytes32 s; + } +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4.sol b/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4.sol new file mode 100644 index 000000000..514efbef2 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4.sol @@ -0,0 +1,144 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + +import "../../IWETH.sol"; +import "./IZeroxV4.sol"; +import "./LibOrderV4.sol"; +import "../Utils.sol"; +import "../IExchange.sol"; +import "../../AdapterStorage.sol"; + + +contract ZeroxV4 is IExchange, AdapterStorage { + + struct ZeroxData { + LibOrderV4.Order order; + LibOrderV4.Signature signature; + } + + // No LocalData for this adapter + + function initialize(bytes calldata data) external override { + bytes32 key = getKey(); + require(!adapterInitialized[key], "Adapter already initialized"); + //abi.decode(data, (LocalData)); + adapterInitialized[key] = true; + adapterVsData[key] = data; + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + if (address(fromToken) == Utils.ethAddress()) { + IWETH(Utils.wethAddress()).deposit{value: fromAmount}(); + } + + _swap( + fromToken, + toToken, + fromAmount, + toAmount, + exchange, + payload + ); + } + + function buy( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes calldata payload + ) + external + payable + override + { + if (address(fromToken) == Utils.ethAddress()) { + IWETH(Utils.wethAddress()).deposit{value: fromAmount}(); + } + + _swap( + fromToken, + toToken, + fromAmount, + toAmount, + exchange, + payload + ); + + if (address(fromToken) == Utils.ethAddress()) { + uint256 remainingAmount = Utils.tokenBalance(Utils.wethAddress(), address(this)); + if (remainingAmount > 0) { + IWETH(Utils.wethAddress()).withdraw(remainingAmount); + } + } + } + + function onChainSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount + ) + external + override + payable + returns (uint256) + { + revert("METHOD NOT SUPPORTED"); + } + + function getKey() public override pure returns(bytes32) { + return keccak256(abi.encodePacked("0xV4", "1.0.0")); + } + + function _swap( + IERC20 fromToken, + IERC20 toToken, + uint256 fromAmount, + uint256 toAmount, + address exchange, + bytes memory payload) private { + + ZeroxData memory data = abi.decode(payload, (ZeroxData)); + + address _fromToken = address(fromToken); + address _toToken = address(toToken); + + if (address(fromToken) == Utils.ethAddress()) { + _fromToken = Utils.wethAddress(); + } + else if (address(toToken) == Utils.ethAddress()) { + _toToken = Utils.wethAddress(); + } + + require(address(data.order.takerToken) == address(_fromToken), "Invalid from token!!"); + require(address(data.order.makerToken) == address(_toToken), "Invalid to token!!"); + + Utils.approve(exchange, address(_fromToken), fromAmount); + + IZeroxV4(exchange).fillRfqOrder( + data.order, + data.signature, + uint128(fromAmount) + ); + + if (address(toToken) == Utils.ethAddress()) { + uint256 receivedAmount = Utils.tokenBalance(Utils.wethAddress(), address(this)); + IWETH(Utils.wethAddress()).withdraw(receivedAmount); + } + } +} diff --git a/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4TargetExchangeMock.sol b/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4TargetExchangeMock.sol new file mode 100644 index 000000000..f19403ff2 --- /dev/null +++ b/lib_0.7/paraswap/V4/lib/zeroxv4/ZeroxV4TargetExchangeMock.sol @@ -0,0 +1,24 @@ +pragma solidity 0.7.5; +pragma experimental ABIEncoderV2; + +import "./IZeroxV4.sol"; + +contract ZeroxV4TargetExchangeMock is IZeroxV4 { + + function fillRfqOrder( + // The order + LibOrderV4.Order calldata order, + // The signature + LibOrderV4.Signature calldata signature, + // How much taker token to fill the order with + uint128 takerTokenFillAmount + ) + external + override + payable + // How much maker token from the order the taker received. + returns (uint128, uint128) + { + // empty mock + } +} \ No newline at end of file diff --git a/lib_0.7/paraswap/V4/proxy/AugustusProxy.sol b/lib_0.7/paraswap/V4/proxy/AugustusProxy.sol new file mode 100644 index 000000000..5734adbff --- /dev/null +++ b/lib_0.7/paraswap/V4/proxy/AugustusProxy.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.5; + +import "./UpgradeabilityProxy.sol"; + + +/** + * @title AugustusProxy + * @dev This contract combines an upgradeability proxy with an authorization + * mechanism for administrative tasks. + * All external functions in this contract must be guarded by the + * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity + * feature proposal that would enable this to be done automatically. + */ +contract AugustusProxy is UpgradeabilityProxy { + /** + * Contract constructor. + * @param _logic address of the initial implementation. + * @param _admin Address of the proxy administrator. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + constructor( + address _logic, + address _admin, + bytes memory _data + ) + UpgradeabilityProxy(_logic, _data) + public + payable + { + assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)); + _setAdmin(_admin); + } + + /** + * @dev Emitted when the administration has been transferred. + * @param previousAdmin Address of the previous admin. + * @param newAdmin Address of the new admin. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Storage slot with the admin of the contract. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is + * validated in the constructor. + */ + + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /** + * @dev Modifier to check whether the `msg.sender` is the admin. + * If it is, it will run the function. Otherwise, it will delegate the call + * to the implementation. + */ + modifier ifAdmin() { + if (msg.sender == _admin()) { + _; + } else { + _fallback(); + } + } + + /** + * @return The address of the proxy admin. + */ + function admin() external ifAdmin returns (address) { + return _admin(); + } + + /** + * @return The address of the implementation. + */ + function implementation() external ifAdmin returns (address) { + return _implementation(); + } + + /** + * @dev Changes the admin of the proxy. + * Only the current admin can call this function. + * @param newAdmin Address to transfer proxy administration to. + */ + function changeAdmin(address newAdmin) external ifAdmin { + require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address"); + emit AdminChanged(_admin(), newAdmin); + _setAdmin(newAdmin); + } + + /** + * @dev Upgrade the backing implementation of the proxy. + * Only the admin can call this function. + * @param newImplementation Address of the new implementation. + */ + function upgradeTo(address newImplementation) external ifAdmin { + _upgradeTo(newImplementation); + } + + /** + * @dev Upgrade the backing implementation of the proxy and call a function + * on the new implementation. + * This is useful to initialize the proxied contract. + * @param newImplementation Address of the new implementation. + * @param data Data to send as msg.data in the low level call. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + */ + function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin { + _upgradeTo(newImplementation); + (bool success,) = newImplementation.delegatecall(data); + require(success); + } + + /** + * @return adm The admin slot. + */ + function _admin() internal view returns (address adm) { + bytes32 slot = ADMIN_SLOT; + assembly { + adm := sload(slot) + } + } + + /** + * @dev Sets the address of the proxy admin. + * @param newAdmin Address of the new proxy admin. + */ + function _setAdmin(address newAdmin) internal { + bytes32 slot = ADMIN_SLOT; + + assembly { + sstore(slot, newAdmin) + } + } + + /** + * @dev Only fall back when the sender is not the admin. + */ + function _willFallback() internal override virtual { + require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin"); + super._willFallback(); + } +} diff --git a/lib_0.7/paraswap/V4/proxy/Proxy.sol b/lib_0.7/paraswap/V4/proxy/Proxy.sol new file mode 100644 index 000000000..a8a62f964 --- /dev/null +++ b/lib_0.7/paraswap/V4/proxy/Proxy.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.5; + +/** + * @title Proxy + * @dev Implements delegation of calls to other contracts, with proper + * forwarding of return values and bubbling of failures. + * It defines a fallback function that delegates all calls to the address + * returned by the abstract _implementation() internal function. + */ +abstract contract Proxy { + /** + * @dev Fallback function. + * Implemented entirely in `_fallback`. + */ + fallback () payable external { + _fallback(); + } + + /** + * @dev Receive function. + * Implemented entirely in `_fallback`. + */ + receive () payable external { + _fallback(); + } + + /** + * @return The Address of the implementation. + */ + function _implementation() internal virtual view returns (address); + + /** + * @dev Delegates execution to an implementation contract. + * This is a low level function that doesn't return to its internal call site. + * It will return to the external caller whatever the implementation returns. + * @param implementation Address to delegate. + */ + function _delegate(address implementation) internal { + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + /** + * @dev Function that is run as the first thing in the fallback function. + * Can be redefined in derived contracts to add functionality. + * Redefinitions must call super._willFallback(). + */ + function _willFallback() internal virtual { + } + + /** + * @dev fallback implementation. + * Extracted to enable manual triggering. + */ + function _fallback() internal { + _willFallback(); + _delegate(_implementation()); + } +} diff --git a/lib_0.7/paraswap/V4/proxy/UpgradeabilityProxy.sol b/lib_0.7/paraswap/V4/proxy/UpgradeabilityProxy.sol new file mode 100644 index 000000000..1cadae575 --- /dev/null +++ b/lib_0.7/paraswap/V4/proxy/UpgradeabilityProxy.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.7.5; + +import "./Proxy.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; + + +/** + * @title UpgradeabilityProxy + * @dev This contract implements a proxy that allows to change the + * implementation address to which it will delegate. + * Such a change is called an implementation upgrade. + */ +contract UpgradeabilityProxy is Proxy { + /** + * @dev Contract constructor. + * @param _logic Address of the initial implementation. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + constructor( + address _logic, + bytes memory _data + ) + public + payable + { + assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); + _setImplementation(_logic); + if (_data.length > 0) { + (bool success,) = _logic.delegatecall(_data); + require(success); + } + } + + /** + * @dev Emitted when the implementation is upgraded. + * @param implementation Address of the new implementation. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal override view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + assembly { + impl := sload(slot) + } + } + + /** + * @dev Upgrades the proxy to a new implementation. + * @param newImplementation Address of the new implementation. + */ + function _upgradeTo(address newImplementation) internal { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); + } + + /** + * @dev Sets the implementation address of the proxy. + * @param newImplementation Address of the new implementation. + */ + function _setImplementation(address newImplementation) internal { + require(Address.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address"); + + bytes32 slot = IMPLEMENTATION_SLOT; + + assembly { + sstore(slot, newImplementation) + } + } +} diff --git a/lib_0.7/paraswap/V4/test/IZerox.sol b/lib_0.7/paraswap/V4/test/IZerox.sol new file mode 100644 index 000000000..3e7ea8f70 --- /dev/null +++ b/lib_0.7/paraswap/V4/test/IZerox.sol @@ -0,0 +1,21 @@ +pragma solidity 0.7.5; + +/// @dev VIP uniswap fill functions. +interface IZerox { + + /// @dev Efficiently sell directly to uniswap/sushiswap. + /// @param tokens Sell path. + /// @param sellAmount of `tokens[0]` Amount to sell. + /// @param minBuyAmount Minimum amount of `tokens[-1]` to buy. + /// @param isSushi Use sushiswap if true. + /// @return buyAmount Amount of `tokens[-1]` bought. + function sellToUniswap( + address[] calldata tokens, + uint256 sellAmount, + uint256 minBuyAmount, + bool isSushi + ) + external + payable + returns (uint256 buyAmount); +} diff --git a/lib_0.7/paraswap/V4/test/UniswapCreate2Check.sol b/lib_0.7/paraswap/V4/test/UniswapCreate2Check.sol new file mode 100644 index 000000000..432dc6b6a --- /dev/null +++ b/lib_0.7/paraswap/V4/test/UniswapCreate2Check.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.7.0; + +contract UniswapCreate2Check { + function test() external pure returns (address[4] memory result) { + address token0 = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + address token1 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + result[0] = address(uint(keccak256(abi.encodePacked( + hex"ff", + 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f, //UniswapV2 factory + keccak256(abi.encodePacked(token0, token1)), + hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" + )))); //0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc + result[1] = address(uint(keccak256(abi.encodePacked( + hex"ff", + 0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac, //SushiSwap factory + keccak256(abi.encodePacked(token0, token1)), + hex"e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303" + )))); //0x397FF1542f962076d0BFE58eA045FfA2d347ACa0 + result[2] = address(uint(keccak256(abi.encodePacked( + hex"ff", + 0x696708Db871B77355d6C2bE7290B27CF0Bb9B24b, //LinkSwap factory + keccak256(abi.encodePacked(token0, token1)), + hex"50955d9250740335afc702786778ebeae56a5225e4e18b7cb046e61437cde6b3" + )))); //0x466d82B7D15Af812FB6c788D7b15C635FA933499 + result[3] = address(uint(keccak256(abi.encodePacked( + hex"ff", + 0x9DEB29c9a4c7A88a3C0257393b7f3335338D9A9D, //DefiSwap factory + keccak256(abi.encodePacked(token0, token1)), + hex"69d637e77615df9f235f642acebbdad8963ef35c5523142078c9b8f9d0ceba7e" + )))); //0x3Aa370AacF4CB08C7E1E7AA8E8FF9418D73C7e0F + } +} diff --git a/lib_0.7/yearn/Controller.sol b/lib_0.7/yearn/Controller.sol new file mode 100644 index 000000000..b16b90be4 --- /dev/null +++ b/lib_0.7/yearn/Controller.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; + +import "./interfaces/IConverter.sol"; +import "./interfaces/IOneSplitAudit.sol"; +import "./interfaces/IStrategy.sol"; + +contract Controller { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + address public governance; + address public strategist; + + address public onesplit; + address public rewards; + mapping(address => address) public vaults; + mapping(address => address) public strategies; + mapping(address => mapping(address => address)) public converters; + + mapping(address => mapping(address => bool)) public approvedStrategies; + + uint256 public split = 500; + uint256 public constant max = 10000; + + constructor(address _rewards) public { + governance = msg.sender; + strategist = msg.sender; + onesplit = address(0x50FDA034C0Ce7a8f7EFDAebDA7Aa7cA21CC1267e); + rewards = _rewards; + } + + function setRewards(address _rewards) public { + require(msg.sender == governance, "!governance"); + rewards = _rewards; + } + + function setStrategist(address _strategist) public { + require(msg.sender == governance, "!governance"); + strategist = _strategist; + } + + function setSplit(uint256 _split) public { + require(msg.sender == governance, "!governance"); + split = _split; + } + + function setOneSplit(address _onesplit) public { + require(msg.sender == governance, "!governance"); + onesplit = _onesplit; + } + + function setGovernance(address _governance) public { + require(msg.sender == governance, "!governance"); + governance = _governance; + } + + function setVault(address _token, address _vault) public { + require(msg.sender == strategist || msg.sender == governance, "!strategist"); + require(vaults[_token] == address(0), "vault"); + vaults[_token] = _vault; + } + + function approveStrategy(address _token, address _strategy) public { + require(msg.sender == governance, "!governance"); + approvedStrategies[_token][_strategy] = true; + } + + function revokeStrategy(address _token, address _strategy) public { + require(msg.sender == governance, "!governance"); + approvedStrategies[_token][_strategy] = false; + } + + function setConverter( + address _input, + address _output, + address _converter + ) public { + require(msg.sender == strategist || msg.sender == governance, "!strategist"); + converters[_input][_output] = _converter; + } + + function setStrategy(address _token, address _strategy) public { + require(msg.sender == strategist || msg.sender == governance, "!strategist"); + require(approvedStrategies[_token][_strategy] == true, "!approved"); + + address _current = strategies[_token]; + if (_current != address(0)) { + IStrategy(_current).withdrawAll(); + } + strategies[_token] = _strategy; + } + + function earn(address _token, uint256 _amount) public { + address _strategy = strategies[_token]; + address _want = IStrategy(_strategy).want(); + if (_want != _token) { + address converter = converters[_token][_want]; + IERC20(_token).safeTransfer(converter, _amount); + _amount = IConverter(converter).convert(_strategy); + IERC20(_want).safeTransfer(_strategy, _amount); + } else { + IERC20(_token).safeTransfer(_strategy, _amount); + } + IStrategy(_strategy).deposit(); + } + + function balanceOf(address _token) external view returns (uint256) { + return IStrategy(strategies[_token]).balanceOf(); + } + + function withdrawAll(address _token) public { + require(msg.sender == strategist || msg.sender == governance, "!strategist"); + IStrategy(strategies[_token]).withdrawAll(); + } + + function inCaseTokensGetStuck(address _token, uint256 _amount) public { + require(msg.sender == strategist || msg.sender == governance, "!governance"); + IERC20(_token).safeTransfer(msg.sender, _amount); + } + + function inCaseStrategyTokenGetStuck(address _strategy, address _token) public { + require(msg.sender == strategist || msg.sender == governance, "!governance"); + IStrategy(_strategy).withdraw(_token); + } + + function getExpectedReturn( + address _strategy, + address _token, + uint256 parts + ) public view returns (uint256 expected) { + uint256 _balance = IERC20(_token).balanceOf(_strategy); + address _want = IStrategy(_strategy).want(); + (expected, ) = IOneSplitAudit(onesplit).getExpectedReturn(_token, _want, _balance, parts, 0); + } + + // Only allows to withdraw non-core strategy tokens ~ this is over and above normal yield + function yearn( + address _strategy, + address _token, + uint256 parts + ) public { + require(msg.sender == strategist || msg.sender == governance, "!governance"); + // This contract should never have value in it, but just incase since this is a public call + uint256 _before = IERC20(_token).balanceOf(address(this)); + IStrategy(_strategy).withdraw(_token); + uint256 _after = IERC20(_token).balanceOf(address(this)); + if (_after > _before) { + uint256 _amount = _after.sub(_before); + address _want = IStrategy(_strategy).want(); + uint256[] memory _distribution; + uint256 _expected; + _before = IERC20(_want).balanceOf(address(this)); + IERC20(_token).safeApprove(onesplit, 0); + IERC20(_token).safeApprove(onesplit, _amount); + (_expected, _distribution) = IOneSplitAudit(onesplit).getExpectedReturn(_token, _want, _amount, parts, 0); + IOneSplitAudit(onesplit).swap(_token, _want, _amount, _expected, _distribution, 0); + _after = IERC20(_want).balanceOf(address(this)); + if (_after > _before) { + _amount = _after.sub(_before); + uint256 _reward = _amount.mul(split).div(max); + earn(_want, _amount.sub(_reward)); + IERC20(_want).safeTransfer(rewards, _reward); + } + } + } + + function withdraw(address _token, uint256 _amount) public { + require(msg.sender == vaults[_token], "!vault"); + IStrategy(strategies[_token]).withdraw(_amount); + } +} \ No newline at end of file diff --git a/lib_0.7/yearn/StrategyMock.sol b/lib_0.7/yearn/StrategyMock.sol new file mode 100644 index 000000000..69ad899a1 --- /dev/null +++ b/lib_0.7/yearn/StrategyMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +import "./interfaces/IStrategy.sol"; + +contract StrategyMock is IStrategy { + function want() external override view returns (address) {} + + function deposit() external override {} + + // NOTE: must exclude any tokens used in the yield + // Controller role - withdraw should return to Controller + function withdraw(address) external override{} + + // Controller | Vault role - withdraw should always return to Vault + function withdraw(uint256) override external {} + + function skim() external override {} + + // Controller | Vault role - withdraw should always return to Vault + function withdrawAll() external override returns (uint256) {} + + function balanceOf() external override view returns (uint256) {} + + function withdrawalFee() external override view returns (uint256) {} +} \ No newline at end of file diff --git a/lib_0.7/yearn/interfaces/IController.sol b/lib_0.7/yearn/interfaces/IController.sol new file mode 100644 index 000000000..341fc9651 --- /dev/null +++ b/lib_0.7/yearn/interfaces/IController.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +interface IController { + function withdraw(address, uint256) external; + + function balanceOf(address) external view returns (uint256); + + function earn(address, uint256) external; + + function want(address) external view returns (address); + + function rewards() external view returns (address); + + function vaults(address) external view returns (address); + + function strategies(address) external view returns (address); + + function approvedStrategies(address, address) external view returns (bool); +} \ No newline at end of file diff --git a/lib_0.7/yearn/interfaces/IConverter.sol b/lib_0.7/yearn/interfaces/IConverter.sol new file mode 100644 index 000000000..517f0173d --- /dev/null +++ b/lib_0.7/yearn/interfaces/IConverter.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +interface IConverter { + function convert(address) external returns (uint256); +} \ No newline at end of file diff --git a/lib_0.7/yearn/interfaces/IOneSplitAudit.sol b/lib_0.7/yearn/interfaces/IOneSplitAudit.sol new file mode 100644 index 000000000..06a752545 --- /dev/null +++ b/lib_0.7/yearn/interfaces/IOneSplitAudit.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +interface IOneSplitAudit { + function swap( + address fromToken, + address destToken, + uint256 amount, + uint256 minReturn, + uint256[] calldata distribution, + uint256 flags + ) external payable returns (uint256 returnAmount); + + function getExpectedReturn( + address fromToken, + address destToken, + uint256 amount, + uint256 parts, + uint256 flags // See constants in IOneSplit.sol + ) external view returns (uint256 returnAmount, uint256[] memory distribution); +} \ No newline at end of file diff --git a/lib_0.7/yearn/interfaces/IStrategy.sol b/lib_0.7/yearn/interfaces/IStrategy.sol new file mode 100644 index 000000000..e7e5f8e5d --- /dev/null +++ b/lib_0.7/yearn/interfaces/IStrategy.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +interface IStrategy { + function want() external view returns (address); + + function deposit() external; + + // NOTE: must exclude any tokens used in the yield + // Controller role - withdraw should return to Controller + function withdraw(address) external; + + // Controller | Vault role - withdraw should always return to Vault + function withdraw(uint256) external; + + function skim() external; + + // Controller | Vault role - withdraw should always return to Vault + function withdrawAll() external returns (uint256); + + function balanceOf() external view returns (uint256); + + function withdrawalFee() external view returns (uint256); +} \ No newline at end of file diff --git a/lib_0.7/yearn/interfaces/IWETH.sol b/lib_0.7/yearn/interfaces/IWETH.sol new file mode 100644 index 000000000..07b3a160b --- /dev/null +++ b/lib_0.7/yearn/interfaces/IWETH.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.7.5; + +interface IWETH { + function deposit() external payable; + function withdraw(uint wad) external; + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); +} \ No newline at end of file diff --git a/lib_0.7/yearn/yVault.sol b/lib_0.7/yearn/yVault.sol new file mode 100644 index 000000000..9b548f082 --- /dev/null +++ b/lib_0.7/yearn/yVault.sol @@ -0,0 +1,171 @@ +pragma solidity ^0.7.5; + +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/utils/Address.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "openzeppelin-solidity/contracts/access/Ownable.sol"; + +import "./interfaces/IController.sol"; +import "./interfaces/IWETH.sol"; + +contract yVault is ERC20 { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + + IERC20 public token; + + uint256 public min = 9500; + uint256 public constant max = 10000; + + address public governance; + address public controller; + + constructor(address _token, address _controller) + public + ERC20( + string(abi.encodePacked("yearn ", ERC20(_token).name())), + string(abi.encodePacked("y", ERC20(_token).symbol())) + ) + { + token = IERC20(_token); + governance = msg.sender; + controller = _controller; + } + + function balance() public view returns (uint256) { + return token.balanceOf(address(this)).add(IController(controller).balanceOf(address(token))); + } + + function setMin(uint256 _min) external { + require(msg.sender == governance, "!governance"); + min = _min; + } + + function setGovernance(address _governance) public { + require(msg.sender == governance, "!governance"); + governance = _governance; + } + + function setController(address _controller) public { + require(msg.sender == governance, "!governance"); + controller = _controller; + } + + // Custom logic in here for how much the vault allows to be borrowed + // Sets minimum required on-hand to keep small withdrawals cheap + function available() public view returns (uint256) { + return token.balanceOf(address(this)).mul(min).div(max); + } + + function earn() public { + uint256 _bal = available(); + token.safeTransfer(controller, _bal); + IController(controller).earn(address(token), _bal); + } + + function depositAll() external { + deposit(token.balanceOf(msg.sender)); + } + + function deposit(uint256 _amount) public { + uint256 _pool = balance(); + uint256 _before = token.balanceOf(address(this)); + token.safeTransferFrom(msg.sender, address(this), _amount); + uint256 _after = token.balanceOf(address(this)); + _amount = _after.sub(_before); // Additional check for deflationary tokens + uint256 shares = 0; + if (totalSupply() == 0) { + shares = _amount; + } else { + shares = (_amount.mul(totalSupply())).div(_pool); + } + _mint(msg.sender, shares); + } + + function withdrawAll() external { + withdraw(balanceOf(msg.sender)); + } + + // Used to swap any borrowed reserve over the debt limit to liquidate to 'token' + function harvest(address reserve, uint256 amount) external { + require(msg.sender == controller, "!controller"); + require(reserve != address(token), "token"); + IERC20(reserve).safeTransfer(controller, amount); + } + + // No rebalance implementation for lower fees and faster swaps + function withdraw(uint256 _shares) public { + uint256 r = (balance().mul(_shares)).div(totalSupply()); + _burn(msg.sender, _shares); + + // Check balance + uint256 b = token.balanceOf(address(this)); + if (b < r) { + uint256 _withdraw = r.sub(b); + IController(controller).withdraw(address(token), _withdraw); + uint256 _after = token.balanceOf(address(this)); + uint256 _diff = _after.sub(b); + if (_diff < _withdraw) { + r = b.add(_diff); + } + } + + token.safeTransfer(msg.sender, r); + } + + function getPricePerFullShare() public view returns (uint256) { + return balance().mul(1e18).div(totalSupply()); + } + + // ETH deposit/withdrawals + + function depositETH() public payable { + uint _pool = balance(); + uint _before = token.balanceOf(address(this)); + uint _amount = msg.value; + IWETH(address(token)).deposit{value: _amount}(); + uint _after = token.balanceOf(address(this)); + _amount = _after.sub(_before); // Additional check for deflationary tokens + uint shares = 0; + if (totalSupply() == 0) { + shares = _amount; + } else { + shares = (_amount.mul(totalSupply())).div(_pool); + } + _mint(msg.sender, shares); + } + + // No rebalance implementation for lower fees and faster swaps + function withdrawETH(uint _shares) public { + uint256 r = (balance().mul(_shares)).div(totalSupply()); + _burn(msg.sender, _shares); + + // Check balance + uint256 b = token.balanceOf(address(this)); + if (b < r) { + uint256 _withdraw = r.sub(b); + IController(controller).withdraw(address(token), _withdraw); + uint256 _after = token.balanceOf(address(this)); + uint256 _diff = _after.sub(b); + if (_diff < _withdraw) { + r = b.add(_diff); + } + } + + IWETH(address(token)).withdraw(r); + payable(msg.sender).call{value: r}(""); + } + + function withdrawAllETH() external { + withdrawETH(balanceOf(msg.sender)); + } + + fallback () external payable { + if (msg.sender != address(token)) { + depositETH(); + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ee821bf9d..e1c0352d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -727,35 +727,35 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", - "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.3", + "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", - "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", - "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.3", + "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" } }, "@openzeppelin/contracts": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.0.1.tgz", - "integrity": "sha512-uSrD7hZ0ViuHGqHZbeHawZBi/uy7aBiNramXAt2dFFuSuoU4u9insS3V3zdVfOnYSPreUo636xSOuQIFN4//HA==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.0.0.tgz", + "integrity": "sha512-UcIJl/vUVjTr3H1yYXZi7Sr2PlXzBEHVUJKOUlVyzyy0FI8oQCCy0Wx+BuK/fojdnmLeMvUk4KUvhKUybP+C7Q==" }, "@resolver-engine/core": { "version": "0.2.1", @@ -2835,9 +2835,9 @@ "dev": true }, "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, "@types/node": { @@ -2895,6 +2895,27 @@ "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==" }, + "@uniswap/v2-periphery": { + "version": "1.1.0-beta.0", + "resolved": "https://registry.npmjs.org/@uniswap/v2-periphery/-/v2-periphery-1.1.0-beta.0.tgz", + "integrity": "sha512-6dkwAMKza8nzqYiXEr2D86dgW3TTavUvCR0w2Tu33bAbM8Ah43LKAzH7oKKPRT5VJQaMi1jtkGs1E8JPor1n5g==", + "requires": { + "@uniswap/lib": "1.1.1", + "@uniswap/v2-core": "1.0.0" + }, + "dependencies": { + "@uniswap/lib": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-1.1.1.tgz", + "integrity": "sha512-2yK7sLpKIT91TiS5sewHtOa7YuM8IuBXVl4GZv2jZFys4D2sY7K5vZh6MqD25TPA95Od+0YzCVq6cTF2IKrOmg==" + }, + "@uniswap/v2-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.0.tgz", + "integrity": "sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA==" + } + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -5078,25 +5099,6 @@ "string.prototype.trimstart": "^1.0.1" }, "dependencies": { - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, "object.assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", @@ -7074,9 +7076,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", - "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -7105,9 +7107,9 @@ "dev": true }, "fastq": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz", - "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -10256,6 +10258,23 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "ltgt": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", @@ -11012,12 +11031,6 @@ "strip-ansi": "^3.0.1" } }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, "yargs": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.6.0.tgz", @@ -11431,9 +11444,9 @@ "dev": true }, "openzeppelin-solidity": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", - "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-3.4.0.tgz", + "integrity": "sha512-329HOVApCH24VUST4dz8q7X/lcGm6IdRQ/gAGx2ZWaKYrfX4xMy9oEBBI3n1ih3re8ubUNELrMzgSAnqj2hpeg==" }, "optionator": { "version": "0.8.3", @@ -12102,6 +12115,12 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", @@ -12530,10 +12549,13 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-parallel": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", - "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } }, "rustbn.js": { "version": "0.2.0", @@ -13204,12 +13226,12 @@ } }, "solidity-coverage": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.7.12.tgz", - "integrity": "sha512-9iCiZU1rppeZEaprN9j7QSNWzOyMqipQ1VYMPbeipVr2HI0qdxkmg/QtizyJyHz35eClmuxyBNEzXTMaFnldTA==", + "version": "0.7.16", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.7.16.tgz", + "integrity": "sha512-ttBOStywE6ZOTJmmABSg4b8pwwZfYKG8zxu40Nz+sRF5bQX7JULXWj/XbX0KXps3Fsp8CJXg8P29rH3W54ipxw==", "dev": true, "requires": { - "@solidity-parser/parser": "^0.8.1", + "@solidity-parser/parser": "^0.12.0", "@truffle/provider": "^0.2.24", "chalk": "^2.4.2", "death": "^1.1.0", @@ -13225,10 +13247,17 @@ "pify": "^4.0.1", "recursive-readdir": "^2.2.2", "sc-istanbul": "^0.4.5", + "semver": "^7.3.4", "shelljs": "^0.8.3", "web3-utils": "^1.3.0" }, "dependencies": { + "@solidity-parser/parser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.12.0.tgz", + "integrity": "sha512-DT3f/Aa4tQysZwUsuqBwvr8YRJzKkvPUKV/9o2/o5EVw3xqlbzmtx4O60lTUcZdCawL+N8bBLNUyOGpHjGlJVQ==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -13264,9 +13293,9 @@ "dev": true }, "sc-istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.5.tgz", - "integrity": "sha512-7wR5EZFLsC4w0wSm9BUuCgW+OGKAU7PNlW5L0qwVPbh+Q1sfVn2fyzfMXYCm6rkNA5ipaCOt94nApcguQwF5Gg==", + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", + "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", "dev": true, "requires": { "abbrev": "1.0.x", @@ -13295,6 +13324,15 @@ } } } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -15153,9 +15191,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", "dev": true }, "yaeti": { @@ -15265,6 +15303,12 @@ "requires": { "ansi-regex": "^4.1.0" } + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true } } }, @@ -15363,6 +15407,12 @@ "ansi-regex": "^4.1.0" } }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, "yargs": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", diff --git a/package.json b/package.json index 8c1a34960..6cbb49a59 100644 --- a/package.json +++ b/package.json @@ -6,36 +6,35 @@ "test": "test" }, "scripts": { - "compile:lib": "npx truffle compile --config truffle-config-lib.js", - "compile:legacy:1.3": "npx truffle compile --config truffle-config-contracts-legacy-1.3.js", - "compile:legacy:1.6": "npx truffle compile --config truffle-config-contracts-legacy-1.6.js", - "compile:legacy": "npm run compile:legacy:1.3 && npm run compile:legacy:1.6", + "compile:lib_0.5": "npx truffle compile --config truffle-config-lib-0.5.js", + "compile:lib_0.7": "npx truffle compile --config truffle-config-lib-0.7.js", + "compile:lib": "npm run compile:lib_0.5 && npm run compile:lib_0.7", "compile:infrastructure_0.5": "npx truffle compile --config truffle-config-infrastructure-0.5.js", - "compile:infrastructure_0.6": "npx truffle compile --config truffle-config-infrastructure.js", - "compile:infrastructure": "npm run compile:infrastructure_0.5 && npm run compile:infrastructure_0.6", - "compile:modules": "npx truffle compile --config truffle-config-modules.js && rm build/contracts/ApprovedTransfer.json && npx truffle compile --contracts_directory=./contracts/modules/ApprovedTransfer*.sol && rm build/contracts/RelayerManager.json && npx truffle compile --contracts_directory=./contracts/modules/RelayerManager*.sol && rm build/contracts/TransferManager.json && npx truffle compile --contracts_directory=./contracts/modules/TransferManager*.sol && rm build/contracts/TokenExchanger.json && npx truffle compile --contracts_directory=./contracts/modules/TokenExchanger*.sol", + "compile:infrastructure_0.8": "npx truffle compile --config truffle-config-infrastructure.js", + "compile:infrastructure": "npm run compile:infrastructure_0.5 && npm run compile:infrastructure_0.8", + "compile:modules": "npx truffle compile --config truffle-config-modules.js", "compile:wallet": "npx truffle compile --config truffle-config-wallet.js", "compile:test": "npx truffle compile --config truffle-config-contracts-test.js", "compile": "npm run compile:infrastructure && npm run compile:modules && npm run compile:wallet", - "cc": "rm -rf build && rm -rf build-legacy && npm run compile:lib && npm run compile && npm run compile:legacy && npm run compile:test && npm run provision:lib:artefacts", - "ganache": "npx ganache-cli --chainId 1895 --networkId 1597649375983 --gasLimit=20700000 -e 10000 --acctKeys=\"./ganache-accounts.json\" --deterministic", - "kovan-fork": "npx ganache-cli -i 42 -l 20700000 -f https://kovan.infura.io/v3/$(cat .env | sed -En 's/INFURA_KEY=''\"''([^''\"'']+)''\"''/\\1/p')@16988040", - "kovan-fork-latest": "npx ganache-cli -i 42 -l 20700000 -f https://kovan.infura.io/v3/$(cat .env | sed -En 's/INFURA_KEY=''\"''([^''\"'']+)''\"''/\\1/p')", + "cc": "rm -rf build && npm run compile:lib && npm run compile && npm run compile:test && npm run provision:lib:artefacts", + "ganache": "npx ganache-cli --chainId 1895 --networkId 1597649375983 --gasLimit=20700000 --gasPrice 0 -e 10000 --acctKeys=\"./ganache-accounts.json\" --deterministic", + "mainnet-fork": "bash ./scripts/start_mainnet_fork.sh", "test": "npx truffle test --compile-none", "ctest": "npm run compile && npm run test", + "test:integration": "npx truffle test --compile-none --network prodFork test-integration/*", "provision:lib:artefacts": "bash ./scripts/provision_lib_artefacts.sh", - "test:coverage": "COVERAGE=1 node scripts/coverage.js && istanbul check-coverage --statements 82 --branches 78 --functions 82 --lines 82", + "test:coverage": "COVERAGE=1 node scripts/coverage.js && istanbul check-coverage --branches 85 --lines 93", "lint:js": "eslint .", "lint:contracts": "npx solhint contracts/**/*.sol && npx solhint contracts/**/**/*.sol", - "test:deployment": "./scripts/deploy.sh --no-compile development `seq 1 6`", + "test:deployment": "./scripts/deploy.sh --no-compile development `seq 1 8`", "validate:erc20": "bash ./scripts/validate_erc20.sh", - "security:slither": "npm run security:slither:infrastructure_0.5 && npm run security:slither:infrastructure_0.6 && npm run security:slither:modules && npm run security:slither:wallet", + "security:slither": "npm run security:slither:infrastructure_0.5 && npm run security:slither:infrastructure && npm run security:slither:modules && npm run security:slither:wallet", "security:slither:infrastructure_0.5": "rm -rf build && npm run compile:infrastructure_0.5 && slither .", - "security:slither:infrastructure_0.6": "rm -rf build && npm run compile:infrastructure_0.6 && slither .", + "security:slither:infrastructure": "rm -rf build && npm run compile:infrastructure_0.8 && slither .", "security:slither:modules": "rm -rf build && npx truffle compile --config truffle-config-modules.js && slither .", "security:slither:wallet": "rm -rf build && npm run compile:wallet && slither .", "security:slither:infrastructure_0.5:triage": "rm -rf build && npm run compile:infrastructure_0.5 && slither . --triage-mode", - "security:slither:infrastructure_0.6:triage": "rm -rf build && npm run compile:infrastructure_0.6 && slither . --triage-mode", + "security:slither:infrastructure:triage": "rm -rf build && npm run compile:infrastructure_0.8 && slither . --triage-mode", "security:slither:modules:triage": "rm -rf build && npx truffle compile --config truffle-config-modules.js && slither . --triage-mode", "security:slither:wallet:triage": "rm -rf build && npm run compile:wallet && slither . --triage-mode" }, @@ -60,9 +59,10 @@ }, "homepage": "https://github.com/argentlabs/argent-contracts#readme", "dependencies": { - "@openzeppelin/contracts": "3.0.1", + "@openzeppelin/contracts": "^4.0.0", "@uniswap/lib": "^1.1.2", "@uniswap/v2-core": "^1.0.1", + "@uniswap/v2-periphery": "^1.1.0-beta.0", "ajv": "^6.10.0", "aws-sdk": "^2.799.0", "child_process": "^1.0.2", @@ -70,7 +70,7 @@ "fs": "0.0.1-security", "inquirer": "^7.0.0", "node-fetch": "^2.6.1", - "openzeppelin-solidity": "2.3", + "openzeppelin-solidity": "3.4.0", "semver": "^7.1.3", "util": "^0.12.3" }, @@ -94,7 +94,7 @@ "lint-staged": "^10.5.2", "mocha-junit-reporter": "^2.0.0", "solhint": "^3.3.2", - "solidity-coverage": "^0.7.12", + "solidity-coverage": "^0.7.16", "truffle": "5.1.48", "truffle-assertions": "^0.9.2", "truffle-flatten": "^1.0.8", diff --git a/scripts/coverage.js b/scripts/coverage.js index 43afb2e4f..77e8b00c3 100644 --- a/scripts/coverage.js +++ b/scripts/coverage.js @@ -26,12 +26,12 @@ async function coverage(){ // Configs // ======= const configs = [ - "truffle-config-lib.js", + "truffle-config-lib-0.5.js", + "truffle-config-lib-0.7.js", "truffle-config-infrastructure-0.5.js", "truffle-config-infrastructure.js", "truffle-config-modules.js", "truffle-config-wallet.js", - //"truffle-config-contracts-legacy-1.6.js", // (Takes forever?) "truffle-config-contracts-test.js" ] @@ -122,8 +122,8 @@ async function coverage(){ ); // Copy Uniswap pre-compiles into temp build folder - const exchangePath = path.join(process.cwd(), 'lib/uniswap/UniswapExchange.json') - const factoryPath = path.join(process.cwd(), 'lib/uniswap/UniswapFactory.json') + const exchangePath = path.join(process.cwd(), 'lib_0.5/uniswap/UniswapExchange.json') + const factoryPath = path.join(process.cwd(), 'lib_0.5/uniswap/UniswapFactory.json') shell.cp(exchangePath, config.contracts_build_directory); shell.cp(factoryPath, config.contracts_build_directory); @@ -196,5 +196,5 @@ coverage() .then(() => process.exit(0)) .catch(err => { console.log(err); - process.exit(err) + process.exit(1) }); diff --git a/scripts/deploy_filter.js b/scripts/deploy_filter.js new file mode 100644 index 000000000..3b37a1216 --- /dev/null +++ b/scripts/deploy_filter.js @@ -0,0 +1,155 @@ +/* global artifacts */ +global.web3 = web3; +global.artifacts = artifacts; + +const ethers = require("ethers"); +const BN = require("bn.js"); + +const DappRegistry = artifacts.require("DappRegistry"); +const MultiSig = artifacts.require("MultiSigWallet"); + +const IAugustusSwapper = artifacts.require("IAugustusSwapper"); +const ParaswapFilter = artifacts.require("ParaswapFilter"); +const ParaswapUniV2RouterFilter = artifacts.require("ParaswapUniV2RouterFilter"); +const OnlyApproveFilter = artifacts.require("OnlyApproveFilter"); + +const deployManager = require("../utils/deploy-manager.js"); +const MultisigExecutor = require("../utils/multisigexecutor.js"); + +const main = async () => { + const { deploymentAccount, configurator } = await deployManager.getProps(); + + // ////////////////////////////////// + // Setup + // ////////////////////////////////// + + const { config } = configurator; + const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); + const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); + const DappRegistryWrapper = await DappRegistry.at(config.contracts.DappRegistry); + + const idx = process.argv.indexOf("--force"); + const force = (idx !== -1); + + const installFilter = async ({ filterDeployer, dapp, dappName = "Dapp", filterName = "Filter", registryId = 0 }) => { + const timelock = 1000 * parseInt((await DappRegistryWrapper.timelockPeriod()).toString(16), 16); + const { filter } = await DappRegistryWrapper.getAuthorisation(registryId, dapp); + const [filterStr, dappStr] = [`${filterName}@${filter}`, `${dappName}@${dapp}`]; + if (filter === ethers.constants.AddressZero) { + const newFilter = await filterDeployer(); + console.log(`Adding ${filterName}@${newFilter} for ${dappStr}`); + await multisigExecutor.executeCall(DappRegistryWrapper, "addDapp", [registryId, dapp, newFilter]); + console.log(`Done. Filter will be active on ${(new Date(Date.now() + timelock)).toLocaleString()}\n`); + } else { + const pendingUpdate = await DappRegistryWrapper.pendingFilterUpdates(registryId, dapp); + const pendingUpdateConfirmationTime = 1000 * parseInt(new BN(pendingUpdate.slice(2), 16).maskn(64).toString(16), 16); + const pendingUpdateFilterAddress = `0x${pendingUpdate.slice(10, 50)}`; + if (pendingUpdate === ethers.constants.HashZero) { + if (force) { + const newFilter = await filterDeployer(); + console.log(`Requesting replacement of ${filterStr} by ${filterName}@${newFilter} for ${dappStr}`); + await multisigExecutor.executeCall(DappRegistryWrapper, "requestFilterUpdate", [registryId, dapp, newFilter]); + console.log( + `Done. Pending filter update will be confirmable on ${new Date(Date.now() + timelock).toLocaleString()}\n` + ); + } else { + console.log(`Existing filter ${filterStr} found for ${dappStr}. Use --force to request its replacement\n`); + } + } else if (Date.now() < pendingUpdateConfirmationTime) { + const confTime = new Date(pendingUpdateConfirmationTime).toLocaleString(); + console.log( + `Pending installation of ${filterName}@${pendingUpdateFilterAddress} for ${dappStr} will be confirmable on ${confTime}\n` + ); + } else { + console.log(`Confirming installation of ${filterName}@${pendingUpdateFilterAddress} for ${dappStr}`); + await multisigExecutor.executeCall(DappRegistryWrapper, "confirmFilterUpdate", [registryId, dapp]); + console.log("Done.\n"); + } + } + }; + + const getFilterFromConfigOrDeployNew = async (filterArtifact) => { + const { contractName } = filterArtifact._json; + if (!config.filters[contractName] || config.filters[contractName] === ethers.constants.AddressZero) { + console.log(`Deploying ${contractName}`); + const wrapper = await filterArtifact.new(); + console.log(`Deployed ${contractName} at ${wrapper.address}\n`); + configurator.updateFilterAddresses({ [contractName]: wrapper.address }); + await configurator.save(); + return wrapper.address; + } + return config.filters[contractName]; + }; + + // ////////////////////////////////// + // Deploy and add filters to Argent Registry + // ////////////////////////////////// + + // Paraswap Proxy + const AugustusSwapperWrapper = await IAugustusSwapper.at(config.defi.paraswap.contract); + await installFilter({ + filterDeployer: async () => getFilterFromConfigOrDeployNew(OnlyApproveFilter), + dapp: await AugustusSwapperWrapper.getTokenTransferProxy(), + dappName: "ParaswapTokenTransferProxy", + filterName: "OnlyApproveFilter" + }); + + // Paraswap + await installFilter({ + filterDeployer: async () => { + console.log("Deploying ParaswapFilter"); + const ParaswapFilterWrapper = await ParaswapFilter.new( + config.contracts.TokenRegistry, + config.contracts.DappRegistry, + config.defi.paraswap.contract, + config.defi.paraswap.uniswapProxy, + config.defi.paraswap.uniswapForks.map((f) => f.factory), + config.defi.paraswap.uniswapForks.map((f) => f.initCode), + [ + config.defi.paraswap.adapters.uniswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.uniswapV2 || ethers.constants.AddressZero, + config.defi.paraswap.adapters.sushiswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.linkswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.defiswap || ethers.constants.AddressZero, + config.defi.paraswap.adapters.zeroexV2 || ethers.constants.AddressZero, + config.defi.paraswap.adapters.zeroexV4 || ethers.constants.AddressZero + ], + Object.values(config.defi.paraswap.targetExchanges || {}), + config.defi.paraswap.marketMakers || [], + ); + console.log(`Deployed ParaswapFilter at ${ParaswapFilterWrapper.address}`); + return ParaswapFilterWrapper.address; + }, + dapp: config.defi.paraswap.contract, + dappName: "Augustus", + filterName: "ParaswapFilter" + }); + + // ParaswapUniV2RouterFilter + if (config.defi.uniswap.paraswapUniV2Router) { + const factories = [config.defi.uniswap.factoryV2, ...config.defi.paraswap.uniswapForks.map((f) => f.factory)]; + const initCodes = [config.defi.uniswap.initCodeV2, ...config.defi.paraswap.uniswapForks.map((f) => f.initCode)]; + const routers = [config.defi.uniswap.paraswapUniV2Router, ...config.defi.paraswap.uniswapForks.map((f) => f.paraswapUniV2Router)]; + for (let i = 0; i < routers.length; i += 1) { + await installFilter({ + filterDeployer: async () => { + console.log(`Deploying ParaswapUniV2RouterFilter#${i}`); + const ParaswapUniV2RouterFilterWrapper = await ParaswapUniV2RouterFilter.new( + config.contracts.TokenRegistry, + factories[i], + initCodes[i], + config.defi.weth + ); + console.log(`Deployed ParaswapUniV2RouterFilter#${i} at ${ParaswapUniV2RouterFilterWrapper.address}`); + return ParaswapUniV2RouterFilterWrapper.address; + }, + dapp: routers[i], + dappName: `ParaswapUniV2Router#${i}`, + filterName: `ParaswapUniV2RouterFilter#${i}` + }); + } + } +}; + +// For truffle exec +module.exports = (cb) => main().then(cb).catch(cb); diff --git a/scripts/deploy_wallet_detector.js b/scripts/deploy_wallet_detector.js index 976998579..a94af1f6a 100644 --- a/scripts/deploy_wallet_detector.js +++ b/scripts/deploy_wallet_detector.js @@ -2,50 +2,63 @@ // Script to deploy a new ArgentWalletDectector. // // To deploy a new ArgentWalletDectector: -// ./execute_script.sh deploy_wallet_detector.js +// bash ./scripts/execute_script.sh --no-compile scripts/deploy_wallet_detector.js // // where: // - network = [test, staging, prod] // //////////////////////////////////////////////////////////////////// /* global artifacts */ +global.web3 = web3; const ArgentWalletDetector = artifacts.require("ArgentWalletDetector"); -const { configurator, abiUploader } = require("deploy-manager.js"); +const deployManager = require("../utils/deploy-manager.js"); const PROXYWALLET_CODEHASH = [ "0x0b44c9be520023d9f6091278e7e5a8853257eb9fb3d78e6951315df59679e3b2", // factory prod Mar-30-2020 "0x83baa4b265772664a88dcfc8be0e24e1fe969a3c66f03851c6aa2f5da73cd7fd", // factory prod Feb-04-2019 ]; -const BASEWALLET_IMPL = [ - "0xb1dd690cc9af7bb1a906a9b5a94f94191cc553ce", // prod Feb-04-2019 - "0xb6d64221451edbac7736d4c3da7fc827457dec03", // prod Mar-30-2020 - "0x8cbe893fb3372e3ce1e63ad0262b2a544fa1fb9c", // staging Jan-24-2019 - "0x609282d2d8f9ba4bb87ac9c38de20ed5de86596b", // staging Dec-06-2019 - "0xb11da8fbd8126f4f66c093070ecb8316734a7130", // staging Mar-10-2020 -]; // mainnet only - async function main() { + const { network, configurator, abiUploader } = await deployManager.getProps(); const { config } = configurator; + let BASEWALLET_IMPL; + if (network === "test") { + BASEWALLET_IMPL = [ + "0xA1832B5D79bdbbA645Fd6969275Ee1c6CF503E99", + "0x1C26ef883464e265F3bcaE751Dab5D855F458b25", + "0xB6E572129e4E749552dB93EB996BD9655fB758B1", + "0xdC1378831cd5244FafcE5783187334122cFA7f35", + "0xd35fB09F16Ad78f6238bF28D7ffCA1AC4b72Df69" + ]; + } else { + BASEWALLET_IMPL = [ + "0xb1dd690cc9af7bb1a906a9b5a94f94191cc553ce", // prod Feb-04-2019 + "0xb6d64221451edbac7736d4c3da7fc827457dec03", // prod Mar-30-2020 + "0x8cbe893fb3372e3ce1e63ad0262b2a544fa1fb9c", // staging Jan-24-2019 + "0x609282d2d8f9ba4bb87ac9c38de20ed5de86596b", // staging Dec-06-2019 + "0xb11da8fbd8126f4f66c093070ecb8316734a7130", // staging Mar-10-2020 + ]; // mainnet only used both for staging and prod + } + // Deploy ArgentWalletDetector contract console.log("Deploying ArgentWalletDetector..."); - const ArgentWalletDetectortWrapper = await ArgentWalletDetector.new(PROXYWALLET_CODEHASH, BASEWALLET_IMPL); + const ArgentWalletDetectorWrapper = await ArgentWalletDetector.new(PROXYWALLET_CODEHASH, BASEWALLET_IMPL); // Transfer ownership to the multisig console.log("Transferring ownership to the Multisig..."); - await ArgentWalletDetectortWrapper.changeOwner(config.contracts.MultiSigWallet); + await ArgentWalletDetectorWrapper.changeOwner(config.contracts.MultiSigWallet); // Update config configurator.updateInfrastructureAddresses({ - ArgentWalletDetector: ArgentWalletDetectortWrapper.contractAddress, + ArgentWalletDetector: ArgentWalletDetectorWrapper.contractAddress, }); await configurator.save(); // Upload ABI await Promise.all([ - abiUploader.upload(ArgentWalletDetectortWrapper, "contracts"), + abiUploader.upload(ArgentWalletDetectorWrapper, "contracts"), ]); } diff --git a/scripts/provision_lib_artefacts.sh b/scripts/provision_lib_artefacts.sh index 60bbbc85d..26f8296e5 100644 --- a/scripts/provision_lib_artefacts.sh +++ b/scripts/provision_lib_artefacts.sh @@ -3,5 +3,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd $DIR cd .. -cp lib/uniswap/UniswapExchange.json build/contracts/UniswapExchange.json -cp lib/uniswap/UniswapFactory.json build/contracts/UniswapFactory.json \ No newline at end of file +cp lib_0.5/uniswap/UniswapExchange.json build/contracts/UniswapExchange.json +cp lib_0.5/uniswap/UniswapFactory.json build/contracts/UniswapFactory.json \ No newline at end of file diff --git a/scripts/set_tradable_tokens.js b/scripts/set_tradable_tokens.js index e63ed1ffe..531d6f2c5 100644 --- a/scripts/set_tradable_tokens.js +++ b/scripts/set_tradable_tokens.js @@ -16,7 +16,7 @@ global.web3 = web3; const fs = require("fs"); const MultiSig = artifacts.require("MultiSigWallet"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); +const TokenRegistry = artifacts.require("TokenRegistry"); const MultisigExecutor = require("../utils/multisigexecutor.js"); const deployManager = require("../utils/deploy-manager.js"); @@ -34,14 +34,14 @@ async function main() { const accounts = await web3.eth.getAccounts(); const deploymentAccount = accounts[0]; - const TokenPriceRegistryWrapper = await TokenPriceRegistry.at(config.modules.TokenPriceRegistry); + const TokenRegistryWrapper = await TokenRegistry.at(config.modules.TokenRegistry); const data = JSON.parse(fs.readFileSync(input, "utf8")); const addresses = Object.keys(data); console.log(`${addresses.length} tokens provided in ${input}`); - const tradableStatus = await TokenPriceRegistryWrapper.getTradableForTokenList(addresses); - const priceStatus = await TokenPriceRegistryWrapper.getPriceForTokenList(addresses); + const tradableStatus = await TokenRegistryWrapper.getTradableForTokenList(addresses); + const priceStatus = await TokenRegistryWrapper.getPriceForTokenList(addresses); // we only update tokens meeting those two conditions: // (1) tradable flag is different than the one on chain @@ -60,7 +60,7 @@ async function main() { const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); - const receipt = await multisigExecutor.executeCall(TokenPriceRegistryWrapper, "setTradableForTokenList", [tokens, tradable]); + const receipt = await multisigExecutor.executeCall(TokenRegistryWrapper, "setTradableForTokenList", [tokens, tradable]); console.log(receipt.transactionHash); } diff --git a/scripts/start_mainnet_fork.sh b/scripts/start_mainnet_fork.sh new file mode 100644 index 000000000..d603b7649 --- /dev/null +++ b/scripts/start_mainnet_fork.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +if [ -z "$CI" ]; then + source .env + + lsof -i tcp:3601 | grep LISTEN | awk '{print $2}' | xargs kill +fi + +# Exit script as soon as a command fails. +set -o errexit + +node_modules/.bin/ganache-cli --chainId 1895 --port 3601 --gasPrice 0 --deterministic --fork https://mainnet.infura.io/v3/"$INFURA_KEY" --unlock "0xe982615d461dd5cd06575bbea87624fda4e3de17" \ No newline at end of file diff --git a/scripts/update_compound_registry.js b/scripts/update_compound_registry.js deleted file mode 100644 index 707e0c932..000000000 --- a/scripts/update_compound_registry.js +++ /dev/null @@ -1,62 +0,0 @@ -// /////////////////////////////////////////////////////////////////// -// Script to add/remove CTokens on Compound -// -// To add a token: -// ./execute_script.sh update_compound_registry.js --add --token --ctoken -// -// To remove a token: -// ./execute_script.sh update_compound_registry.js --remove --token -// -// where: -// - network = [development, test, staging, prod] -// //////////////////////////////////////////////////////////////////// - -/* global artifacts */ -const CompoundRegistry = artifacts.require("CompoundRegistry"); -const MultiSig = artifacts.require("MultiSigWallet"); - -const deployManager = require("../utils/deploy-manager.js"); -const MultisigExecutor = require("../utils/multisigexecutor.js"); - -async function main() { - let add; - - // Read Command Line Arguments - let idx = process.argv.indexOf("--add"); - if (idx > 0) { - add = true; - } else { - idx = process.argv.indexOf("--remove"); - if (idx > 0) { - add = false; - } else { - console.log("Error: Use --add or --remove to add or remove tokens from Compound"); - return; - } - } - - idx = process.argv.indexOf("--token"); - const token = process.argv[idx + 1]; - - idx = process.argv.indexOf("--ctoken"); - const ctoken = process.argv[idx + 1]; - - const { configurator } = await deployManager.getProps(); - const { config } = configurator; - const accounts = await web3.eth.getAccounts(); - const deploymentAccount = accounts[0]; - - const CompoundRegistryWrapper = await CompoundRegistry.at(config.contracts.CompoundRegistry); - const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); - const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); - - if (add) { - console.log(`Adding token ${token} to Compound`); - await multisigExecutor.executeCall(CompoundRegistryWrapper, "addCToken", [token, ctoken]); - } else { - console.log(`Removing token ${token} from Compound`); - await multisigExecutor.executeCall(CompoundRegistryWrapper, "removeCToken", [token]); - } -} - -module.exports = (cb) => main().then(cb).catch(cb); diff --git a/scripts/update_dex_registry.js b/scripts/update_dex_registry.js index 2aca57b27..0f18c2c1b 100644 --- a/scripts/update_dex_registry.js +++ b/scripts/update_dex_registry.js @@ -1,5 +1,5 @@ // /////////////////////////////////////////////////////////////////// -// Script to register and deregister DEXes in the DexRegistry contract +// Script to register and deregister DEXes in the ParaswapFilter contract // // Can be executed as: // ./execute_script.sh update_dex_registry.js --dex = @@ -13,7 +13,7 @@ global.web3 = web3; const MultiSig = artifacts.require("MultiSigWallet"); -const DexRegistry = artifacts.require("DexRegistry"); +const ParaswapFilter = artifacts.require("ParaswapFilter"); const deployManager = require("../utils/deploy-manager.js"); const MultisigExecutor = require("../utils/multisigexecutor.js"); @@ -39,12 +39,12 @@ async function main() { const { deploymentAccount, configurator } = await deployManager.getProps(); const { config } = configurator; - const DexRegistryWrapper = await DexRegistry.at(config.contracts.DexRegistry); + const ParaswapFilterWrapper = await ParaswapFilter.at(config.contracts.ParaswapFilter); const MultiSigWrapper = await MultiSig.at(config.contracts.MultiSigWallet); const multisigExecutor = new MultisigExecutor(MultiSigWrapper, deploymentAccount, config.multisig.autosign); console.log(`Updating registry for dex [${dexAddress}] with value [${dexStatus}]`); - await multisigExecutor.executeCall(DexRegistryWrapper, "setAuthorised", [dexAddress, dexStatus]); + await multisigExecutor.executeCall(ParaswapFilterWrapper, "setAuthorised", [dexAddress, dexStatus]); } module.exports = (cb) => main().then(cb).catch(cb); diff --git a/scripts/verify.js b/scripts/verify.js index 2815a360c..e7bfb4818 100644 --- a/scripts/verify.js +++ b/scripts/verify.js @@ -45,6 +45,14 @@ async function main() { await execVerify(contractName, contractAddress, network); } + for (const [contractName, value] of Object.entries(configuration.filters)) { + const contractAddresses = [].concat(...[value]); // value can be an address or array of addresses + for (const addr of contractAddresses) { + console.log(`Verifying ${contractName} at ${addr} ...`); + await execVerify(contractName, addr, network); + } + } + for (const [moduleName, moduleAddress] of Object.entries(configuration.modules)) { await execVerify(moduleName, moduleAddress, network); } diff --git a/slither.db.json b/slither.db.json index 59abd8de4..1c9ee0a2c 100644 --- a/slither.db.json +++ b/slither.db.json @@ -1 +1 @@ -[{"elements": [{"type": "contract", "name": "GuardianStorage", "source_mapping": {"start": 1192, "length": 4511, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152], "starting_column": 1, "ending_column": 0}}, {"type": "node", "name": "config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1)", "source_mapping": {"start": 2192, "length": 76, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [60], "starting_column": 9, "ending_column": 85}, "type_specific_fields": {"parent": {"type": "function", "name": "addGuardian", "source_mapping": {"start": 1985, "length": 290, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [57, 58, 59, 60, 61], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "GuardianStorage", "source_mapping": {"start": 1192, "length": 4511, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152], "starting_column": 1, "ending_column": 0}}, "signature": "addGuardian(address,address)"}}}}], "description": "GuardianStorage (infrastructure_0.5/storage/GuardianStorage.sol#29-152) contract sets array length with a user-controlled value:\n\t- config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1) (infrastructure_0.5/storage/GuardianStorage.sol#60)\n", "markdown": "[GuardianStorage](contracts/infrastructure_0.5/storage/GuardianStorage.sol#L29-L152) contract sets array length with a user-controlled value:\n\t- [config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1)](contracts/infrastructure_0.5/storage/GuardianStorage.sol#L60)\n", "id": "fa034692f14355e0014bde5fb278b738b994c91a2b53e322fc4fcb87466cdcd6", "check": "controlled-array-length", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "contract", "name": "MakerRegistry", "source_mapping": {"start": 311, "length": 2826, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/MakerRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_short": "infrastructure_0.5/MakerRegistry.sol", "is_dependency": false, "lines": [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94], "starting_column": 1, "ending_column": 0}}, {"type": "node", "name": "collaterals[token].index = uint128(tokens.push(token) - 1)", "source_mapping": {"start": 1294, "length": 58, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/MakerRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_short": "infrastructure_0.5/MakerRegistry.sol", "is_dependency": false, "lines": [40], "starting_column": 9, "ending_column": 67}, "type_specific_fields": {"parent": {"type": "function", "name": "addCollateral", "source_mapping": {"start": 949, "length": 620, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/MakerRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_short": "infrastructure_0.5/MakerRegistry.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerRegistry", "source_mapping": {"start": 311, "length": 2826, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/MakerRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MakerRegistry.sol", "filename_short": "infrastructure_0.5/MakerRegistry.sol", "is_dependency": false, "lines": [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94], "starting_column": 1, "ending_column": 0}}, "signature": "addCollateral(JoinLike)"}}}}], "description": "MakerRegistry (infrastructure_0.5/MakerRegistry.sol#10-94) contract sets array length with a user-controlled value:\n\t- collaterals[token].index = uint128(tokens.push(token) - 1) (infrastructure_0.5/MakerRegistry.sol#40)\n", "markdown": "[MakerRegistry](contracts/infrastructure_0.5/MakerRegistry.sol#L10-L94) contract sets array length with a user-controlled value:\n\t- [collaterals[token].index = uint128(tokens.push(token) - 1)](contracts/infrastructure_0.5/MakerRegistry.sol#L40)\n", "id": "62b0f5aa6e2702b1a85cfe9121f4500d53ce1b183fbef8cf8f4c5cbf99caf338", "check": "controlled-array-length", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340], "starting_column": 1, "ending_column": 0}}, {"type": "node", "name": "staticCallSignatures[newVersion].push(sigs[j])", "source_mapping": {"start": 5753, "length": 46, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [133], "starting_column": 17, "ending_column": 63}, "type_specific_fields": {"parent": {"type": "function", "name": "addVersion", "source_mapping": {"start": 5248, "length": 970, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340], "starting_column": 1, "ending_column": 0}}, "signature": "addVersion(address[],address[])"}}}}], "description": "VersionManager (modules/VersionManager.sol#35-340) contract sets array length with a user-controlled value:\n\t- staticCallSignatures[newVersion].push(sigs[j]) (modules/VersionManager.sol#133)\n", "markdown": "[VersionManager](contracts/modules/VersionManager.sol#L35-L340) contract sets array length with a user-controlled value:\n\t- [staticCallSignatures[newVersion].push(sigs[j])](contracts/modules/VersionManager.sol#L133)\n", "id": "06b19b97c01d7b22267e1c6a65e160e122e9dcf2e92c23d1d730fa9b78aa6a28", "check": "controlled-array-length", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "setAddr", "source_mapping": {"start": 986, "length": 54, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [26], "starting_column": 5, "ending_column": 59}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setAddr(bytes32,address)"}}, {"type": "function", "name": "setAddr", "source_mapping": {"start": 1727, "length": 150, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [49, 50, 51, 52], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "setAddr(bytes32,address)"}}], "description": "setAddr(bytes32,address) should be declared external:\n\t- ENSResolver.setAddr(bytes32,address) (infrastructure_0.5/ens/ENSResolver.sol#26)\n\t- ArgentENSResolver.setAddr(bytes32,address) (infrastructure_0.5/ens/ArgentENSResolver.sol#49-52)\n", "markdown": "setAddr(bytes32,address) should be declared external:\n\t- [ENSResolver.setAddr(bytes32,address)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L26)\n\t- [ArgentENSResolver.setAddr(bytes32,address)](contracts/infrastructure_0.5/ens/ArgentENSResolver.sol#L49-L52)\n", "id": "a54fad1af87f1a6a0618d5f675a0d50bffa77f611b1ede38243c11a9d5e9938f", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setName", "source_mapping": {"start": 2050, "length": 156, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [59, 60, 61, 62], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "setName(bytes32,string)"}}, {"type": "function", "name": "setName", "source_mapping": {"start": 1115, "length": 60, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [28], "starting_column": 5, "ending_column": 65}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setName(bytes32,string)"}}], "description": "setName(bytes32,string) should be declared external:\n\t- ArgentENSResolver.setName(bytes32,string) (infrastructure_0.5/ens/ArgentENSResolver.sol#59-62)\n\t- ENSResolver.setName(bytes32,string) (infrastructure_0.5/ens/ENSResolver.sol#28)\n", "markdown": "setName(bytes32,string) should be declared external:\n\t- [ArgentENSResolver.setName(bytes32,string)](contracts/infrastructure_0.5/ens/ArgentENSResolver.sol#L59-L62)\n\t- [ENSResolver.setName(bytes32,string)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L28)\n", "id": "889fa15ea763f7ab757b5cab54c5f9a30efb53458ff59852ecfd417acb677690", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "init", "source_mapping": {"start": 2042, "length": 814, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "UpgraderToVersionManager", "source_mapping": {"start": 1186, "length": 1794, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}, {"type": "function", "name": "init", "source_mapping": {"start": 9371, "length": 71, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [221], "starting_column": 5, "ending_column": 76}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}], "description": "init(address) should be declared external:\n\t- UpgraderToVersionManager.init(address) (modules/UpgraderToVersionManager.sol#59-76)\n\t- VersionManager.init(address) (modules/VersionManager.sol#221)\n", "markdown": "init(address) should be declared external:\n\t- [UpgraderToVersionManager.init(address)](contracts/modules/UpgraderToVersionManager.sol#L59-L76)\n\t- [VersionManager.init(address)](contracts/modules/VersionManager.sol#L221)\n", "id": "0030dd7b0d3b956a7d73c00756a1fd090ef528b714ee99baefae399f2abf3e9a", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "init", "source_mapping": {"start": 2042, "length": 814, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "UpgraderToVersionManager", "source_mapping": {"start": 1186, "length": 1794, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}], "description": "init(address) should be declared external:\n\t- UpgraderToVersionManager.init(address) (modules/UpgraderToVersionManager.sol#59-76)\n", "markdown": "init(address) should be declared external:\n\t- [UpgraderToVersionManager.init(address)](contracts/modules/UpgraderToVersionManager.sol#L59-L76)\n", "id": "a4356db4c4ecdac21d0d157f2d2d4686f31157d6f65dbec9f1038945cd5c6cc7", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addr", "source_mapping": {"start": 922, "length": 59, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [25], "starting_column": 5, "ending_column": 64}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "addr(bytes32)"}}], "description": "addr(bytes32) should be declared external:\n\t- ENSResolver.addr(bytes32) (infrastructure_0.5/ens/ENSResolver.sol#25)\n", "markdown": "addr(bytes32) should be declared external:\n\t- [ENSResolver.addr(bytes32)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L25)\n", "id": "65a9765d424d9d4480e74cd9cfb9d78123aa0f3b0204aaff31cb02a86874b30d", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setAddr", "source_mapping": {"start": 986, "length": 54, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [26], "starting_column": 5, "ending_column": 59}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setAddr(bytes32,address)"}}], "description": "setAddr(bytes32,address) should be declared external:\n\t- ENSResolver.setAddr(bytes32,address) (infrastructure_0.5/ens/ENSResolver.sol#26)\n", "markdown": "setAddr(bytes32,address) should be declared external:\n\t- [ENSResolver.setAddr(bytes32,address)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L26)\n", "id": "7dd4eec33d71d3e6f85e1f000726a7decf23e9f39a90a6d8743055067436374b", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "name", "source_mapping": {"start": 1045, "length": 65, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [27], "starting_column": 5, "ending_column": 70}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "name(bytes32)"}}], "description": "name(bytes32) should be declared external:\n\t- ENSResolver.name(bytes32) (infrastructure_0.5/ens/ENSResolver.sol#27)\n", "markdown": "name(bytes32) should be declared external:\n\t- [ENSResolver.name(bytes32)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L27)\n", "id": "30eb413c4e864a0f24f74d17b6e0dbf77de62b2397ca6e600c8102cfc6364f3d", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setName", "source_mapping": {"start": 1115, "length": 60, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [28], "starting_column": 5, "ending_column": 65}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setName(bytes32,string)"}}], "description": "setName(bytes32,string) should be declared external:\n\t- ENSResolver.setName(bytes32,string) (infrastructure_0.5/ens/ENSResolver.sol#28)\n", "markdown": "setName(bytes32,string) should be declared external:\n\t- [ENSResolver.setName(bytes32,string)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L28)\n", "id": "fa3f558acee0b0e92984f41d330237f44d7fc1fc477597d55c11ed78c7d9bf80", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addr", "source_mapping": {"start": 922, "length": 59, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [25], "starting_column": 5, "ending_column": 64}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "addr(bytes32)"}}, {"type": "function", "name": "addr", "source_mapping": {"start": 2371, "length": 102, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [69, 70, 71], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "addr(bytes32)"}}], "description": "addr(bytes32) should be declared external:\n\t- ENSResolver.addr(bytes32) (infrastructure_0.5/ens/ENSResolver.sol#25)\n\t- ArgentENSResolver.addr(bytes32) (infrastructure_0.5/ens/ArgentENSResolver.sol#69-71)\n", "markdown": "addr(bytes32) should be declared external:\n\t- [ENSResolver.addr(bytes32)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L25)\n\t- [ArgentENSResolver.addr(bytes32)](contracts/infrastructure_0.5/ens/ArgentENSResolver.sol#L69-L71)\n", "id": "0f84a89e4f55b16204a109d2cba1adf10dd0ed0128a297978ed2bc8919b48492", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "name", "source_mapping": {"start": 1045, "length": 65, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [27], "starting_column": 5, "ending_column": 70}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "name(bytes32)"}}, {"type": "function", "name": "name", "source_mapping": {"start": 2640, "length": 108, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [78, 79, 80], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "name(bytes32)"}}], "description": "name(bytes32) should be declared external:\n\t- ENSResolver.name(bytes32) (infrastructure_0.5/ens/ENSResolver.sol#27)\n\t- ArgentENSResolver.name(bytes32) (infrastructure_0.5/ens/ArgentENSResolver.sol#78-80)\n", "markdown": "name(bytes32) should be declared external:\n\t- [ENSResolver.name(bytes32)](contracts/infrastructure_0.5/ens/ENSResolver.sol#L27)\n\t- [ArgentENSResolver.name(bytes32)](contracts/infrastructure_0.5/ens/ArgentENSResolver.sol#L78-L80)\n", "id": "bc8e751de6144569ae34c5883a9082b238907ba26bcb208fc3327d47f818b487", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "debt", "source_mapping": {"start": 15910, "length": 720, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "debt(uint256)"}}, {"type": "node", "name": "_fullRepayment = art.mul(rate).div(RAY).add(1).add(art - art.mul(rate).div(RAY).mul(RAY).div(rate))", "source_mapping": {"start": 16334, "length": 217, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [433, 434, 435], "starting_column": 13, "ending_column": 68}, "type_specific_fields": {"parent": {"type": "function", "name": "debt", "source_mapping": {"start": 15910, "length": 720, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "debt(uint256)"}}}}], "description": "MakerV2Loan.debt(uint256) (modules/maker/MakerV2Loan.sol#421-437) performs a multiplication on the result of a division:\n\t-_fullRepayment = art.mul(rate).div(RAY).add(1).add(art - art.mul(rate).div(RAY).mul(RAY).div(rate)) (modules/maker/MakerV2Loan.sol#433-435)\n", "markdown": "[MakerV2Loan.debt(uint256)](contracts/modules/maker/MakerV2Loan.sol#L421-L437) performs a multiplication on the result of a division:\n\t-[_fullRepayment = art.mul(rate).div(RAY).add(1).add(art - art.mul(rate).div(RAY).mul(RAY).div(rate))](contracts/modules/maker/MakerV2Loan.sol#L433-L435)\n", "id": "daa4142f349227eeaf685236dc5433ec6d70fa972360247494f316dac5b5b666", "check": "divide-before-multiply", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "closeLoan", "source_mapping": {"start": 5508, "length": 937, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "CompoundManager", "source_mapping": {"start": 2024, "length": 17780, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469], "starting_column": 1, "ending_column": 2}}, "signature": "closeLoan(address,bytes32)"}}, {"type": "node", "name": "collateral == 0", "source_mapping": {"start": 6085, "length": 15, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [144], "starting_column": 21, "ending_column": 36}, "type_specific_fields": {"parent": {"type": "function", "name": "closeLoan", "source_mapping": {"start": 5508, "length": 937, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "CompoundManager", "source_mapping": {"start": 2024, "length": 17780, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469], "starting_column": 1, "ending_column": 2}}, "signature": "closeLoan(address,bytes32)"}}}}], "description": "CompoundManager.closeLoan(address,bytes32) (modules/CompoundManager.sol#129-155) uses a dangerous strict equality:\n\t- collateral == 0 (modules/CompoundManager.sol#144)\n", "markdown": "[CompoundManager.closeLoan(address,bytes32)](contracts/modules/CompoundManager.sol#L129-L155) uses a dangerous strict equality:\n\t- [collateral == 0](contracts/modules/CompoundManager.sol#L144)\n", "id": "11cd45dd4e312ba1c2eaf16bd09666f25405955e9b2e3e5a956e7b9a9e99f485", "check": "incorrect-equality", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "function", "name": "exitMarketIfNeeded", "source_mapping": {"start": 19409, "length": 393, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [462, 463, 464, 465, 466, 467, 468], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "CompoundManager", "source_mapping": {"start": 2024, "length": 17780, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469], "starting_column": 1, "ending_column": 2}}, "signature": "exitMarketIfNeeded(address,address,address)"}}, {"type": "node", "name": "collateral == 0 && debt == 0", "source_mapping": {"start": 19646, "length": 28, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [465], "starting_column": 13, "ending_column": 41}, "type_specific_fields": {"parent": {"type": "function", "name": "exitMarketIfNeeded", "source_mapping": {"start": 19409, "length": 393, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [462, 463, 464, 465, 466, 467, 468], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "CompoundManager", "source_mapping": {"start": 2024, "length": 17780, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_relative": "contracts/modules/CompoundManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/CompoundManager.sol", "filename_short": "modules/CompoundManager.sol", "is_dependency": false, "lines": [46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469], "starting_column": 1, "ending_column": 2}}, "signature": "exitMarketIfNeeded(address,address,address)"}}}}], "description": "CompoundManager.exitMarketIfNeeded(address,address,address) (modules/CompoundManager.sol#462-468) uses a dangerous strict equality:\n\t- collateral == 0 && debt == 0 (modules/CompoundManager.sol#465)\n", "markdown": "[CompoundManager.exitMarketIfNeeded(address,address,address)](contracts/modules/CompoundManager.sol#L462-L468) uses a dangerous strict equality:\n\t- [collateral == 0 && debt == 0](contracts/modules/CompoundManager.sol#L465)\n", "id": "b586da6b7d0f435c718cb06c4e0f8ae67502620407dfd585276f04ce5a350288", "check": "incorrect-equality", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "function", "name": "notWhenRecovery", "source_mapping": {"start": 3001, "length": 164, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_relative": "contracts/modules/RecoveryManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_short": "modules/RecoveryManager.sol", "is_dependency": false, "lines": [76, 77, 78, 79], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "RecoveryManager", "source_mapping": {"start": 1285, "length": 7878, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_relative": "contracts/modules/RecoveryManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_short": "modules/RecoveryManager.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215], "starting_column": 1, "ending_column": null}}, "signature": "notWhenRecovery(address)"}}, {"type": "node", "name": "require(bool,string)(recoveryConfigs[_wallet].executeAfter == 0,RM: there cannot be an ongoing recovery)", "source_mapping": {"start": 3053, "length": 94, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_relative": "contracts/modules/RecoveryManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_short": "modules/RecoveryManager.sol", "is_dependency": false, "lines": [77], "starting_column": 9, "ending_column": 103}, "type_specific_fields": {"parent": {"type": "function", "name": "notWhenRecovery", "source_mapping": {"start": 3001, "length": 164, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_relative": "contracts/modules/RecoveryManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_short": "modules/RecoveryManager.sol", "is_dependency": false, "lines": [76, 77, 78, 79], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "RecoveryManager", "source_mapping": {"start": 1285, "length": 7878, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_relative": "contracts/modules/RecoveryManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RecoveryManager.sol", "filename_short": "modules/RecoveryManager.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215], "starting_column": 1, "ending_column": null}}, "signature": "notWhenRecovery(address)"}}}}], "description": "RecoveryManager.notWhenRecovery(address) (modules/RecoveryManager.sol#76-79) uses a dangerous strict equality:\n\t- require(bool,string)(recoveryConfigs[_wallet].executeAfter == 0,RM: there cannot be an ongoing recovery) (modules/RecoveryManager.sol#77)\n", "markdown": "[RecoveryManager.notWhenRecovery(address)](contracts/modules/RecoveryManager.sol#L76-L79) uses a dangerous strict equality:\n\t- [require(bool,string)(recoveryConfigs[_wallet].executeAfter == 0,RM: there cannot be an ongoing recovery)](contracts/modules/RecoveryManager.sol#L77)\n", "id": "99ff545bfd47b744fa4ee5fbd8df786146d9e07b106455d48b1317f7a9f3e531", "check": "incorrect-equality", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "function", "name": "giveVault", "source_mapping": {"start": 11163, "length": 335, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "giveVault(address,bytes32)"}}, {"type": "node", "name": "cdpManager.give(uint256(_loanId),msg.sender)", "source_mapping": {"start": 11404, "length": 45, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [304], "starting_column": 9, "ending_column": 54}, "type_specific_fields": {"parent": {"type": "function", "name": "giveVault", "source_mapping": {"start": 11163, "length": 335, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "giveVault(address,bytes32)"}}}, "additional_fields": {"underlying_type": "external_calls"}}, {"type": "node", "name": "onlyNewVersion()", "source_mapping": {"start": 11298, "length": 14, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [300], "starting_column": 9, "ending_column": 23}, "type_specific_fields": {"parent": {"type": "function", "name": "giveVault", "source_mapping": {"start": 11163, "length": 335, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "giveVault(address,bytes32)"}}}, "additional_fields": {"underlying_type": "external_calls"}}, {"type": "node", "name": "(success,res) = msg.sender.call(abi.encodeWithSignature(isNewVersion(address),address(this)))", "source_mapping": {"start": 3822, "length": 115, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [92], "starting_column": 9, "ending_column": 124}, "type_specific_fields": {"parent": {"type": "function", "name": "onlyNewVersion", "source_mapping": {"start": 3786, "length": 269, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [91, 92, 93, 94, 95], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "onlyNewVersion()"}}}, "additional_fields": {"underlying_type": "external_calls_sending_eth"}}, {"type": "node", "name": "clearLoanOwner(_wallet,_loanId)", "source_mapping": {"start": 11459, "length": 32, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [305], "starting_column": 9, "ending_column": 41}, "type_specific_fields": {"parent": {"type": "function", "name": "giveVault", "source_mapping": {"start": 11163, "length": 335, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "giveVault(address,bytes32)"}}}, "additional_fields": {"underlying_type": "variables_written", "variable_name": "loanIds"}}, {"type": "node", "name": "delete loanIds[_wallet][cdpManager.ilks(uint256(_loanId))]", "source_mapping": {"start": 12466, "length": 58, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [330], "starting_column": 9, "ending_column": 67}, "type_specific_fields": {"parent": {"type": "function", "name": "clearLoanOwner", "source_mapping": {"start": 12389, "length": 142, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [329, 330, 331], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "clearLoanOwner(address,bytes32)"}}}, "additional_fields": {"underlying_type": "variables_written", "variable_name": "loanIds"}}], "description": "Reentrancy in MakerV2Loan.giveVault(address,bytes32) (modules/maker/MakerV2Loan.sol#294-306):\n\tExternal calls:\n\t- cdpManager.give(uint256(_loanId),msg.sender) (modules/maker/MakerV2Loan.sol#304)\n\t- onlyNewVersion() (modules/maker/MakerV2Loan.sol#300)\n\t\t- (success,res) = msg.sender.call(abi.encodeWithSignature(isNewVersion(address),address(this))) (modules/maker/MakerV2Loan.sol#92)\n\tState variables written after the call(s):\n\t- clearLoanOwner(_wallet,_loanId) (modules/maker/MakerV2Loan.sol#305)\n\t\t- delete loanIds[_wallet][cdpManager.ilks(uint256(_loanId))] (modules/maker/MakerV2Loan.sol#330)\n", "markdown": "Reentrancy in [MakerV2Loan.giveVault(address,bytes32)](contracts/modules/maker/MakerV2Loan.sol#L294-L306):\n\tExternal calls:\n\t- [cdpManager.give(uint256(_loanId),msg.sender)](contracts/modules/maker/MakerV2Loan.sol#L304)\n\t- [onlyNewVersion()](contracts/modules/maker/MakerV2Loan.sol#L300)\n\t\t- [(success,res) = msg.sender.call(abi.encodeWithSignature(isNewVersion(address),address(this)))](contracts/modules/maker/MakerV2Loan.sol#L92)\n\tState variables written after the call(s):\n\t- [clearLoanOwner(_wallet,_loanId)](contracts/modules/maker/MakerV2Loan.sol#L305)\n\t\t- [delete loanIds[_wallet][cdpManager.ilks(uint256(_loanId))]](contracts/modules/maker/MakerV2Loan.sol#L330)\n", "id": "2ddc55f82dc755e4af496d96406f6d391ebfdbe9b8c19854827734046f8efa80", "check": "reentrancy-no-eth", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "openVault", "source_mapping": {"start": 17626, "length": 1144, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "openVault(address,address,uint256,uint256)"}}, {"type": "node", "name": "_cdpId = cdpManager.open(ilk,address(this))", "source_mapping": {"start": 18301, "length": 44, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [487], "starting_column": 13, "ending_column": 57}, "type_specific_fields": {"parent": {"type": "function", "name": "openVault", "source_mapping": {"start": 17626, "length": 1144, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "openVault(address,address,uint256,uint256)"}}}, "additional_fields": {"underlying_type": "external_calls"}}, {"type": "node", "name": "loanIds[_wallet][ilk] = bytes32(_cdpId)", "source_mapping": {"start": 18416, "length": 39, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [489], "starting_column": 13, "ending_column": 52}, "type_specific_fields": {"parent": {"type": "function", "name": "openVault", "source_mapping": {"start": 17626, "length": 1144, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "openVault(address,address,uint256,uint256)"}}}, "additional_fields": {"underlying_type": "variables_written", "variable_name": "loanIds"}}], "description": "Reentrancy in MakerV2Loan.openVault(address,address,uint256,uint256) (modules/maker/MakerV2Loan.sol#469-497):\n\tExternal calls:\n\t- _cdpId = cdpManager.open(ilk,address(this)) (modules/maker/MakerV2Loan.sol#487)\n\tState variables written after the call(s):\n\t- loanIds[_wallet][ilk] = bytes32(_cdpId) (modules/maker/MakerV2Loan.sol#489)\n", "markdown": "Reentrancy in [MakerV2Loan.openVault(address,address,uint256,uint256)](contracts/modules/maker/MakerV2Loan.sol#L469-L497):\n\tExternal calls:\n\t- [_cdpId = cdpManager.open(ilk,address(this))](contracts/modules/maker/MakerV2Loan.sol#L487)\n\tState variables written after the call(s):\n\t- [loanIds[_wallet][ilk] = bytes32(_cdpId)](contracts/modules/maker/MakerV2Loan.sol#L489)\n", "id": "e706a8c65d7aa824d1815521bd7893bef7035feb85374f6fe9a125371cba4219", "check": "reentrancy-no-eth", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "recoverToken", "source_mapping": {"start": 3282, "length": 242, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_relative": "contracts/modules/common/BaseFeature.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_short": "modules/common/BaseFeature.sol", "is_dependency": false, "lines": [99, 100, 101, 102], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseFeature", "source_mapping": {"start": 1264, "length": 4840, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_relative": "contracts/modules/common/BaseFeature.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_short": "modules/common/BaseFeature.sol", "is_dependency": false, "lines": [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}, {"type": "node", "name": "_token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,address(versionManager),total))", "source_mapping": {"start": 3417, "length": 100, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_relative": "contracts/modules/common/BaseFeature.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_short": "modules/common/BaseFeature.sol", "is_dependency": false, "lines": [101], "starting_column": 9, "ending_column": 109}, "type_specific_fields": {"parent": {"type": "function", "name": "recoverToken", "source_mapping": {"start": 3282, "length": 242, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_relative": "contracts/modules/common/BaseFeature.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_short": "modules/common/BaseFeature.sol", "is_dependency": false, "lines": [99, 100, 101, 102], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseFeature", "source_mapping": {"start": 1264, "length": 4840, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_relative": "contracts/modules/common/BaseFeature.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/common/BaseFeature.sol", "filename_short": "modules/common/BaseFeature.sol", "is_dependency": false, "lines": [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}}}], "description": "BaseFeature.recoverToken(address) (modules/common/BaseFeature.sol#99-102) ignores return value by _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,address(versionManager),total)) (modules/common/BaseFeature.sol#101)\n", "markdown": "[BaseFeature.recoverToken(address)](contracts/modules/common/BaseFeature.sol#L99-L102) ignores return value by [_token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,address(versionManager),total))](contracts/modules/common/BaseFeature.sol#L101)\n", "id": "bf848dc5e3354f97714a37f93b523ba7af9dbf2c0afeff232ae0251a7ae25d89", "check": "unchecked-lowlevel", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "recoverToken", "source_mapping": {"start": 3953, "length": 231, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [101, 102, 103, 104], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}, {"type": "node", "name": "_token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,msg.sender,total))", "source_mapping": {"start": 4090, "length": 87, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [103], "starting_column": 9, "ending_column": 96}, "type_specific_fields": {"parent": {"type": "function", "name": "recoverToken", "source_mapping": {"start": 3953, "length": 231, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [101, 102, 103, 104], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}}}], "description": "VersionManager.recoverToken(address) (modules/VersionManager.sol#101-104) ignores return value by _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,msg.sender,total)) (modules/VersionManager.sol#103)\n", "markdown": "[VersionManager.recoverToken(address)](contracts/modules/VersionManager.sol#L101-L104) ignores return value by [_token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector,msg.sender,total))](contracts/modules/VersionManager.sol#L103)\n", "id": "a93a1c71c8bd33bf0c59fc76c42adcf2680545c50b93a93d32df16bd25250559", "check": "unchecked-lowlevel", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "variable", "name": "stack", "source_mapping": {"start": 4089, "length": 27, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_relative": "contracts/modules/RelayerManager/RelayerManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_short": "modules/RelayerManager/RelayerManager.sol", "is_dependency": false, "lines": [113], "starting_column": 9, "ending_column": 36}, "type_specific_fields": {"parent": {"type": "function", "name": "execute", "source_mapping": {"start": 3467, "length": 2216, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_relative": "contracts/modules/RelayerManager/RelayerManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_short": "modules/RelayerManager/RelayerManager.sol", "is_dependency": false, "lines": [95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "RelayerManager", "source_mapping": {"start": 1323, "length": 13018, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_relative": "contracts/modules/RelayerManager/RelayerManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager/RelayerManager.sol", "filename_short": "modules/RelayerManager/RelayerManager.sol", "is_dependency": false, "lines": [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371], "starting_column": 1, "ending_column": null}}, "signature": "execute(address,address,bytes,uint256,bytes,uint256,uint256,address,address)"}}}}], "description": "RelayerManager.execute(address,address,bytes,uint256,bytes,uint256,uint256,address,address).stack (modules/RelayerManager/RelayerManager.sol#113) is a local variable never initialized\n", "markdown": "[RelayerManager.execute(address,address,bytes,uint256,bytes,uint256,uint256,address,address).stack](contracts/modules/RelayerManager/RelayerManager.sol#L113) is a local variable never initialized\n", "id": "50a9b69c8b3047fba59251923cf5337dcfbb55dad8d8a1092d7fdb3a390aa612", "check": "uninitialized-local", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "joinCollateral", "source_mapping": {"start": 13022, "length": 1076, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "joinCollateral(address,uint256,uint256,bytes32)"}}, {"type": "node", "name": "collateral.approve(address(gemJoin),_collateralAmount)", "source_mapping": {"start": 13849, "length": 55, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [366], "starting_column": 9, "ending_column": 64}, "type_specific_fields": {"parent": {"type": "function", "name": "joinCollateral", "source_mapping": {"start": 13022, "length": 1076, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "joinCollateral(address,uint256,uint256,bytes32)"}}}}], "description": "MakerV2Loan.joinCollateral(address,uint256,uint256,bytes32) (modules/maker/MakerV2Loan.sol#344-369) ignores return value by collateral.approve(address(gemJoin),_collateralAmount) (modules/maker/MakerV2Loan.sol#366)\n", "markdown": "[MakerV2Loan.joinCollateral(address,uint256,uint256,bytes32)](contracts/modules/maker/MakerV2Loan.sol#L344-L369) ignores return value by [collateral.approve(address(gemJoin),_collateralAmount)](contracts/modules/maker/MakerV2Loan.sol#L366)\n", "id": "b0f61571b13dd2c4e224199a61c15ea0fb4b3ee5fc8ed4dfe72d19e957cff556", "check": "unused-return", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "joinDebt", "source_mapping": {"start": 14104, "length": 796, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "joinDebt(address,uint256,uint256)"}}, {"type": "node", "name": "daiToken.approve(address(daiJoin),_debtAmount)", "source_mapping": {"start": 14586, "length": 47, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [386], "starting_column": 9, "ending_column": 56}, "type_specific_fields": {"parent": {"type": "function", "name": "joinDebt", "source_mapping": {"start": 14104, "length": 796, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MakerV2Loan", "source_mapping": {"start": 1302, "length": 21494, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_relative": "contracts/modules/maker/MakerV2Loan.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/maker/MakerV2Loan.sol", "filename_short": "modules/maker/MakerV2Loan.sol", "is_dependency": false, "lines": [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613], "starting_column": 1, "ending_column": null}}, "signature": "joinDebt(address,uint256,uint256)"}}}}], "description": "MakerV2Loan.joinDebt(address,uint256,uint256) (modules/maker/MakerV2Loan.sol#371-390) ignores return value by daiToken.approve(address(daiJoin),_debtAmount) (modules/maker/MakerV2Loan.sol#386)\n", "markdown": "[MakerV2Loan.joinDebt(address,uint256,uint256)](contracts/modules/maker/MakerV2Loan.sol#L371-L390) ignores return value by [daiToken.approve(address(daiJoin),_debtAmount)](contracts/modules/maker/MakerV2Loan.sol#L386)\n", "id": "7b7b605f7beea5dbc190ac516c062468d02f9d5718b568fd079091ca9881f7a2", "check": "unused-return", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "init", "source_mapping": {"start": 9371, "length": 71, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [221], "starting_column": 5, "ending_column": 76}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}], "description": "init(address) should be declared external:\n\t- VersionManager.init(address) (modules/VersionManager.sol#221)\n", "markdown": "init(address) should be declared external:\n\t- [VersionManager.init(address)](contracts/modules/VersionManager.sol#L221)\n", "id": "ff33584434c9217dd31d83ed2215d4615eb1a4016d703505b1b3f67c61b91b2e", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "isValidSignature", "source_mapping": {"start": 19444, "length": 367, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/TransferManager/TransferManager.sol", "filename_relative": "contracts/modules/TransferManager/TransferManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/TransferManager/TransferManager.sol", "filename_short": "modules/TransferManager/TransferManager.sol", "is_dependency": false, "lines": [487, 488, 489, 490, 491, 492], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "TransferManager", "source_mapping": {"start": 1348, "length": 22598, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/TransferManager/TransferManager.sol", "filename_relative": "contracts/modules/TransferManager/TransferManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/TransferManager/TransferManager.sol", "filename_short": "modules/TransferManager/TransferManager.sol", "is_dependency": false, "lines": [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597], "starting_column": 1, "ending_column": 2}}, "signature": "isValidSignature(bytes32,bytes)"}}], "description": "isValidSignature(bytes32,bytes) should be declared external:\n\t- TransferManager.isValidSignature(bytes32,bytes) (modules/TransferManager/TransferManager.sol#487-492)\n", "markdown": "isValidSignature(bytes32,bytes) should be declared external:\n\t- [TransferManager.isValidSignature(bytes32,bytes)](contracts/modules/TransferManager/TransferManager.sol#L487-L492)\n", "id": "4e90c0989f4fd83f728caf93179b9859a8c795ff556a1124972f9bcf5f84a07b", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "init", "source_mapping": {"start": 9371, "length": 71, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [221], "starting_column": 5, "ending_column": 76}, "type_specific_fields": {"parent": {"type": "contract", "name": "VersionManager", "source_mapping": {"start": 1529, "length": 13019, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_relative": "contracts/modules/VersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/VersionManager.sol", "filename_short": "modules/VersionManager.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}, {"type": "function", "name": "init", "source_mapping": {"start": 2042, "length": 814, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "UpgraderToVersionManager", "source_mapping": {"start": 1186, "length": 1794, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_relative": "contracts/modules/UpgraderToVersionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/UpgraderToVersionManager.sol", "filename_short": "modules/UpgraderToVersionManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82], "starting_column": 1, "ending_column": null}}, "signature": "init(address)"}}], "description": "init(address) should be declared external:\n\t- VersionManager.init(address) (modules/VersionManager.sol#221)\n\t- UpgraderToVersionManager.init(address) (modules/UpgraderToVersionManager.sol#59-76)\n", "markdown": "init(address) should be declared external:\n\t- [VersionManager.init(address)](contracts/modules/VersionManager.sol#L221)\n\t- [UpgraderToVersionManager.init(address)](contracts/modules/UpgraderToVersionManager.sol#L59-L76)\n", "id": "26b89cc901b975d44972b3f5fbe91407047a76bdaf9d28916b5ccc4c5e47b24c", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "invoke", "source_mapping": {"start": 4281, "length": 501, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 998, "length": 4720, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155], "starting_column": 1, "ending_column": null}}, "signature": "invoke(address,uint256,bytes)"}}, {"type": "node", "name": "(success,_result) = _target.call{value: _value}(_data)", "source_mapping": {"start": 4432, "length": 55, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [119], "starting_column": 9, "ending_column": 64}, "type_specific_fields": {"parent": {"type": "function", "name": "invoke", "source_mapping": {"start": 4281, "length": 501, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 998, "length": 4720, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155], "starting_column": 1, "ending_column": null}}, "signature": "invoke(address,uint256,bytes)"}}}}], "description": "BaseWallet.invoke(address,uint256,bytes) (wallet/BaseWallet.sol#117-128) sends eth to arbitrary user\n\tDangerous calls:\n\t- (success,_result) = _target.call{value: _value}(_data) (wallet/BaseWallet.sol#119)\n", "markdown": "[BaseWallet.invoke(address,uint256,bytes)](contracts/wallet/BaseWallet.sol#L117-L128) sends eth to arbitrary user\n\tDangerous calls:\n\t- [(success,_result) = _target.call{value: _value}(_data)](contracts/wallet/BaseWallet.sol#L119)\n", "id": "3a445adf96dfc0060ee47dfb0ef4a1fb3fac45de27785b9fc3b76f833aa9a8be", "check": "arbitrary-send", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "contract", "name": "Proxy", "source_mapping": {"start": 973, "length": 783, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51], "starting_column": 1, "ending_column": null}}, {"type": "function", "name": "fallback", "source_mapping": {"start": 1200, "length": 458, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Proxy", "source_mapping": {"start": 973, "length": 783, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51], "starting_column": 1, "ending_column": null}}, "signature": "fallback()"}}, {"type": "function", "name": "receive", "source_mapping": {"start": 1664, "length": 90, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [48, 49, 50], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Proxy", "source_mapping": {"start": 973, "length": 783, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51], "starting_column": 1, "ending_column": null}}, "signature": "receive()"}}], "description": "Contract locking ether found in :\n\tContract Proxy (wallet/Proxy.sol#25-51) has payable functions:\n\t - Proxy.fallback() (wallet/Proxy.sol#35-46)\n\t - Proxy.receive() (wallet/Proxy.sol#48-50)\n\tBut does not have a function to withdraw the ether\n", "markdown": "Contract locking ether found in :\n\tContract [Proxy](contracts/wallet/Proxy.sol#L25-L51) has payable functions:\n\t - [Proxy.fallback()](contracts/wallet/Proxy.sol#L35-L46)\n\t - [Proxy.receive()](contracts/wallet/Proxy.sol#L48-L50)\n\tBut does not have a function to withdraw the ether\n", "id": "65059149746fe5e1f25c150be699d486d9916f15492140a8e9feaa9f4e710392", "check": "locked-ether", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "function", "name": "recoverToken", "source_mapping": {"start": 3109, "length": 176, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [88, 89, 90, 91], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ModuleRegistry", "source_mapping": {"start": 1003, "length": 3863, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}, {"type": "node", "name": "ERC20(_token).transfer(msg.sender,total)", "source_mapping": {"start": 3237, "length": 41, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [90], "starting_column": 9, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "recoverToken", "source_mapping": {"start": 3109, "length": 176, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [88, 89, 90, 91], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ModuleRegistry", "source_mapping": {"start": 1003, "length": 3863, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142], "starting_column": 1, "ending_column": null}}, "signature": "recoverToken(address)"}}}}], "description": "ModuleRegistry.recoverToken(address) (infrastructure_0.5/ModuleRegistry.sol#88-91) ignores return value by ERC20(_token).transfer(msg.sender,total) (infrastructure_0.5/ModuleRegistry.sol#90)\n", "markdown": "[ModuleRegistry.recoverToken(address)](contracts/infrastructure_0.5/ModuleRegistry.sol#L88-L91) ignores return value by [ERC20(_token).transfer(msg.sender,total)](contracts/infrastructure_0.5/ModuleRegistry.sol#L90)\n", "id": "41fbcd0b9d8141f132deb3d6bc28e164003eb789f1b13973222664f22be39bd1", "check": "unused-return", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "variable", "name": "implementation", "source_mapping": {"start": 1075, "length": 29, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [30], "starting_column": 5, "ending_column": 34}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 998, "length": 4720, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155], "starting_column": 1, "ending_column": null}}}}], "description": "BaseWallet.implementation (wallet/BaseWallet.sol#30) should be constant\n", "markdown": "[BaseWallet.implementation](contracts/wallet/BaseWallet.sol#L30) should be constant\n", "id": "d2306d0a84c87fdb83bc0d89c644bd1c8abef756e76872d834ea614196cb27a4", "check": "constable-states", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setAddr", "source_mapping": {"start": 1727, "length": 150, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_relative": "contracts/infrastructure/ens/IENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_short": "infrastructure/ens/IENSManager.sol", "is_dependency": false, "lines": [44, 45, 46], "starting_column": 3, "ending_column": 49}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "setAddr(bytes32,address)"}}, {"type": "function", "name": "setAddr", "source_mapping": {"start": 986, "length": 54, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_relative": "contracts/infrastructure/storage/IGuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_short": "infrastructure/storage/IGuardianStorage.sol", "is_dependency": false, "lines": [26], "starting_column": 7, "ending_column": 61}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setAddr(bytes32,address)"}}], "description": "setAddr(bytes32,address) should be declared external:\n\t- ArgentENSResolver.setAddr(bytes32,address) (infrastructure/ens/IENSManager.sol#44-46)\n\t- ENSResolver.setAddr(bytes32,address) (infrastructure/storage/IGuardianStorage.sol#26)\n", "markdown": "setAddr(bytes32,address) should be declared external:\n\t- [ArgentENSResolver.setAddr(bytes32,address)](contracts/infrastructure/ens/IENSManager.sol#L44-L46)\n\t- [ENSResolver.setAddr(bytes32,address)](contracts/infrastructure/storage/IGuardianStorage.sol#L26)\n", "id": "1df4080d3c5362f00dc45553da3b1b97c767f40d1c3ff3b2a2a7d3ea02d7729a", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setName", "source_mapping": {"start": 1115, "length": 60, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_relative": "contracts/infrastructure/storage/IGuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_short": "infrastructure/storage/IGuardianStorage.sol", "is_dependency": false, "lines": [29, 30, 31], "starting_column": 56, "ending_column": 1}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "setName(bytes32,string)"}}, {"type": "function", "name": "setName", "source_mapping": {"start": 2050, "length": 156, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_relative": "contracts/infrastructure/ens/IENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_short": "infrastructure/ens/IENSManager.sol", "is_dependency": false, "lines": [52, 53, 54, 55, 56, 57, 58], "starting_column": 29, "ending_column": 9}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "setName(bytes32,string)"}}], "description": "setName(bytes32,string) should be declared external:\n\t- ENSResolver.setName(bytes32,string) (infrastructure/storage/IGuardianStorage.sol#29-31)\n\t- ArgentENSResolver.setName(bytes32,string) (infrastructure/ens/IENSManager.sol#52-58)\n", "markdown": "setName(bytes32,string) should be declared external:\n\t- [ENSResolver.setName(bytes32,string)](contracts/infrastructure/storage/IGuardianStorage.sol#L29-L31)\n\t- [ArgentENSResolver.setName(bytes32,string)](contracts/infrastructure/ens/IENSManager.sol#L52-L58)\n", "id": "cab5c12507be1b8fc93e5c90e8dcd7836d4fabfcd2b667d23ccdaf68856cad2c", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addr", "source_mapping": {"start": 2371, "length": 102, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_relative": "contracts/infrastructure/ens/IENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_short": "infrastructure/ens/IENSManager.sol", "is_dependency": false, "lines": [], "starting_column": null, "ending_column": null}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "addr(bytes32)"}}, {"type": "function", "name": "addr", "source_mapping": {"start": 922, "length": 59, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_relative": "contracts/infrastructure/storage/IGuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_short": "infrastructure/storage/IGuardianStorage.sol", "is_dependency": false, "lines": [23, 24, 25, 26], "starting_column": 37, "ending_column": 2}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "addr(bytes32)"}}], "description": "addr(bytes32) should be declared external:\n\t- ArgentENSResolver.addr(bytes32) (infrastructure/ens/IENSManager.sol)\n\t- ENSResolver.addr(bytes32) (infrastructure/storage/IGuardianStorage.sol#23-26)\n", "markdown": "addr(bytes32) should be declared external:\n\t- [ArgentENSResolver.addr(bytes32)](contracts/infrastructure/ens/IENSManager.sol)\n\t- [ENSResolver.addr(bytes32)](contracts/infrastructure/storage/IGuardianStorage.sol#L23-L26)\n", "id": "1228b61e964c40df49d3014cb13affcb699d58056aad326c6b0aded70dda7826", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "name", "source_mapping": {"start": 2640, "length": 108, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_relative": "contracts/infrastructure/ens/IENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_short": "infrastructure/ens/IENSManager.sol", "is_dependency": false, "lines": [], "starting_column": null, "ending_column": null}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "name(bytes32)"}}, {"type": "function", "name": "name", "source_mapping": {"start": 1045, "length": 65, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_relative": "contracts/infrastructure/storage/IGuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/IGuardianStorage.sol", "filename_short": "infrastructure/storage/IGuardianStorage.sol", "is_dependency": false, "lines": [26, 27, 28, 29], "starting_column": 66, "ending_column": 51}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSResolver", "source_mapping": {"start": 773, "length": 404, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ENSResolver.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26, 27, 28, 29], "starting_column": 1, "ending_column": null}}, "signature": "name(bytes32)"}}], "description": "name(bytes32) should be declared external:\n\t- ArgentENSResolver.name(bytes32) (infrastructure/ens/IENSManager.sol)\n\t- ENSResolver.name(bytes32) (infrastructure/storage/IGuardianStorage.sol#26-29)\n", "markdown": "name(bytes32) should be declared external:\n\t- [ArgentENSResolver.name(bytes32)](contracts/infrastructure/ens/IENSManager.sol)\n\t- [ENSResolver.name(bytes32)](contracts/infrastructure/storage/IGuardianStorage.sol#L26-L29)\n", "id": "402593e2522882c515b1d1875761d791f380e334b812ce0a896680c0706bb23f", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "supportsInterface", "source_mapping": {"start": 3008, "length": 209, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_relative": "contracts/infrastructure/ens/IENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/IENSManager.sol", "filename_short": "infrastructure/ens/IENSManager.sol", "is_dependency": false, "lines": [], "starting_column": null, "ending_column": null}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1098, "length": 2122, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSResolver.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91], "starting_column": 1, "ending_column": 2}}, "signature": "supportsInterface(bytes4)"}}], "description": "supportsInterface(bytes4) should be declared external:\n\t- ArgentENSResolver.supportsInterface(bytes4) (infrastructure/ens/IENSManager.sol)\n", "markdown": "supportsInterface(bytes4) should be declared external:\n\t- [ArgentENSResolver.supportsInterface(bytes4)](contracts/infrastructure/ens/IENSManager.sol)\n", "id": "d85de6068d5cb9fe1539e925323619b7c6fd0ab84371a6d54ed9e220655d3508", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "claim", "source_mapping": {"start": 817, "length": 56, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_relative": "contracts/infrastructure/storage/ILimitStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_short": "infrastructure/storage/ILimitStorage.sol", "is_dependency": false, "lines": [22, 23, 24, 25], "starting_column": 8, "ending_column": 18}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSReverseRegistrar", "source_mapping": {"start": 782, "length": 317, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_short": "infrastructure_0.5/ens/ENSReverseRegistrar.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26], "starting_column": 1, "ending_column": null}}, "signature": "claim(address)"}}], "description": "claim(address) should be declared external:\n\t- ENSReverseRegistrar.claim(address) (infrastructure/storage/ILimitStorage.sol#22-25)\n", "markdown": "claim(address) should be declared external:\n\t- [ENSReverseRegistrar.claim(address)](contracts/infrastructure/storage/ILimitStorage.sol#L22-L25)\n", "id": "4f9e0abd9fe9ffeea6c7d874a1af06e2c4acde79052640fa782e24ea57f2d6db", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "claimWithResolver", "source_mapping": {"start": 878, "length": 87, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_relative": "contracts/infrastructure/storage/ILimitStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_short": "infrastructure/storage/ILimitStorage.sol", "is_dependency": false, "lines": [25, 26, 27, 28], "starting_column": 23, "ending_column": 20}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSReverseRegistrar", "source_mapping": {"start": 782, "length": 317, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_short": "infrastructure_0.5/ens/ENSReverseRegistrar.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26], "starting_column": 1, "ending_column": null}}, "signature": "claimWithResolver(address,address)"}}], "description": "claimWithResolver(address,address) should be declared external:\n\t- ENSReverseRegistrar.claimWithResolver(address,address) (infrastructure/storage/ILimitStorage.sol#25-28)\n", "markdown": "claimWithResolver(address,address) should be declared external:\n\t- [ENSReverseRegistrar.claimWithResolver(address,address)](contracts/infrastructure/storage/ILimitStorage.sol#L25-L28)\n", "id": "1986538270e526ebc1b8e4d4222c7cb6ddd784bda2796b04f3916d7b92965596", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setName", "source_mapping": {"start": 970, "length": 63, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_relative": "contracts/infrastructure/storage/ILimitStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_short": "infrastructure/storage/ILimitStorage.sol", "is_dependency": false, "lines": [28, 29, 30], "starting_column": 25, "ending_column": 3}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSReverseRegistrar", "source_mapping": {"start": 782, "length": 317, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_short": "infrastructure_0.5/ens/ENSReverseRegistrar.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26], "starting_column": 1, "ending_column": null}}, "signature": "setName(string)"}}], "description": "setName(string) should be declared external:\n\t- ENSReverseRegistrar.setName(string) (infrastructure/storage/ILimitStorage.sol#28-30)\n", "markdown": "setName(string) should be declared external:\n\t- [ENSReverseRegistrar.setName(string)](contracts/infrastructure/storage/ILimitStorage.sol#L28-L30)\n", "id": "baaa51b3c540270842a2eed17d0a5b88aebbe8b49a4ad21a7aebb3d2b77cd108", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "node", "source_mapping": {"start": 1038, "length": 59, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_relative": "contracts/infrastructure/storage/ILimitStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/storage/ILimitStorage.sol", "filename_short": "infrastructure/storage/ILimitStorage.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34], "starting_column": 8, "ending_column": 8}, "type_specific_fields": {"parent": {"type": "contract", "name": "ENSReverseRegistrar", "source_mapping": {"start": 782, "length": 317, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ENSReverseRegistrar.sol", "filename_short": "infrastructure_0.5/ens/ENSReverseRegistrar.sol", "is_dependency": false, "lines": [21, 22, 23, 24, 25, 26], "starting_column": 1, "ending_column": null}}, "signature": "node(address)"}}], "description": "node(address) should be declared external:\n\t- ENSReverseRegistrar.node(address) (infrastructure/storage/ILimitStorage.sol#30-34)\n", "markdown": "node(address) should be declared external:\n\t- [ENSReverseRegistrar.node(address)](contracts/infrastructure/storage/ILimitStorage.sol#L30-L34)\n", "id": "087e13ac7a31bf083bfa50ea2d90d946a97525ee370fbb135322aa8fa49d7165", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "execute", "source_mapping": {"start": 3050, "length": 1361, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6369, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], "starting_column": 1, "ending_column": null}}, "signature": "execute(address,uint256,bytes,bytes)"}}], "description": "execute(address,uint256,bytes,bytes) should be declared external:\n\t- MultiSigWallet.execute(address,uint256,bytes,bytes) (infrastructure_0.5/MultiSigWallet.sol#77-104)\n", "markdown": "execute(address,uint256,bytes,bytes) should be declared external:\n\t- [MultiSigWallet.execute(address,uint256,bytes,bytes)](contracts/infrastructure_0.5/MultiSigWallet.sol#L77-L104)\n", "id": "c3af5848178a216692f1dd8bcebeb3cec8f959ac7b120616003a9eeef59a0196", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addOwner", "source_mapping": {"start": 4668, "length": 295, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [111, 112, 113, 114, 115, 116, 117], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6369, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], "starting_column": 1, "ending_column": null}}, "signature": "addOwner(address)"}}], "description": "addOwner(address) should be declared external:\n\t- MultiSigWallet.addOwner(address) (infrastructure_0.5/MultiSigWallet.sol#111-117)\n", "markdown": "addOwner(address) should be declared external:\n\t- [MultiSigWallet.addOwner(address)](contracts/infrastructure_0.5/MultiSigWallet.sol#L111-L117)\n", "id": "5efcde21d2a78ac98bff9840f6f55e1c1786b437adaa99ec2fd11cf6eacf78c4", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "removeOwner", "source_mapping": {"start": 5235, "length": 288, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [124, 125, 126, 127, 128, 129, 130], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6369, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], "starting_column": 1, "ending_column": null}}, "signature": "removeOwner(address)"}}], "description": "removeOwner(address) should be declared external:\n\t- MultiSigWallet.removeOwner(address) (infrastructure_0.5/MultiSigWallet.sol#124-130)\n", "markdown": "removeOwner(address) should be declared external:\n\t- [MultiSigWallet.removeOwner(address)](contracts/infrastructure_0.5/MultiSigWallet.sol#L124-L130)\n", "id": "abec3a9be52925542f3856e3e13b059576195bf6e1664d926bac665d14c7fff7", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "changeThreshold", "source_mapping": {"start": 5784, "length": 252, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [137, 138, 139, 140, 141], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6369, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168], "starting_column": 1, "ending_column": null}}, "signature": "changeThreshold(uint256)"}}], "description": "changeThreshold(uint256) should be declared external:\n\t- MultiSigWallet.changeThreshold(uint256) (infrastructure_0.5/MultiSigWallet.sol#137-141)\n", "markdown": "changeThreshold(uint256) should be declared external:\n\t- [MultiSigWallet.changeThreshold(uint256)](contracts/infrastructure_0.5/MultiSigWallet.sol#L137-L141)\n", "id": "434de4d96de5a8d7990cbd19c4b842b7b2dc02b90b2cb2d11c934328ba9321d4", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "getImplementations", "source_mapping": {"start": 4033, "length": 108, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [112, 113, 114], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentWalletDetector", "source_mapping": {"start": 1419, "length": 3338, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133], "starting_column": 1, "ending_column": 2}}, "signature": "getImplementations()"}}], "description": "getImplementations() should be declared external:\n\t- ArgentWalletDetector.getImplementations() (infrastructure/ArgentWalletDetector.sol#112-114)\n", "markdown": "getImplementations() should be declared external:\n\t- [ArgentWalletDetector.getImplementations()](contracts/infrastructure/ArgentWalletDetector.sol#L112-L114)\n", "id": "6eaecf61b17287074dc9f11d7d60adedfb4c78edddada83e1076ed2b733be86b", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "getCodes", "source_mapping": {"start": 4213, "length": 88, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [119, 120, 121], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentWalletDetector", "source_mapping": {"start": 1419, "length": 3338, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133], "starting_column": 1, "ending_column": 2}}, "signature": "getCodes()"}}], "description": "getCodes() should be declared external:\n\t- ArgentWalletDetector.getCodes() (infrastructure/ArgentWalletDetector.sol#119-121)\n", "markdown": "getCodes() should be declared external:\n\t- [ArgentWalletDetector.getCodes()](contracts/infrastructure/ArgentWalletDetector.sol#L119-L121)\n", "id": "f75311b1c5c48c8adcf64ec74fad15ac31a7a8bec74977e39f6501c4d25377fc", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "isAvailable", "source_mapping": {"start": 5048, "length": 300, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Owned.sol", "filename_relative": "contracts/infrastructure/base/Owned.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Owned.sol", "filename_short": "infrastructure/base/Owned.sol", "is_dependency": false, "lines": [], "starting_column": null, "ending_column": null}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSManager", "source_mapping": {"start": 1308, "length": 4175, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSManager.sol", "filename_relative": "contracts/infrastructure_0.5/ens/ArgentENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ens/ArgentENSManager.sol", "filename_short": "infrastructure_0.5/ens/ArgentENSManager.sol", "is_dependency": false, "lines": [32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140], "starting_column": 1, "ending_column": 2}}, "signature": "isAvailable(bytes32)"}}], "description": "isAvailable(bytes32) should be declared external:\n\t- ArgentENSManager.isAvailable(bytes32) (infrastructure/base/Owned.sol)\n", "markdown": "isAvailable(bytes32) should be declared external:\n\t- [ArgentENSManager.isAvailable(bytes32)](contracts/infrastructure/base/Owned.sol)\n", "id": "cea6114fb3f780560283a46b42da16e84525fd172d22aef195e6fb3716f49b5d", "check": "external-function", "impact": "Optimization", "confidence": "High"}] \ No newline at end of file +[{"elements": [{"type": "function", "name": "invoke", "source_mapping": {"start": 4382, "length": 501, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, "signature": "invoke(address,uint256,bytes)"}}, {"type": "node", "name": "(success,_result) = _target.call{value: _value}(_data)", "source_mapping": {"start": 4533, "length": 55, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [128], "starting_column": 9, "ending_column": 64}, "type_specific_fields": {"parent": {"type": "function", "name": "invoke", "source_mapping": {"start": 4382, "length": 501, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, "signature": "invoke(address,uint256,bytes)"}}}}], "description": "BaseWallet.invoke(address,uint256,bytes) (wallet/BaseWallet.sol#126-137) sends eth to arbitrary user\n\tDangerous calls:\n\t- (success,_result) = _target.call{value: _value}(_data) (wallet/BaseWallet.sol#128)\n", "markdown": "[BaseWallet.invoke(address,uint256,bytes)](contracts/wallet/BaseWallet.sol#L126-L137) sends eth to arbitrary user\n\tDangerous calls:\n\t- [(success,_result) = _target.call{value: _value}(_data)](contracts/wallet/BaseWallet.sol#L128)\n", "id": "8aee1fb6153c795b58fc391cbc7c6572df4ca6f9d9fbde02db9d0730525c360d", "check": "arbitrary-send", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "variable", "name": "newTimelockPeriod", "source_mapping": {"start": 965, "length": 31, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [27], "starting_column": 5, "ending_column": 36}, "type_specific_fields": {"parent": {"type": "contract", "name": "DappRegistry", "source_mapping": {"start": 829, "length": 12581, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278], "starting_column": 1, "ending_column": 0}}}}], "description": "DappRegistry.newTimelockPeriod (infrastructure/DappRegistry.sol#27) should be constant\n", "markdown": "[DappRegistry.newTimelockPeriod](contracts/infrastructure/DappRegistry.sol#L27) should be constant\n", "id": "b975b51e70b649a20a7a04c0b25bb78a65753f7cc24ea44513e9960e857435f7", "check": "constable-states", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "variable", "name": "timelockPeriodChangeAfter", "source_mapping": {"start": 1058, "length": 39, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [29], "starting_column": 5, "ending_column": 44}, "type_specific_fields": {"parent": {"type": "contract", "name": "DappRegistry", "source_mapping": {"start": 829, "length": 12581, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278], "starting_column": 1, "ending_column": 0}}}}], "description": "DappRegistry.timelockPeriodChangeAfter (infrastructure/DappRegistry.sol#29) should be constant\n", "markdown": "[DappRegistry.timelockPeriodChangeAfter](contracts/infrastructure/DappRegistry.sol#L29) should be constant\n", "id": "9a81fceca804142b27af2d467334401e8802b16e7e3276ca9944d4e90f96c0b0", "check": "constable-states", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setAddr", "source_mapping": {"start": 1665, "length": 159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [48, 49, 50, 51], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1035, "length": 2159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "starting_column": 1, "ending_column": 2}}, "signature": "setAddr(bytes32,address)"}}], "description": "setAddr(bytes32,address) should be declared external:\n\t- ArgentENSResolver.setAddr(bytes32,address) (infrastructure/ens/ArgentENSResolver.sol#48-51)\n", "markdown": "setAddr(bytes32,address) should be declared external:\n\t- [ArgentENSResolver.setAddr(bytes32,address)](contracts/infrastructure/ens/ArgentENSResolver.sol#L48-L51)\n", "id": "341d0bf835f749a44100bb312b2c486a382421ca0f91d91edffa87178b1aaab1", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "setName", "source_mapping": {"start": 1997, "length": 165, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [58, 59, 60, 61], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1035, "length": 2159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "starting_column": 1, "ending_column": 2}}, "signature": "setName(bytes32,string)"}}], "description": "setName(bytes32,string) should be declared external:\n\t- ArgentENSResolver.setName(bytes32,string) (infrastructure/ens/ArgentENSResolver.sol#58-61)\n", "markdown": "setName(bytes32,string) should be declared external:\n\t- [ArgentENSResolver.setName(bytes32,string)](contracts/infrastructure/ens/ArgentENSResolver.sol#L58-L61)\n", "id": "9a8e2af93034ad3e26bf54a3e282317f9c1b7418586bf79eea9ebf3f79c6b007", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addr", "source_mapping": {"start": 2327, "length": 111, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [68, 69, 70], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1035, "length": 2159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "starting_column": 1, "ending_column": 2}}, "signature": "addr(bytes32)"}}], "description": "addr(bytes32) should be declared external:\n\t- ArgentENSResolver.addr(bytes32) (infrastructure/ens/ArgentENSResolver.sol#68-70)\n", "markdown": "addr(bytes32) should be declared external:\n\t- [ArgentENSResolver.addr(bytes32)](contracts/infrastructure/ens/ArgentENSResolver.sol#L68-L70)\n", "id": "892cc271601739fa14b1c1265ca202bb4d07a83a71b16a2b1659f114e564035d", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "name", "source_mapping": {"start": 2605, "length": 117, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [77, 78, 79], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1035, "length": 2159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "starting_column": 1, "ending_column": 2}}, "signature": "name(bytes32)"}}], "description": "name(bytes32) should be declared external:\n\t- ArgentENSResolver.name(bytes32) (infrastructure/ens/ArgentENSResolver.sol#77-79)\n", "markdown": "name(bytes32) should be declared external:\n\t- [ArgentENSResolver.name(bytes32)](contracts/infrastructure/ens/ArgentENSResolver.sol#L77-L79)\n", "id": "da244ff73266331703edd04749026d3f82ae55b2a42bba8b08b0e50ddd8b5b95", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "supportsInterface", "source_mapping": {"start": 2982, "length": 209, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [86, 87, 88], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSResolver", "source_mapping": {"start": 1035, "length": 2159, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSResolver.sol", "filename_short": "infrastructure/ens/ArgentENSResolver.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90], "starting_column": 1, "ending_column": 2}}, "signature": "supportsInterface(bytes4)"}}], "description": "supportsInterface(bytes4) should be declared external:\n\t- ArgentENSResolver.supportsInterface(bytes4) (infrastructure/ens/ArgentENSResolver.sol#86-88)\n", "markdown": "supportsInterface(bytes4) should be declared external:\n\t- [ArgentENSResolver.supportsInterface(bytes4)](contracts/infrastructure/ens/ArgentENSResolver.sol#L86-L88)\n", "id": "d85de6068d5cb9fe1539e925323619b7c6fd0ab84371a6d54ed9e220655d3508", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "getImplementations", "source_mapping": {"start": 4025, "length": 108, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [112, 113, 114], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentWalletDetector", "source_mapping": {"start": 1418, "length": 3331, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133], "starting_column": 1, "ending_column": 2}}, "signature": "getImplementations()"}}], "description": "getImplementations() should be declared external:\n\t- ArgentWalletDetector.getImplementations() (infrastructure/ArgentWalletDetector.sol#112-114)\n", "markdown": "getImplementations() should be declared external:\n\t- [ArgentWalletDetector.getImplementations()](contracts/infrastructure/ArgentWalletDetector.sol#L112-L114)\n", "id": "6eaecf61b17287074dc9f11d7d60adedfb4c78edddada83e1076ed2b733be86b", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "getCodes", "source_mapping": {"start": 4205, "length": 88, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [119, 120, 121], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentWalletDetector", "source_mapping": {"start": 1418, "length": 3331, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_relative": "contracts/infrastructure/ArgentWalletDetector.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ArgentWalletDetector.sol", "filename_short": "infrastructure/ArgentWalletDetector.sol", "is_dependency": false, "lines": [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133], "starting_column": 1, "ending_column": 2}}, "signature": "getCodes()"}}], "description": "getCodes() should be declared external:\n\t- ArgentWalletDetector.getCodes() (infrastructure/ArgentWalletDetector.sol#119-121)\n", "markdown": "getCodes() should be declared external:\n\t- [ArgentWalletDetector.getCodes()](contracts/infrastructure/ArgentWalletDetector.sol#L119-L121)\n", "id": "f75311b1c5c48c8adcf64ec74fad15ac31a7a8bec74977e39f6501c4d25377fc", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "isAvailable", "source_mapping": {"start": 5734, "length": 309, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSManager.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSManager.sol", "filename_short": "infrastructure/ens/ArgentENSManager.sol", "is_dependency": false, "lines": [152, 153, 154, 155, 156, 157, 158, 159], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentENSManager", "source_mapping": {"start": 1264, "length": 4914, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSManager.sol", "filename_relative": "contracts/infrastructure/ens/ArgentENSManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/ens/ArgentENSManager.sol", "filename_short": "infrastructure/ens/ArgentENSManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164], "starting_column": 1, "ending_column": 2}}, "signature": "isAvailable(bytes32)"}}], "description": "isAvailable(bytes32) should be declared external:\n\t- ArgentENSManager.isAvailable(bytes32) (infrastructure/ens/ArgentENSManager.sol#152-159)\n", "markdown": "isAvailable(bytes32) should be declared external:\n\t- [ArgentENSManager.isAvailable(bytes32)](contracts/infrastructure/ens/ArgentENSManager.sol#L152-L159)\n", "id": "cea6114fb3f780560283a46b42da16e84525fd172d22aef195e6fb3716f49b5d", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "variable", "name": "creationCode", "source_mapping": {"start": 312, "length": 29, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModuleTest.sol", "filename_relative": "contracts/modules/ArgentModuleTest.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModuleTest.sol", "filename_short": "modules/ArgentModuleTest.sol", "is_dependency": false, "lines": [12], "starting_column": 5, "ending_column": 34}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentModuleTest", "source_mapping": {"start": 263, "length": 1349, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModuleTest.sol", "filename_relative": "contracts/modules/ArgentModuleTest.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModuleTest.sol", "filename_short": "modules/ArgentModuleTest.sol", "is_dependency": false, "lines": [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51], "starting_column": 1, "ending_column": 0}}}}], "description": "ArgentModuleTest.creationCode (modules/ArgentModuleTest.sol#12) should be constant\n", "markdown": "[ArgentModuleTest.creationCode](contracts/modules/ArgentModuleTest.sol#L12) should be constant\n", "id": "341ca7153779b23dd2bdfb73e2b485c9d04719967c1b1d464f50413e32a0bfdd", "check": "constable-states", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "getRequiredSignatures", "source_mapping": {"start": 2365, "length": 2826, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModule.sol", "filename_relative": "contracts/modules/ArgentModule.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModule.sol", "filename_short": "modules/ArgentModule.sol", "is_dependency": false, "lines": [71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ArgentModule", "source_mapping": {"start": 1058, "length": 4294, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModule.sol", "filename_relative": "contracts/modules/ArgentModule.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/ArgentModule.sol", "filename_short": "modules/ArgentModule.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129], "starting_column": 1, "ending_column": 0}}, "signature": "getRequiredSignatures(address,bytes)"}}, {"type": "function", "name": "getRequiredSignatures", "source_mapping": {"start": 2483, "length": 124, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager.sol", "filename_relative": "contracts/modules/RelayerManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager.sol", "filename_short": "modules/RelayerManager.sol", "is_dependency": false, "lines": [68], "starting_column": 5, "ending_column": 129}, "type_specific_fields": {"parent": {"type": "contract", "name": "RelayerManager", "source_mapping": {"start": 1195, "length": 14683, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager.sol", "filename_relative": "contracts/modules/RelayerManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/RelayerManager.sol", "filename_short": "modules/RelayerManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382], "starting_column": 1, "ending_column": 0}}, "signature": "getRequiredSignatures(address,bytes)"}}], "description": "getRequiredSignatures(address,bytes) should be declared external:\n\t- ArgentModule.getRequiredSignatures(address,bytes) (modules/ArgentModule.sol#71-123)\n\t- RelayerManager.getRequiredSignatures(address,bytes) (modules/RelayerManager.sol#68)\n", "markdown": "getRequiredSignatures(address,bytes) should be declared external:\n\t- [ArgentModule.getRequiredSignatures(address,bytes)](contracts/modules/ArgentModule.sol#L71-L123)\n\t- [RelayerManager.getRequiredSignatures(address,bytes)](contracts/modules/RelayerManager.sol#L68)\n", "id": "829f1dd7582b8d6984d0bd08e1f92502d0a175fbaa027fb60d3e5b9020499d8e", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "isWhitelisted", "source_mapping": {"start": 7506, "length": 253, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/TransactionManager.sol", "filename_relative": "contracts/modules/TransactionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/TransactionManager.sol", "filename_short": "modules/TransactionManager.sol", "is_dependency": false, "lines": [187, 188, 189, 190], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "TransactionManager", "source_mapping": {"start": 1155, "length": 10716, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/TransactionManager.sol", "filename_relative": "contracts/modules/TransactionManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/TransactionManager.sol", "filename_short": "modules/TransactionManager.sol", "is_dependency": false, "lines": [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280], "starting_column": 1, "ending_column": 0}}, "signature": "isWhitelisted(address,address)"}}], "description": "isWhitelisted(address,address) should be declared external:\n\t- TransactionManager.isWhitelisted(address,address) (modules/TransactionManager.sol#187-190)\n", "markdown": "isWhitelisted(address,address) should be declared external:\n\t- [TransactionManager.isWhitelisted(address,address)](contracts/modules/TransactionManager.sol#L187-L190)\n", "id": "20a2fa1815e7df075b0c4b94ba33bde7d07de3586936d245e3cc510964813e42", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "variable", "name": "staticCallExecutor", "source_mapping": {"start": 1212, "length": 33, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [34], "starting_column": 5, "ending_column": 38}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}}}], "description": "BaseWallet.staticCallExecutor (wallet/BaseWallet.sol#34) should be constant\n", "markdown": "[BaseWallet.staticCallExecutor](contracts/wallet/BaseWallet.sol#L34) should be constant\n", "id": "9aed61415a645939a8235d18a2572321e25be98259edcbf0e873b6ae7b88e164", "check": "constable-states", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "enabled", "source_mapping": {"start": 3312, "length": 272, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [93, 94, 95, 96, 97, 98, 99], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, "signature": "enabled(bytes4)"}}], "description": "enabled(bytes4) should be declared external:\n\t- BaseWallet.enabled(bytes4) (wallet/BaseWallet.sol#93-99)\n", "markdown": "enabled(bytes4) should be declared external:\n\t- [BaseWallet.enabled(bytes4)](contracts/wallet/BaseWallet.sol#L93-L99)\n", "id": "9dc0b8bff2834f2ac19776d9fc73e412a275f2c3313626035ed04117e279299a", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "variable", "name": "recoveryConfigs", "source_mapping": {"start": 1579, "length": 60, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/SecurityManager.sol", "filename_relative": "contracts/modules/SecurityManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/SecurityManager.sol", "filename_short": "modules/SecurityManager.sol", "is_dependency": false, "lines": [44], "starting_column": 5, "ending_column": 65}, "type_specific_fields": {"parent": {"type": "contract", "name": "SecurityManager", "source_mapping": {"start": 1176, "length": 16121, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/modules/SecurityManager.sol", "filename_relative": "contracts/modules/SecurityManager.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/modules/SecurityManager.sol", "filename_short": "modules/SecurityManager.sol", "is_dependency": false, "lines": [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380], "starting_column": 1, "ending_column": 0}}}}], "description": "SecurityManager.recoveryConfigs (modules/SecurityManager.sol#44) is never initialized. It is used in:\n", "markdown": "[SecurityManager.recoveryConfigs](contracts/modules/SecurityManager.sol#L44) is never initialized. It is used in:\n", "id": "1b45b11455304cb98691c21cd852b49b3cf3d5deef07c1aebdd8df8b3973dcb5", "check": "uninitialized-state", "impact": "High", "confidence": "High"}, {"elements": [{"type": "variable", "name": "managers", "source_mapping": {"start": 1052, "length": 41, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_relative": "contracts/infrastructure/base/Managed.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_short": "infrastructure/base/Managed.sol", "is_dependency": false, "lines": [29], "starting_column": 5, "ending_column": 46}, "type_specific_fields": {"parent": {"type": "contract", "name": "Managed", "source_mapping": {"start": 999, "length": 1144, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_relative": "contracts/infrastructure/base/Managed.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_short": "infrastructure/base/Managed.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65], "starting_column": 1, "ending_column": 0}}}}], "description": "Managed.managers (infrastructure/base/Managed.sol#29) is never initialized. It is used in:\n", "markdown": "[Managed.managers](contracts/infrastructure/base/Managed.sol#L29) is never initialized. It is used in:\n", "id": "3caf8b4c9b68910a3b89942ff6e039fd074ffe059beaf4d64915b1ac95288134", "check": "uninitialized-state", "impact": "High", "confidence": "High"}, {"elements": [{"type": "variable", "name": "managers", "source_mapping": {"start": 1052, "length": 41, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_relative": "contracts/infrastructure/base/Managed.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_short": "infrastructure/base/Managed.sol", "is_dependency": false, "lines": [29], "starting_column": 5, "ending_column": 46}, "type_specific_fields": {"parent": {"type": "contract", "name": "Managed", "source_mapping": {"start": 999, "length": 1144, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_relative": "contracts/infrastructure/base/Managed.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/base/Managed.sol", "filename_short": "infrastructure/base/Managed.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65], "starting_column": 1, "ending_column": 0}}}}, {"type": "function", "name": "setTradableForTokenList", "source_mapping": {"start": 1596, "length": 407, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/TokenRegistry.sol", "filename_relative": "contracts/infrastructure/TokenRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/TokenRegistry.sol", "filename_short": "infrastructure/TokenRegistry.sol", "is_dependency": false, "lines": [43, 44, 45, 46, 47, 48, 49], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "TokenRegistry", "source_mapping": {"start": 1027, "length": 978, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/TokenRegistry.sol", "filename_relative": "contracts/infrastructure/TokenRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/TokenRegistry.sol", "filename_short": "infrastructure/TokenRegistry.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51], "starting_column": 1, "ending_column": 0}}, "signature": "setTradableForTokenList(address[],bool[])"}}], "description": "Managed.managers (infrastructure/base/Managed.sol#29) is never initialized. It is used in:\n\t- TokenRegistry.setTradableForTokenList(address[],bool[]) (infrastructure/TokenRegistry.sol#43-49)\n", "markdown": "[Managed.managers](contracts/infrastructure/base/Managed.sol#L29) is never initialized. It is used in:\n\t- [TokenRegistry.setTradableForTokenList(address[],bool[])](contracts/infrastructure/TokenRegistry.sol#L43-L49)\n", "id": "e606526439713a65be773200286cc36ed8e9f1340a97ece38bada0a14c613c2c", "check": "uninitialized-state", "impact": "High", "confidence": "High"}, {"elements": [{"type": "variable", "name": "authorisations", "source_mapping": {"start": 1333, "length": 69, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [34], "starting_column": 5, "ending_column": 74}, "type_specific_fields": {"parent": {"type": "contract", "name": "DappRegistry", "source_mapping": {"start": 829, "length": 12581, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278], "starting_column": 1, "ending_column": 0}}}}, {"type": "function", "name": "isAuthorised", "source_mapping": {"start": 3795, "length": 1259, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "DappRegistry", "source_mapping": {"start": 829, "length": 12581, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_relative": "contracts/infrastructure/DappRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure/DappRegistry.sol", "filename_short": "infrastructure/DappRegistry.sol", "is_dependency": false, "lines": [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278], "starting_column": 1, "ending_column": 0}}, "signature": "isAuthorised(address,address,address,bytes)"}}], "description": "DappRegistry.authorisations (infrastructure/DappRegistry.sol#34) is never initialized. It is used in:\n\t- DappRegistry.isAuthorised(address,address,address,bytes) (infrastructure/DappRegistry.sol#84-102)\n", "markdown": "[DappRegistry.authorisations](contracts/infrastructure/DappRegistry.sol#L34) is never initialized. It is used in:\n\t- [DappRegistry.isAuthorised(address,address,address,bytes)](contracts/infrastructure/DappRegistry.sol#L84-L102)\n", "id": "99e577517edf2786d8b68a2bbd34959bee9f8983e557377be3033fb3a9ec5cb0", "check": "uninitialized-state", "impact": "High", "confidence": "High"}, {"elements": [{"type": "contract", "name": "Proxy", "source_mapping": {"start": 972, "length": 793, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52], "starting_column": 1, "ending_column": 0}}, {"type": "function", "name": "fallback", "source_mapping": {"start": 1209, "length": 464, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Proxy", "source_mapping": {"start": 972, "length": 793, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52], "starting_column": 1, "ending_column": 0}}, "signature": "fallback()"}}, {"type": "function", "name": "receive", "source_mapping": {"start": 1679, "length": 84, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [48, 49, 50], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "Proxy", "source_mapping": {"start": 972, "length": 793, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_relative": "contracts/wallet/Proxy.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/Proxy.sol", "filename_short": "wallet/Proxy.sol", "is_dependency": false, "lines": [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52], "starting_column": 1, "ending_column": 0}}, "signature": "receive()"}}], "description": "Contract locking ether found in :\n\tContract Proxy (wallet/Proxy.sol#25-52) has payable functions:\n\t - Proxy.fallback() (wallet/Proxy.sol#35-46)\n\t - Proxy.receive() (wallet/Proxy.sol#48-50)\n\tBut does not have a function to withdraw the ether\n", "markdown": "Contract locking ether found in :\n\tContract [Proxy](contracts/wallet/Proxy.sol#L25-L52) has payable functions:\n\t - [Proxy.fallback()](contracts/wallet/Proxy.sol#L35-L46)\n\t - [Proxy.receive()](contracts/wallet/Proxy.sol#L48-L50)\n\tBut does not have a function to withdraw the ether\n", "id": "65059149746fe5e1f25c150be699d486d9916f15492140a8e9feaa9f4e710392", "check": "locked-ether", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, {"type": "function", "name": "fallback", "source_mapping": {"start": 5060, "length": 692, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, "signature": "fallback()"}}, {"type": "function", "name": "receive", "source_mapping": {"start": 5758, "length": 34, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [162, 163], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "BaseWallet", "source_mapping": {"start": 997, "length": 4797, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_relative": "contracts/wallet/BaseWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/wallet/BaseWallet.sol", "filename_short": "wallet/BaseWallet.sol", "is_dependency": false, "lines": [27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165], "starting_column": 1, "ending_column": 0}}, "signature": "receive()"}}], "description": "Contract locking ether found in :\n\tContract BaseWallet (wallet/BaseWallet.sol#27-165) has payable functions:\n\t - BaseWallet.fallback() (wallet/BaseWallet.sol#143-160)\n\t - BaseWallet.receive() (wallet/BaseWallet.sol#162-163)\n\tBut does not have a function to withdraw the ether\n", "markdown": "Contract locking ether found in :\n\tContract [BaseWallet](contracts/wallet/BaseWallet.sol#L27-L165) has payable functions:\n\t - [BaseWallet.fallback()](contracts/wallet/BaseWallet.sol#L143-L160)\n\t - [BaseWallet.receive()](contracts/wallet/BaseWallet.sol#L162-L163)\n\tBut does not have a function to withdraw the ether\n", "id": "cb6e31bfdbf4097343e5c78042c14b94007b6c977edf2e37ea48f28098dfd083", "check": "locked-ether", "impact": "Medium", "confidence": "High"}, {"elements": [{"type": "contract", "name": "GuardianStorage", "source_mapping": {"start": 1165, "length": 4511, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152], "starting_column": 1, "ending_column": 0}}, {"type": "node", "name": "config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1)", "source_mapping": {"start": 2165, "length": 76, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [60], "starting_column": 9, "ending_column": 85}, "type_specific_fields": {"parent": {"type": "function", "name": "addGuardian", "source_mapping": {"start": 1958, "length": 290, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [57, 58, 59, 60, 61], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "GuardianStorage", "source_mapping": {"start": 1165, "length": 4511, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_relative": "contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/storage/GuardianStorage.sol", "filename_short": "infrastructure_0.5/storage/GuardianStorage.sol", "is_dependency": false, "lines": [29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152], "starting_column": 1, "ending_column": 0}}, "signature": "addGuardian(address,address)"}}}}], "description": "GuardianStorage (infrastructure_0.5/storage/GuardianStorage.sol#29-152) contract sets array length with a user-controlled value:\n\t- config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1) (infrastructure_0.5/storage/GuardianStorage.sol#60)\n", "markdown": "[GuardianStorage](contracts/infrastructure_0.5/storage/GuardianStorage.sol#L29-L152) contract sets array length with a user-controlled value:\n\t- [config.info[_guardian].index = uint128(config.guardians.push(_guardian) - 1)](contracts/infrastructure_0.5/storage/GuardianStorage.sol#L60)\n", "id": "fa034692f14355e0014bde5fb278b738b994c91a2b53e322fc4fcb87466cdcd6", "check": "controlled-array-length", "impact": "High", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "recoverToken", "source_mapping": {"start": 3113, "length": 176, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [88, 89, 90, 91], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ModuleRegistry", "source_mapping": {"start": 1007, "length": 3863, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143], "starting_column": 1, "ending_column": 0}}, "signature": "recoverToken(address)"}}, {"type": "node", "name": "ERC20(_token).transfer(msg.sender,total)", "source_mapping": {"start": 3241, "length": 41, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [90], "starting_column": 9, "ending_column": 50}, "type_specific_fields": {"parent": {"type": "function", "name": "recoverToken", "source_mapping": {"start": 3113, "length": 176, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [88, 89, 90, 91], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "ModuleRegistry", "source_mapping": {"start": 1007, "length": 3863, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_relative": "contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/ModuleRegistry.sol", "filename_short": "infrastructure_0.5/ModuleRegistry.sol", "is_dependency": false, "lines": [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143], "starting_column": 1, "ending_column": 0}}, "signature": "recoverToken(address)"}}}}], "description": "ModuleRegistry.recoverToken(address) (infrastructure_0.5/ModuleRegistry.sol#88-91) ignores return value by ERC20(_token).transfer(msg.sender,total) (infrastructure_0.5/ModuleRegistry.sol#90)\n", "markdown": "[ModuleRegistry.recoverToken(address)](contracts/infrastructure_0.5/ModuleRegistry.sol#L88-L91) ignores return value by [ERC20(_token).transfer(msg.sender,total)](contracts/infrastructure_0.5/ModuleRegistry.sol#L90)\n", "id": "41fbcd0b9d8141f132deb3d6bc28e164003eb789f1b13973222664f22be39bd1", "check": "unused-return", "impact": "Medium", "confidence": "Medium"}, {"elements": [{"type": "function", "name": "execute", "source_mapping": {"start": 3050, "length": 1365, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6373, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169], "starting_column": 1, "ending_column": 0}}, "signature": "execute(address,uint256,bytes,bytes)"}}], "description": "execute(address,uint256,bytes,bytes) should be declared external:\n\t- MultiSigWallet.execute(address,uint256,bytes,bytes) (infrastructure_0.5/MultiSigWallet.sol#77-104)\n", "markdown": "execute(address,uint256,bytes,bytes) should be declared external:\n\t- [MultiSigWallet.execute(address,uint256,bytes,bytes)](contracts/infrastructure_0.5/MultiSigWallet.sol#L77-L104)\n", "id": "c3af5848178a216692f1dd8bcebeb3cec8f959ac7b120616003a9eeef59a0196", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "addOwner", "source_mapping": {"start": 4672, "length": 295, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [111, 112, 113, 114, 115, 116, 117], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6373, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169], "starting_column": 1, "ending_column": 0}}, "signature": "addOwner(address)"}}], "description": "addOwner(address) should be declared external:\n\t- MultiSigWallet.addOwner(address) (infrastructure_0.5/MultiSigWallet.sol#111-117)\n", "markdown": "addOwner(address) should be declared external:\n\t- [MultiSigWallet.addOwner(address)](contracts/infrastructure_0.5/MultiSigWallet.sol#L111-L117)\n", "id": "5efcde21d2a78ac98bff9840f6f55e1c1786b437adaa99ec2fd11cf6eacf78c4", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "removeOwner", "source_mapping": {"start": 5239, "length": 288, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [124, 125, 126, 127, 128, 129, 130], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6373, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169], "starting_column": 1, "ending_column": 0}}, "signature": "removeOwner(address)"}}], "description": "removeOwner(address) should be declared external:\n\t- MultiSigWallet.removeOwner(address) (infrastructure_0.5/MultiSigWallet.sol#124-130)\n", "markdown": "removeOwner(address) should be declared external:\n\t- [MultiSigWallet.removeOwner(address)](contracts/infrastructure_0.5/MultiSigWallet.sol#L124-L130)\n", "id": "abec3a9be52925542f3856e3e13b059576195bf6e1664d926bac665d14c7fff7", "check": "external-function", "impact": "Optimization", "confidence": "High"}, {"elements": [{"type": "function", "name": "changeThreshold", "source_mapping": {"start": 5788, "length": 252, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [137, 138, 139, 140, 141], "starting_column": 5, "ending_column": 6}, "type_specific_fields": {"parent": {"type": "contract", "name": "MultiSigWallet", "source_mapping": {"start": 855, "length": 6373, "filename_used": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_relative": "contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_absolute": "/Users/Elena/Source/argent-contracts/contracts/infrastructure_0.5/MultiSigWallet.sol", "filename_short": "infrastructure_0.5/MultiSigWallet.sol", "is_dependency": false, "lines": [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169], "starting_column": 1, "ending_column": 0}}, "signature": "changeThreshold(uint256)"}}], "description": "changeThreshold(uint256) should be declared external:\n\t- MultiSigWallet.changeThreshold(uint256) (infrastructure_0.5/MultiSigWallet.sol#137-141)\n", "markdown": "changeThreshold(uint256) should be declared external:\n\t- [MultiSigWallet.changeThreshold(uint256)](contracts/infrastructure_0.5/MultiSigWallet.sol#L137-L141)\n", "id": "434de4d96de5a8d7990cbd19c4b842b7b2dc02b90b2cb2d11c934328ba9321d4", "check": "external-function", "impact": "Optimization", "confidence": "High"}] \ No newline at end of file diff --git a/specifications/images/SC_Flow_3_x.png b/specifications/images/SC_Flow_3_x.png new file mode 100644 index 000000000..39af3e5ae Binary files /dev/null and b/specifications/images/SC_Flow_3_x.png differ diff --git a/specifications/specifications.pdf b/specifications/specifications.pdf index e41dcff66..223fdbce9 100644 Binary files a/specifications/specifications.pdf and b/specifications/specifications.pdf differ diff --git a/specifications/specifications.tex b/specifications/specifications.tex index 303298582..b3da4a58b 100644 --- a/specifications/specifications.tex +++ b/specifications/specifications.tex @@ -6,7 +6,7 @@ \graphicspath{ {./images/} } \title{Argent Smart Wallet Specification} -\author{v2.0} +\author{v2.5} \date{\today} @@ -22,30 +22,30 @@ \section{Specifications} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Introduction} -The Argent wallet is an Ethereum Smart Contract based mobile wallet. The wallet's user keeps an Ethereum account (Externally Owned Account) secretly on his mobile device. This account is set as the owner of the Smart Contract. User's funds (ETH and ERC20 tokens) are stored on the Smart Contract. With that model, logic can be added to the wallet to improve both the user experience and the wallet security. For instance, the wallet is guarded, recoverable, lockable, protected by a daily limit and upgradable. +The Argent wallet is an Ethereum Smart Contract based mobile wallet. The wallet's user keeps an Ethereum account (Externally Owned Account) secretly on his mobile device. This account is set as the owner of the Smart Contract. User's assets (e.g. ETH, ERC20, ERC721 or ERC1155 tokens) are stored on the Smart Contract. With that model, logic can be added to the wallet to improve both the user experience and the wallet security. For instance, the wallet is guarded, recoverable, transferable, lockable and upgradable. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Guardians} -The wallet security model is based on the ability to add Guardians. A Guardian is an account (EOA or smart contract) that has been given permission by the wallet's owner to execute certain specific operations on their wallet. In particular guardians can lock, unlock, and trigger a recovery procedure on the wallet as well as approve the execution of a large transfer to an unknown account. +The wallet security model is based on the ability to add \textit{guardians}. A guardian is an account (EOA or smart contract) that has been given permission by the wallet's owner to execute certain specific operations on their wallet. In particular guardians can lock, unlock, and trigger a recovery procedure on the wallet as well as approve the execution of transactions to unknown accounts or the creation of a session key. -We do not impose restrictions on who or what Guardians are. They can be a friend's Argent wallet, a friend's EOA, a hardware wallet, or even a paid third-party service. +We do not impose restrictions on who or what guardians are. They can be a friend's Argent wallet, a friend's EOA, a hardware wallet, or even a paid third-party service. -Adding a Guardian is an action triggered by the wallet owner. While the first Guardian is added immediately, all subsequent additions must be confirmed after 24 hours and no later than 36 hours after the addition was requested. This confirmation window ensures that a pending addition will be canceled (expire) should the wallet be locked or recovered. +Adding a guardian is an action triggered by the wallet owner. While the first guardian is added immediately when the wallet is created, all subsequent additions must be confirmed after 36 hours and no later than 48 hours after the addition was requested. This confirmation window ensures that a pending addition will be canceled (expire) should the wallet be locked or recovered. -Removing a Guardian is an action triggered by the wallet owner. It must always be confirmed after 24 hours and no later than 36 hours after the removal was requested. This leaves the legitimate wallet owner enough time to notice and prevent the appointment of an illegitimate Guardian (or the dismissal of a legitimate Guardian) in case the owner lost control over their mobile device. +Removing a guardian is an action triggered by the wallet owner. It must always be confirmed after 36 hours and no later than 48 hours after the removal was requested. This leaves the legitimate wallet owner enough time to notice and prevent the appointment of an illegitimate guardian (or the dismissal of a legitimate guardian) in case the owner lost control over their mobile device. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Locking} -In case the wallet owner suspects his account (i.e. device) is compromised (lost, stolen, ...), he can ask any of his Guardians to lock the wallet for a security period of 5 days. Once the wallet is locked only a limited set of actions can be operated on the wallet, namely the recovery procedure, the unlock procedure, or the revocation of Guardians. All other operations (add guardian, assets transfer, ...) are blocked. +In case the wallet owner suspects his account (i.e. device) is compromised (lost, stolen, ...), he can ask any of his guardians to lock the wallet for a security period of 5 days. Once the wallet is locked only a limited set of actions can be operated on the wallet, namely the recovery procedure, the unlock procedure, or the revocation of Guardians. All other operations (add guardian, assets transfer, ...) are blocked. To unlock a wallet before the end of the security period, any guardian should trigger a wallet unlock. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Recovery} -Wallet recovery is a process requested by a user who asserts ownership of a wallet while not being in possession of the owner key. A successful recovery sets a new account as the wallet owner. This process should be validated by the wallet's guardians to be executed. Once a recovery has been executed it may be finalised after 36 hours, unless it has been cancelled. +Wallet recovery is a process requested by a user who asserts ownership of a wallet while not being in possession of the owner key. A successful recovery sets a new account as the wallet owner. This process should be validated by the wallet's guardians to be executed and thus requires the wallet to have at least 1 guardian. Once a recovery has been executed it may be finalised after 48 hours, unless it has been cancelled. The number of signatures needed to execute a recovery is given by \begin{equation*} @@ -59,7 +59,7 @@ \subsection{Recovery} \end{equation*} where $n$ is the total number of guardians when the recovery was executed. -Once a recovery is started the wallet is automatically locked. The wallet can only be unlock by finalising or cancelling the ongoing procedure, i.e. Guardians cannot unlock during a recovery. +Once a recovery is started the wallet is automatically locked. The wallet can only be unlock by finalising or cancelling the ongoing procedure, i.e. guardians cannot unlock during a recovery. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Ownership Transfer} @@ -71,47 +71,73 @@ \subsection{Ownership Transfer} where the first signature is the owner and $n$ is the total number of guardians. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Daily Transfer Limit} -\label{sec:dailylimit} -The wallet is protected by a daily limit (rolling for 24 hours). The owner can spend up to the daily limit in a given 24 hours period. The daily limit default value is 1 ETH and can be modified by the owner. A reduction of the limit applies immediately. It takes 24 hours for an increase of the limit to be effective, unless the increase is approved by guardians (the number of guardians required is given in Section~\ref{sec:approved-transfer}), in which case the change is immediate. +\subsection{Multi-Call} -Any transfer exceeding the daily limit will be set as pending, and can be executed only after 24 hours. +The wallet can interact with the ethereum ecosystem to e.g. send an asset or interact with the smart-contracts of a decentralized application through multi-calls. A multi-call is a sequence of transactions executed by the wallet one after the other and such that the multi-call fails if any of the transaction fails. -Transfers (and ERC20 token approvals) to whitelisted addresses (see Section~\ref{sec:whitelist}) and transfers (and ERC20 token approvals) approved by guardians (see Section~\ref{sec:approved-transfer}) do not contribute to the daily limit. +By design the wallet will block a multi-call unless one of the following conditions is satisfied: +\begin{enumerate} + \item the multicall is triggered by the wallet owner and each transaction of the multi-call: + \begin{itemize} + \item transfers or approves an asset (ETH, ERC20, ERC721, ERC1155) to a trusted contact, see Section \ref{sec:trusted-contacts} + \item transfers or approves an asset (ETH, ERC20, ERC721, ERC1155) or calls an authorised Dapp, see Section \ref{sec:authorised-dapps} + \end{itemize} + \item the multicall is approved by the owner and a majority of guardians, see Section \ref{sec:guardian-approval} + \item the multicall is executed with a valid session key, see Section \ref{sec:session} +\end{enumerate} -The daily spend of a user is reset after 24 hours or after each transaction made via the \emph{ApprovedTransfer} feature, which involves a majority of guardians (see Section~\ref{sec:approved-transfer}). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Trusted Contacts} +\label{sec:trusted-contacts} + +The wallet keeps a list of trusted addresses managed by the wallet owner. +Adding an address to this list is an action triggered by the wallet owner and takes 36 hours to be effective. Removing an address is triggered by the owner and is immediate. -The daily limit is cross-token (ETH + ERC20) and we're using an on-chain oracle to get the conversion rates for ERC20 tokens. +Interacting with a trusted contact to e.g. send an asset, can be triggered by the wallet owner and does not require any additional authorisation. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Whitelist} -\label{sec:whitelist} +\subsection{Dapp Registry} +\label{sec:authorised-dapps} -The wallet keeps a whitelist of trusted addresses. Transfers to those addresses are immediate and their amounts are not limited. +In addition to trusted contacts, the wallet supports 2 registries of addresses that have been authorised by Argent. -Adding an address to the whitelist is an action triggered by the wallet owner and takes 24 hours to be effective. Removing an address is triggered by the owner and is immediate. +The first registry is enabled by default (opt-out) for all wallets and contains all the dapps that are natively integrated in the Argent client application (Paraswap, Compound, Maker, Aave, etc). The second registry is disabled by default (opt-in) and contains a list of curated dapps that the user can access through WalletConnect. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Approved Transfer} -\label{sec:approved-transfer} +Each registry entry is a mapping between an authorised address and a filter containing the set of conditions that a transaction from a wallet to the authorised address must satisfy. +Adding or removing an entry in a registry is a timelocked operation that can only be initiated by the Argent multisig. +The timelock is set to 7 days to give enough time for a user to disable the correspoding registry should they not approve the new addition. -Transfers and ERC20 token approvals exceeding the daily limit can be executed immediately by the owner, provided that they obtain approval from their guardians. The number of required signatures for an approved transfer is given by +Enabling or disabling a registry for a wallet is achieved via a multicall and requires guardian approvals. The number of required signatures is given by \begin{equation*} 1+\left\lceil {\frac{n}{2}} \right\rceil \end{equation*} where the first signature is the owner and $n$ is the total number of guardians. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{ERC20 Exchange} +Interacting with an authorised dapp to e.g. invest an asset in a DeFi protocol, can be triggered by the owner provided that the corresponding registry is enabled for the wallet and the transaction passes the associated filter. -The owner is able to trade ETH and ERC20 tokens through an integration with Paraswap exchange. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Guardian Approval} +\label{sec:guardian-approval} -Swapping tokens is not constrained by the daily limit since no value is leaving the wallet. +Any sequence of transactions (multi-call) can be executed by the wallet owner provided that they obtain approval from their guardians. The number of required signatures is given by +\begin{equation*} + 1+\left\lceil {\frac{n}{2}} \right\rceil +\end{equation*} +where the first signature is the owner and $n$ is the total number of guardians. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{ENS} +\subsection{Session} +\label{sec:session} + +Users that plan to execute many transactions in a given time window while only requiring their guardian approval once can do so by creating a session. A session defines a temporary session key that can be used to execute any multi-call for as long as the session key is valid. The duration of the session is defined by the owner when the session is created and the session key will automatically become invalid at the end the session. -The Wallet is associated to an ENS. This association is forward and backward meaning that it is possible to obtain the Wallet address from the ENS and the ENS from the Wallet address. +The number of required signatures to start a session is given by +\begin{equation*} + 1+\left\lceil {\frac{n}{2}} \right\rceil +\end{equation*} +where the first signature is the owner and $n$ is the total number of guardians. + +The wallet owner can always close a session before its expiration. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Upgradability} @@ -122,22 +148,22 @@ \subsection{Upgradability} \subsection{ETH-less Account} \label{sec:eth-less-account} -Owner and guardians can execute wallet operations without the need to pay transaction fees and own ETH, i.e. they are ETH-less account. This is achieved by enabling accounts to sign a message showing intent of execution, and allowing a third party relayer to execute the transaction and pay the fee on their behalf. The party signing the transaction can specify if the wallet should refund the gas (partially or totally) required to execute the transaction to the third party relayer. This pattern, now called meta-transactions, is described in EIP 1077\footnote{https://eips.ethereum.org/EIPS/eip-1077} and implemented in the \emph{RelayerManager} feature (see Section~\ref{sec:meta-transactions}). +Owner and guardians can execute wallet operations without the need to pay transaction fees and own ETH, i.e. they are ETH-less account. This is achieved by enabling accounts to sign a message showing intent of execution, and allowing a third party relayer to execute the transaction and pay the fee on their behalf. The party signing the transaction can specify if the wallet should refund the gas (partially or totally) required to execute the transaction to the third party relayer. This pattern, now called meta-transactions, is described in EIP 1077\footnote{https://eips.ethereum.org/EIPS/eip-1077} (see Section~\ref{sec:meta-transactions}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Summary of Guardian Operations} \begin{table}[ht] - \begin{tabular}{ |c|m{6em}|m{6em}|m{6em}|m{6em}|m{6em}| } + \begin{tabular}{ |c|m{5em}|m{5em}|m{5em}|m{5em}|m{5em}|m{5em}| } \hline - & Lock/Unlock & Execute \newline Recovery & Cancel \newline Recovery & Transfer Ownership & Approve Transfer \\ + & Lock/Unlock & Execute \newline Recovery & Cancel \newline Recovery & Transfer Ownership & Approve Multi-call / Start Session & Enable Registry \\ \hline \hline - & Guardians & Guardians & Owner OR Guardians & Owner AND Guardians & Owner AND Guardians \\ + & Guardians & Guardians & Owner OR Guardians & Owner AND Guardians & Owner AND Guardians & Owner AND Guardians \\ \hline - 1 & 1 & 1 & 1 & 2 & 2 \\ - 2 & 1 & 1 & 2 & 2 & 2 \\ - 3 & 1 & 2 & 2 & 3 & 3 \\ - 4 & 1 & 2 & 3 & 3 & 3 \\ - 5 & 1 & 3 & 3 & 4 & 4 \\ + 1 & 1 & 1 & 1 & 2 & 2 & 2 \\ + 2 & 1 & 1 & 2 & 2 & 2 & 2\\ + 3 & 1 & 2 & 2 & 3 & 3 & 3\\ + 4 & 1 & 2 & 3 & 3 & 3 & 4\\ + 5 & 1 & 3 & 3 & 4 & 4 & 4\\ \hline \end{tabular} @@ -152,112 +178,108 @@ \section{Implementation} \subsection{Smart Contracts architecture} -Our architecture is made up of multiple contracts. A first group of contracts form the infrastructure required to deploy or update user wallets. These infrastructure contracts are meant to be deployed only once: +Our architecture is made up of multiple contracts. A first group of contracts form the infrastructure required to deploy or update user wallets as well as to expose on-chain information needed to secure the wallets. These infrastructure contracts are meant to be deployed only once: \begin{itemize} \item \textbf{Multisig Wallet:} Custom-made multi-signatures wallet which is the owner of most of other infrastructure contracts. All calls on those contracts will therefore need to be approved by multiple persons. - \item \textbf{Wallet Factory:} Wallet factory contract used to create proxy wallets using CREATE or CREATE2 and assign them to users. + \item \textbf{Wallet Factory:} Wallet factory contract used to create proxy wallets using CREATE2 and assign them to users. \item \textbf{ENS Manager:} The ENS Manager is responsible for registering ENS subdomains (e.g. mike.argent.xyz) and assigning them to wallets. \item \textbf{ENS Resolver:} The ENS Resolver keeps links between ENS subdomains and wallet addresses and allows to resolve them in both directions. \item \textbf{Module Registry:} The Module Registry maintains a list of the registered \emph{Module} contracts that can be used with user wallets. It also maintains a list of registered \emph{Upgrader} contracts that a user can use to migrate the module(s) used with their wallet (see Section~\ref{sec:upgradability}). - \item \textbf{Compound Registry:} Registry maintaining a mapping between underlying assets (ETH, DAI, BAT, etc) and their corresponding Compound Token (cETH, cDAI, cBAT, etc). - \item \textbf{Maker Registry:} Registry maintaining a mapping between token collaterals (ETH, BAT, USDC, WBTC) and their corresponding Maker Join adapters. - \item \textbf{Token Price Registry:} On-chain price oracle for ERC20 tokens. It is used by wallets to estimate the value in ETH of ERC20 transfers. This is necessary for the update of the daily limit and the refund of the relayer's gas costs. - \item \textbf{Dex Registry:} Registry of supported DEXes. Only the exchanges listed in this registry can be used (through Paraswap) to trade tokens when using the \emph{TokenExchanger} feature. + \item \textbf{Token Registry:} On-chain registry of ERC20 tokens that can be safely traded. + \item \textbf{Dapp Registry:} Registries of dapps authorised by Argent. The \emph{DappRegistry} currently supports 2 registries. \end{itemize} \begin{figure}[h] \label{fig:sc-flow} - \includegraphics[width=\textwidth]{SC_Flow_2_x_final_} + \includegraphics[width=\textwidth]{SC_Flow_3_x} \caption{Call flow in Argent} \end{figure} A second group of contracts implements the functionalities of the wallet: \begin{itemize} - \item \textbf{Version Manager:} The Version Manager is the only module authorised for a wallet. A module is a contract that has the permission to call certain methods of a wallet on which the module is authorised. This follows a wallet design pattern introduced by Nick Johnson\footnote{https://gist.github.com/Arachnid/a619d31f6d32757a4328a428286da186 and https://gist.github.com/Arachnid/6a5c8ff96869fbdf0736a3a7be91b84e}: instead of directly calling a method in their wallet to perform a given operation, users call a method in the appropriate module contract (e.g. \emph{upgradeWallet()} in the \emph{VersionManager} module), which verifies that the user holds the required authorization (e.g. checking that the caller is the wallet owner) and if so, calls an appropriate method on the wallet (e.g. \emph{enableStaticCall()}). - In a previous architecture of the Argent wallet, all functionalities were implemented as modules. Because this led to large gas cost during wallet creation and upgrades, most functionalities are now implemented as \emph{Features} (see below), which can be seen as "layer 2 modules" as they don't call the wallet directly but call it via the \emph{VersionManager} instead (see Figure~\ref{fig:sc-flow}). - The \emph{VersionManager} module holds a mapping of version numbers to feature bundles as well as a mapping of wallet addresses to version numbers. This allows upgrading a wallet (i.e. changing the list of features it uses) by only changing its associated version number in the \emph{VersionManager}. - \item \textbf{Features:} As mentionned above, different functionalities of the wallet are encapsulated in different \emph{Features}. In general, a single feature contract (e.g. \emph{GuardianManager}) is used by all wallets to handle a specific set of operations (e.g. adding and revoking guardians). Features are added in bundles by the Argent multisig and each bundle is given a unique version number. A wallet can then be upgraded by its owner to use a certain bundle of features (i.e. version number). - Features can either be called directly by the appropriate account when only one signature (e.g. owner or guardian) is required or can be called indirectly via the \emph{RelayerManager} feature (see section \ref{RelayerManager}). - \item \textbf{Storages:} Some features store part of their states in a dedicated storage contract (see Section~\ref{sec:storage}). \item \textbf{Proxy Wallet:} Lightweight proxy contract that delegates all calls to a Base Wallet library-like contract. There is one proxy deployed per wallet. Note that the rationale for using the Proxy-Implementation design pattern\footnote{ introduced by Nick Johnson in https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f} is \emph{not} to enable wallet upgradability (we use upgradable modules/features for that) but simply to reduce the deployment cost of each new wallet. \item \textbf{Base Wallet:} The Base Wallet is a simple library-like contract implementing basic wallet functionalities used by proxy wallets (via delegatecalls), that are not expected to ever change. These functionalities include changing the owner of the wallet, (de)authorizating modules and performing (value-carrying) internal transactions to third-party contracts. + \item \textbf{Modules:} The different functionalities of the wallet are encapsulated in \emph{Modules}. Each Module contract is used by all wallets to handle a specific set of operations (e.g. adding and revoking guardians, doing multicalls, etc). Registered Modules can be added and removed by the wallet owner to update the functionalities of its wallet but the wallet must always have at least 1 module. Modules are grouped in bundles and each bundle is given a unique version number. A wallet is typically upgraded by its owner to use a certain bundle of modules (i.e. version number). + \item \textbf{Storages:} Some Modules store part of their states in a dedicated storage contract (see Section~\ref{sec:storage}). \end{itemize} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Upgradability} \label{sec:upgradability} -An Argent wallet can be upgraded on two levels. In both cases, a wallet cannot be upgraded while it is locked. -\begin{itemize} - \item \textbf{Feature Upgrade (occasional)}: -The Argent multisig can add a new version (i.e. bundle of features) to the \emph{VersionManager}. Wallet owners can then choose to upgrade their wallets to that new version by calling the \emph{upgradeWallet()} method of the \emph{VersionManager}. - \item \textbf{Module Upgrade (rare)}: -In the rare case where the \emph{VersionManager} itself needs to be upgraded, the \emph{addModule()} method of the \emph{VersionManager} can be called to add a temporary Upgrader module that defines a migration path for a wallet. This upgrader module would remove (deauthorise) the old \emph{VersionManager} and add (authorise) the new \emph{VersionManager}. The temporary Upgrader module as well as the new \emph{VersionManager} need to be registered in the \emph{ModuleRegistry}. -\end{itemize} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Storage} -\label{sec:storage} -In general, each feature stores the entire state pertaining to all the wallets that use that feature. For example, the \emph{MakerV2Manager} feature stores a mapping of the vaults it manages for the wallets. Some features such as the \emph{TransferManager} make use of an additional storage contract (e.g. \emph{TransferStorage}). This is the case when their storage needs to be accessed by other features and/or to simplify the upgradability of that feature in the future. - -Storage contracts include: -\begin{itemize} -\item \textbf{Lock Storage:} Used to store a wallet's lock status. -\item \textbf{Guardian Storage:} Used to store a wallet's guardians\footnote{Note that \emph{GuardianStorage} used to also be used to store a wallet's lock status, but this is now handled by \emph{LockStorage}.}. -\item \textbf{Limit Storage:} Used to store a wallet's current daily limit (and future daily limit, if it is about to change) as well as the wallet's current daily spend. -\item \textbf{Transfer Storage:} Used to store addresses that have been whitelisted by a wallet. -\end{itemize} +Argent maintains an evolving set of registered \emph{Module} contracts. A subset of these \emph{Module} contracts are \emph{SimpleUpgrader} modules that define a migration path from a particular set of old modules to a new set of registered modules, i.e. it contains the list of modules to disable and the list of modules to enable to go from the old set to the new set. A user can perform an upgrade of their modules using one of the registered \emph{SimpleUpgrader} contracts. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Meta-Transactions} -\label{sec:meta-transactions} -Meta-Transactions are implemented via a dedicated \emph{RelayerManager} feature. It implements a permissionless method \emph{execute()} that is meant to be called by a relayer account. The relayer must pass to the \emph{execute()} function an intention and the signature(s) of this intention by the originator(s) of that intention. As described in Section~\ref{sec:eth-less-account}, this pattern allows ether-less accounts to perform operations on the wallet without the need to directly call the corresponding feature methods to do so. - -The \emph{RelayerManager} calls the target feature (e.g. \emph{TransferManager} for token transfers) to determine the number of required signature(s) and to run the desired business logic (e.g. transfer of tokens). +\subsection{Modules} +All the functionalities of the Argent wallet are currently implemented in a single module. To structure the code, the \emph{ArgentModule} inherits from several abstract modules that each implements a coherent subset of the logic. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\subsection{Features} +\subsubsection{RelayerManager}\label{RelayerManager} -\subsubsection{RelayerManager feature}\label{RelayerManager} +The RelayerManager module is the entry point for meta-transactions. A relayer calls the \emph{execute()} method of the \emph{RelayerManager}, which validates the signatures provided, calls the required target method to run the business logic and optionally refunds the relayer's gas cost. -This feature is the entry point for meta-transactions. A relayer calls the \emph{execute()} method of the \emph{RelayerManager}, which validates the signatures provided, calls the required target feature to run the business logic and optionally refunds the relayer's gas cost. +\subsubsection{SecurityManager} -\subsubsection{GuardianManager feature} +The SecurityManager module contains all the logic related to the security of the wallet; namely adding and removing guardians, locking and unlocking the wallet, recovering and transfering ownership. -This feature is used by the wallet owner to add or revoke a guardian. The addition or revokation of a guardian is done in two steps: an addition (or revokation) step that takes 24h to complete, followed by a confirmation (or cancellation) step that needs to be done in a subsequent 12h window period. +\subsubsection{TransactionManager} -\subsubsection{LockManager feature} +The TransactionManager module contains all the logic to execute multi-calls, including adding or removing a trusted contact, starting or closing a session, and executing multi-calls with the correct authorisation. -This feature is used by guardians to lock or unlock a wallet. +\subsubsection{BaseModule} -\subsubsection{RecoveryManager feature} +The BaseModule is a base contract containing logic common to all modules. -This feature is used to change the owner of a wallet to a new owner. It can be executed immediately if the owner is still in possession of the current owner account (transfer ownership), or with a delay if the owner account is lost or stolen (recovery). Both operations need to be approved by guardians. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Storage} +\label{sec:storage} +To enable cost-efficient upgrades, the part of the wallet state that must be permanent across upgrades is stored on dedicated storage contracts. -\subsubsection{TransferManager feature} +The current version of the wallet uses two storage contracts: +\begin{itemize} + \item \textbf{Transfer Storage:} Used to store the trusted contacts of a wallet. + \item \textbf{Guardian Storage:} Used to store the list of guardians of a wallet. +\end{itemize} -This feature lets users perform transfers of ETH and ERC20 tokens, approve ERC20 tokens for third-party contracts, or call third-party contracts directly. Calling contracts can be coupled with a value transfer by either providing an ETH amount or approving a spender to widthraw an ERC20 amount as part of the same transaction. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Meta-Transactions} +\label{sec:meta-transactions} +Meta-Transactions are implemented in the abstract \emph{RelayerManager} from which the \emph{ArgentModule} inherit. It implements a permissionless method \emph{execute()} that is meant to be called by a relayer account. The relayer must pass to the \emph{execute()} function an intention and the signature(s) of this intention by the originator(s) of that intention. As described in Section~\ref{sec:eth-less-account}, this pattern allows ether-less accounts to perform operations on the wallet without the need to directly call the corresponding feature methods to do so. -All transfers of value can be done either to whitelisted addresses without any limit, or to non-whitelisted addresses within a certain daily allowance. If the daily limit is reached for a given period, the transfer is set to a pending state and will only be executed after 24h. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Dapp Registry} -\subsubsection{ApprovedTransfer feature} +The DappRegistry maintains a list of registries, where each registry is a mapping between a set of authorised addresses and \emph{Filter} contracts that implement the conditions that must be satisfied to send value and/or data to these authorised addresses. In theory up to 256 registries can be defined on the DappRegistry but the current version only defines two: registry Id[0] containing all the dapps that are natively integrated in the Argent client application and registry Id[1] containing a list of curated dapps that can be accessed through WalletConnect. -This feature lets users perform instant transfers of ETH and ERC20 tokens, approval of ERC20 tokens, or contract calls to non-whitelisted addresses with the signed approval of a majority of guardians. It is also used to make instant changes to the daily transfer limit. +Each registry has an owner who is the only account that can request the addition or update of an entry in the registry. Once the addition/update has been requested a timelock period of 7 days must pass before the operation can be completed. Anybody can trigger the confirmation transaction provided that the timelock period has expired. Removing an entry from a registry must be triggered by the owner of the registry and is immediate. The Argent multisig is the owner of registry Id[0] and Id[1]. -\subsubsection{TokenExchanger feature} +In addition, the owner of registry Id[0] has a special role as it is the only account authorised to create a new registry or to change the timelock period of the \emph{DappRegistry} contract. Changing the timelock is also a 2 step process where the confirmation transaction can only be executed after the old timelock period as expired. We note that it is not possible to remove an existing registry because that would allow the owner of registry Id[0] to replace registries that have already been enabled by users with a new (potentially maliciously populated) registry. -This feature lets users trade ETH and ERC20 tokens using Paraswap. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Filters} +\label{sec:filters} -\subsubsection{CompoundManager feature} +Filters are smart-contracts that expose a single method that validates: +\begin{itemize} + \item that a contract call from a wallet to an authorised address is considered secure + \item that a contract call from a wallet to an ERC20/ERC721/ERC1155 token where the authorised address is the spender is considered secure + \item that an ETH transfer from the wallet to the authorised address is considered secure +\end{itemize} + We note that \textit{secure} is to be understood in the context of an Argent wallet whose assets need to be protected even if the owner key is compromised. \\ -This feature lets users lend and borrow tokens with the Compound protocol. +Some example of filters added to registry Id[0]: -\subsubsection{NftTransfer feature} +\subsubsection{CompoundCTokenFilter} +The CompoundCTokenFilter applies to all cTokens but there must be one filter per cToken as defined by its underlying. It only accepts contract calls to a cToken to \textit{mint}, \textit{redeem}, \textit{redeemUnderlying}, \textit{borrow}, and \textit{repayBorrow}. All other methods are blocked. +The CompoundCTokenFilter blocks all contract calls to ERC20/ERC721/ERC1155 tokens where the spender is the cToken to prevent e.g. transfering DAI to the cEther contract. The only exception is an ERC20 approve on the underlying of the cToken. -This feature lets users transfer collectibles that comply to the ERC-721 interface. +\subsubsection{UniswapV2UniZapFilter} +The UniswapV2UniZapFilter lets a wallet add/remove liquidity to Uniswap V2 pools via the UniZap contract deployed at 0xbCc492DF37bD4ec651D46d72230E340c9ec1950c. It will only allow adding/removing liquidity to pools that have been flagged as tradable in the \textit{TokenRegistry} based on their underlying tokens and liquidity. It also checks that the beneficiary of the resulting tokens is the calling wallet. +The UniswapV2UniZapFilter lets a wallet approve the UniZap contract as the spender of ERC20 tokens to enable adding liquidity from a token or removing liquidity by burning LP tokens. All other standard ERC20/ERC721/ERC1155 methods are blocked. -\subsubsection{MakerManagerV2 feature} +\subsubsection{LidoFilter} +The LidoFilter lets a wallet swap ETH for stETH by allowing calls to the \textit{submit} method of the Lido contract. All other methods calls are disabled. -This feature lets users invest their DAI in the DSR (Dai Savings Rate) or borrow DAI with the Maker protocol. +\subsubsection{AaveV2Filter} +The AaveV2Filter lets a wallet add/remove liquidity to the V2 version of the Aave protocol provided that the beneficiary of the resulting tokens is the calling wallet. \end{document} \ No newline at end of file diff --git a/test-integration/filter-aaveV1.js b/test-integration/filter-aaveV1.js new file mode 100644 index 000000000..56b5cc113 --- /dev/null +++ b/test-integration/filter-aaveV1.js @@ -0,0 +1,288 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const { expect } = require("chai"); +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const Proxy = artifacts.require("Proxy"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const AaveV1LendingPoolFilter = artifacts.require("AaveV1LendingPoolFilter"); +const OnlyApproveFilter = artifacts.require("OnlyApproveFilter"); +const AaveV1ATokenFilter = artifacts.require("AaveV1ATokenFilter"); +const IAaveV1LendingPool = artifacts.require("IAaveV1LendingPool"); +const IAToken = artifacts.require("IAToken"); +const IUSDCToken = artifacts.require("IUSDCToken"); +const TokenRegistry = artifacts.require("TokenRegistry"); + +// Utils +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeCalls } = require("../utils/utilities.js"); + +const AAVE_ETH_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("AaveV1 Filter", (accounts) => { + let manager; + + const owner = accounts[1]; + const relayer = accounts[4]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let walletImplementation; + let filter; + let aTokenFilter; + let dappRegistry; + + let uniswapRouter; + + let aaveLendingPool; + let aaveLendingPoolCore; + let aToken; + let usdcToken; + let aUSDCToken; + let tokenRegistry; + + before(async () => { + // Wire up AaveV1 + aaveLendingPoolCore = "0x3dfd23A6c5E8BbcFc9581d2E864a68feb6a076d3"; + aaveLendingPool = await IAaveV1LendingPool.at("0x398eC7346DcD622eDc5ae82352F02bE94C62d119"); + aToken = await IAToken.at("0x3a3A65aAb0dd2A17E3F1947bA16138cd37d08c04"); + usdcToken = await IUSDCToken.at("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); + aUSDCToken = await IAToken.at("0x9ba00d6856a4edf4665bca2c2309936572473b7e"); + + // Deploy and fund UniswapV2 + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + const weth = await WETH.new(); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + tokenRegistry = await TokenRegistry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + filter = await AaveV1LendingPoolFilter.new(); + aTokenFilter = await AaveV1ATokenFilter.new(); + const approveFilter = await OnlyApproveFilter.new(); + await dappRegistry.addDapp(0, aaveLendingPoolCore, approveFilter.address); + await dappRegistry.addDapp(0, aaveLendingPool.address, filter.address); + await dappRegistry.addDapp(0, aToken.address, aTokenFilter.address); + await dappRegistry.addDapp(0, aUSDCToken.address, aTokenFilter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + walletImplementation = await BaseWallet.new(); + manager = new RelayManager(guardianStorage.address, tokenRegistry.address); + }); + + beforeEach(async () => { + // create wallet + const proxy = await Proxy.new(walletImplementation.address); + wallet = await BaseWallet.at(proxy.address); + await wallet.init(owner, [module.address]); + + // fund wallet + await wallet.send(10000000); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + describe("deposit", () => { + it("should allow deposits of ETH on behalf of wallet", async () => { + const transactions = encodeCalls([ + [aaveLendingPool, "deposit", [AAVE_ETH_TOKEN, 1000, ""], 1000] + ]); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `deposit failed: "${error}"`); + + const event = await utils.getEvent(txReceipt, aaveLendingPool, "Deposit"); + assert.equal(event.args._reserve, AAVE_ETH_TOKEN); + assert.equal(event.args._user, wallet.address); + assert.equal(event.args._amount, 1000); + + const balance = await aToken.balanceOf(wallet.address); + expect(balance).to.eq.BN(1000); + }); + + it("should allow deposits of ERC20 on behalf of wallet", async () => { + // Fund the wallet with 1000 USDC tokens + const masterMinter = "0xe982615d461dd5cd06575bbea87624fda4e3de17"; + await usdcToken.configureMinter(accounts[0], web3.utils.toWei("10000"), { from: masterMinter }); + await usdcToken.mint(wallet.address, 1000); + const usdcTokenBalance = await usdcToken.balanceOf(wallet.address); + expect(usdcTokenBalance).to.eq.BN(1000); + + const transactions = encodeCalls([ + [usdcToken, "approve", [aaveLendingPoolCore, 1000]], + [aaveLendingPool, "deposit", [usdcToken.address, 1000, ""]] + ]); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `deposit failed: "${error}"`); + + const event = await utils.getEvent(txReceipt, aaveLendingPool, "Deposit"); + assert.equal(event.args._reserve, usdcToken.address); + assert.equal(event.args._user, wallet.address); + assert.equal(event.args._amount, 1000); + + const balance = await aUSDCToken.balanceOf(wallet.address); + expect(balance).to.eq.BN(1000); + }); + + it("should not allow calling forbidden lending pool methods", async () => { + const transactions = encodeCalls([ + [aaveLendingPool, "borrow", [aToken.address, 10, 0, 0]] + ]); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success, "borrow should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + }); + + describe("redeem", () => { + it("should allow redeem of ETH to wallet", async () => { + // Fund the wallet with 1000 wei and deposit them to Aave + let transactions = encodeCalls([ + [aaveLendingPool, "deposit", [AAVE_ETH_TOKEN, 1000, ""], 1000] + ]); + await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner]); + + // Redeem the 1000 wei tokens + transactions = encodeCalls([ + [aToken, "redeem", [1000], 0] + ]); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `redeem failed: "${error}"`); + + const event = await utils.getEvent(txReceipt, aToken, "Redeem"); + assert.equal(event.args._from, wallet.address); + assert.equal(event.args._value, 1000); + }); + + it("should allow redeem of ERC20 to wallet", async () => { + // Fund the wallet with 1000 USDC tokens and deposit them to Aave + const masterMinter = "0xe982615d461dd5cd06575bbea87624fda4e3de17"; + await usdcToken.configureMinter(accounts[0], web3.utils.toWei("10000"), { from: masterMinter }); + await usdcToken.mint(wallet.address, 1000); + const usdcTokenBalance = await usdcToken.balanceOf(wallet.address); + expect(usdcTokenBalance).to.eq.BN(1000); + + let transactions = encodeCalls([ + [usdcToken, "approve", [aaveLendingPoolCore, 1000]], + [aaveLendingPool, "deposit", [usdcToken.address, 1000, ""]] + ]); + + await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + // Redeem the 1000 aUSDC tokens + transactions = encodeCalls([ + [aUSDCToken, "redeem", [1000], 0] + ]); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `redeem failed: "${error}"`); + + const event = await utils.getEvent(txReceipt, aUSDCToken, "Redeem"); + assert.equal(event.args._from, wallet.address); + assert.equal(event.args._value, 1000); + }); + }); +}); diff --git a/test-integration/filter-lido.js b/test-integration/filter-lido.js new file mode 100644 index 000000000..13fc363b9 --- /dev/null +++ b/test-integration/filter-lido.js @@ -0,0 +1,210 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +chai.use(bnChai(BN)); + +// Argent +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const LidoFilter = artifacts.require("LidoFilter"); +const CurveFilter = artifacts.require("CurveFilter"); +const ILido = artifacts.require("ILido"); +const ICurvePool = artifacts.require("ICurvePool"); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeTransaction } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("Lido Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const relayer = accounts[4]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let lidoFilter; + let curveFilter; + let dappRegistry; + let uniswapRouter; + + let lido; + let curve; + + before(async () => { + // Lido contract on mainnet + lido = await ILido.at("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); + curve = await ICurvePool.at("0xdc24316b9ae028f1497c275eb9192a3ea0f67022"); + + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + const weth = await WETH.new(); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + lidoFilter = await LidoFilter.new(); + curveFilter = await CurveFilter.new(); + await dappRegistry.addDapp(0, lido.address, lidoFilter.address); + await dappRegistry.addDapp(0, curve.address, curveFilter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("0.1")); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + describe("Lido staking", () => { + it("should allow staking from wallet via fallback", async () => { + const transaction = encodeTransaction(lido.address, 100, ZERO_BYTES); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `deposit failed: "${error}"`); + + const walletBalance = await lido.balanceOf(wallet.address); + assert.closeTo(walletBalance.toNumber(), 99, 1); + }); + + it("should allow staking from wallet via submit", async () => { + const data = lido.contract.methods.submit(accounts[5]).encodeABI(); + const transaction = encodeTransaction(lido.address, 100, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `deposit failed: "${error}"`); + + const walletBalance = await lido.balanceOf(wallet.address); + assert.closeTo(walletBalance.toNumber(), 99, 1); + }); + }); + + describe("Selling via CurvePool", () => { + beforeEach(async () => { + // Stake some funds to use to test selling + const data = lido.contract.methods.submit(accounts[5]).encodeABI(); + const transaction = encodeTransaction(lido.address, 100, data); + + await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + }); + + it("should allow selling stETH via Curve", async () => { + const transactions = []; + let data = lido.contract.methods.approve(curve.address, 99).encodeABI(); + let transaction = encodeTransaction(lido.address, 0, data); + transactions.push(transaction); + data = curve.contract.methods.exchange(1, 0, 99, 1).encodeABI(); + transaction = encodeTransaction(curve.address, 0, data); + transactions.push(transaction); + + const before = await utils.getBalance(wallet.address); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + console.log("Gas to exchange stETH for ETH", txReceipt.gasUsed); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `exchange failed: "${error}"`); + + // const event = await utils.getEvent(txReceipt, curve, "TokenExchange"); + // assert.equal(event.args.tokens_sold, 99); // Sold stETH + // assert.closeTo(new BN(event.args.tokens_bought).toNumber(), new BN(96).toNumber(), 3); // Got ETH + // Check ETH was received + const after = await utils.getBalance(wallet.address); + assert.closeTo(after.sub(before).toNumber(), 96, 3); + + // Check only dust stETH left + const walletBalance = await lido.balanceOf(wallet.address); + assert.closeTo(walletBalance.toNumber(), 1, 1); + }); + }); +}); diff --git a/test/aaveV2.js b/test/aaveV2.js new file mode 100644 index 000000000..81ac95b3a --- /dev/null +++ b/test/aaveV2.js @@ -0,0 +1,193 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const Filter = artifacts.require("AaveV2Filter"); +const AaveV2LendingPool = artifacts.require("AaveV2LendingPoolMock"); +const ERC20 = artifacts.require("TestERC20"); +const WalletFactory = artifacts.require("WalletFactory"); + +// Utils + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeCalls, encodeTransaction } = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; +const DECIMALS = 18; // number of decimal for TOKEN_A, TOKEN_B contracts + +const RelayManager = require("../utils/relay-manager"); + +contract("AaveV2 Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const relayer = accounts[4]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let filter; + let dappRegistry; + let factory; + + let uniswapRouter; + + let tokenA; + let aave; + + before(async () => { + // Deploy test tokens + tokenA = await ERC20.new([infrastructure], web3.utils.toWei("1000"), DECIMALS); + + // Deploy AaveV2 + aave = await AaveV2LendingPool.new([tokenA.address]); + + // Deploy and fund UniswapV2 + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + const weth = await WETH.new(); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + filter = await Filter.new(); + await dappRegistry.addDapp(0, aave.address, filter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("0.1")); + await tokenA.mint(wallet.address, web3.utils.toWei("1000")); + }); + + describe("Aave V2", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + const amount = web3.utils.toWei("1"); + + const multiCall = async (transactions) => { + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + return utils.parseRelayReceipt(txReceipt); + }; + + const deposit = async (beneficiary) => multiCall(encodeCalls([ + [tokenA, "approve", [aave.address, amount]], + [aave, "deposit", [tokenA.address, amount, beneficiary, ""]] + ])); + + const withdraw = async (beneficiary) => multiCall(encodeCalls([ + [aave, "withdraw", [tokenA.address, amount, beneficiary]] + ])); + + it("should allow deposits on behalf of wallet", async () => { + const { success, error } = await deposit(wallet.address); + assert.isTrue(success, `deposit failed: "${error}"`); + }); + + it("should not allow deposits on behalf of non-wallet", async () => { + const { success, error } = await deposit(infrastructure); + assert.isFalse(success, "deposit should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should allow withdrawals to wallet", async () => { + await deposit(wallet.address); + const { success, error } = await withdraw(wallet.address); + assert.isTrue(success, `withdraw failed: "${error}"`); + }); + + it("should not allow withdrawals to non-wallet", async () => { + await deposit(wallet.address); + const { success, error } = await withdraw(infrastructure); + assert.isFalse(success, "withdraw should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow direct transfers to lending pool", async () => { + const { success, error } = await multiCall(encodeCalls([ + [tokenA, "transfer", [aave.address, amount]], + ])); + assert.isFalse(success, "transfer should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow calling forbidden lending pool methods", async () => { + const { success, error } = await multiCall(encodeCalls([ + [aave, "borrow", [tokenA.address, amount, 0, 0, wallet.address]] + ])); + assert.isFalse(success, "borrow should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow sending ETH to lending pool", async () => { + const { success, error } = await multiCall([encodeTransaction(aave.address, web3.utils.toWei("0.01"), "0x")]); + assert.isFalse(success, "sending ETH should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + }); +}); diff --git a/test/approvedTransfer.js b/test/approvedTransfer.js deleted file mode 100644 index 2b626744e..000000000 --- a/test/approvedTransfer.js +++ /dev/null @@ -1,436 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); -const Registry = artifacts.require("ModuleRegistry"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const GuardianManager = artifacts.require("GuardianManager"); -const ApprovedTransfer = artifacts.require("ApprovedTransfer"); -const ERC20 = artifacts.require("TestERC20"); -const WETH = artifacts.require("WETH9"); -const TestContract = artifacts.require("TestContract"); -const TestLimitFeature = artifacts.require("TestLimitFeature"); - -const RelayManager = require("../utils/relay-manager"); -const { sortWalletByAddress, parseRelayReceipt, ETH_TOKEN, getBalance } = require("../utils/utilities.js"); -const utils = require("../utils/utilities.js"); - -const ZERO_BYTES32 = ethers.constants.HashZero; - -contract("ApprovedTransfer", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const guardian1 = accounts[2]; - const guardian2 = accounts[3]; - const guardian3 = accounts[4]; - const recipient = accounts[5]; - - let wallet; - let walletImplementation; - let guardianManager; - let approvedTransfer; - let limitFeature; - let relayerManager; - let erc20; - let weth; - const amountToTransfer = 10000; - let contract; - let versionManager; - - before(async () => { - weth = await WETH.new(); - const registry = await Registry.new(); - const lockStorage = await LockStorage.new(); - const guardianStorage = await GuardianStorage.new(); - const limitStorage = await LimitStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - limitStorage.address); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, - 12); - approvedTransfer = await ApprovedTransfer.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - versionManager.address, - weth.address); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - walletImplementation = await BaseWallet.new(); - - limitFeature = await TestLimitFeature.new( - guardianStorage.address, limitStorage.address, versionManager.address); - - await versionManager.addVersion([ - approvedTransfer.address, guardianManager.address, relayerManager.address, limitFeature.address, - ], []); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - - const decimals = 12; // number of decimal for TOKN contract - erc20 = await ERC20.new([infrastructure, wallet.address], 10000000, decimals); // TOKN contract with 10M tokens (5M TOKN for wallet and 5M TOKN for account[0]) - await wallet.send(50000000); - }); - - async function addGuardians(guardians) { - // guardians can be BaseWallet or ContractWrapper objects - for (const guardian of guardians) { - await guardianManager.addGuardian(wallet.address, guardian, { from: owner }); - } - - await utils.increaseTime(30); - for (let i = 1; i < guardians.length; i += 1) { - await guardianManager.confirmGuardianAddition(wallet.address, guardians[i]); - } - const count = (await guardianManager.guardianCount(wallet.address)).toNumber(); - assert.equal(count, guardians.length, `${guardians.length} guardians should be added`); - } - - async function createSmartContractGuardians(guardians) { - const wallets = []; - for (const guardian of guardians) { - const proxy = await Proxy.new(walletImplementation.address); - const guardianWallet = await BaseWallet.at(proxy.address); - - await guardianWallet.init(guardian, [versionManager.address]); - await versionManager.upgradeWallet(guardianWallet.address, await versionManager.lastVersion(), { from: guardian }); - wallets.push(guardianWallet.address); - } - return wallets; - } - - async function transferToken(_token, _signers) { - const to = recipient; - - const before = _token === ETH_TOKEN ? await getBalance(to) : await erc20.balanceOf(to); - await manager.relay(approvedTransfer, "transferToken", - [wallet.address, _token, to, amountToTransfer, ZERO_BYTES32], wallet, _signers); - const after = _token === ETH_TOKEN ? await getBalance(to) : await erc20.balanceOf(to); - assert.equal(after.sub(before).toNumber(), amountToTransfer, "should have transfered the amount"); - } - - async function callContract(_signers) { - const before = await getBalance(contract.address); - const newState = parseInt((await contract.state()).toString(), 10) + 1; - const dataToTransfer = contract.contract.methods.setState([newState]).encodeABI(); - await manager.relay(approvedTransfer, "callContract", - [wallet.address, contract.address, amountToTransfer, dataToTransfer], wallet, _signers); - const after = await getBalance(contract.address); - assert.equal(after.sub(before).toNumber(), amountToTransfer, "should have transfered the ETH amount"); - assert.equal((await contract.state()).toNumber(), newState, "the state of the external contract should have been changed"); - } - - describe("Transfer", () => { - async function expectFailingTransferToken(_token, _signers, _reason) { - await truffleAssert.reverts( - manager.relay( - approvedTransfer, - "transferToken", - [wallet.address, _token, recipient, amountToTransfer, ZERO_BYTES32], - wallet, - _signers, - ), _reason, - ); - } - - it("should fail to transfer ETH on a wallet with no guardian", async () => { - await expectFailingTransferToken(ETH_TOKEN, [owner], "AT: no guardians set on wallet"); - }); - - describe("Approved by EOA guardians", () => { - describe("1 guardian", () => { - beforeEach(async () => { - await addGuardians([guardian1]); - }); - it("should transfer ETH with 1 confirmation for 1 guardian", async () => { - await transferToken(ETH_TOKEN, [owner, guardian1]); - }); - it("should fail to transfer ETH when signer is not a guardian", async () => { - await expectFailingTransferToken(ETH_TOKEN, [owner, guardian2], "RM: Invalid signatures"); - }); - it("should transfer ERC20 with 1 confirmation for 1 guardian", async () => { - await transferToken(erc20.address, [owner, guardian1]); - }); - }); - describe("2 guardians", () => { - beforeEach(async () => { - await addGuardians([guardian1, guardian2]); - }); - it("should transfer ETH with 1 confirmation for 2 guardians", async () => { - await transferToken(ETH_TOKEN, [owner, guardian1]); - }); - }); - describe("3 guardians", () => { - beforeEach(async () => { - await addGuardians([guardian1, guardian2, guardian3]); - }); - it("should not transfer ETH with 1 confirmation for 3 guardians", async () => { - await expectFailingTransferToken(ETH_TOKEN, [owner, guardian1], "RM: Wrong number of signatures"); - }); - it("should transfer ETH with 2 confirmations for 3 guardians", async () => { - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - }); - it("should fail to transfer ERC20 with 1 confirmation for 3 guardians", async () => { - await expectFailingTransferToken(erc20.address, [owner, guardian1], "RM: Wrong number of signatures"); - }); - it("should transfer ERC20 with 2 confirmations for 3 guardians", async () => { - await transferToken(erc20.address, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - }); - }); - }); - - describe("Approved by smart-contract guardians", () => { - describe("1 guardian", () => { - beforeEach(async () => { - await addGuardians(await createSmartContractGuardians([guardian1])); - }); - it("should transfer ETH with 1 confirmation for 1 guardian", async () => { - await transferToken(ETH_TOKEN, [owner, guardian1]); - }); - it("should transfer ERC20 with 1 confirmation for 1 guardian", async () => { - await transferToken(erc20.address, [owner, guardian1]); - }); - }); - describe("2 guardians", () => { - beforeEach(async () => { - await addGuardians(await createSmartContractGuardians([guardian1, guardian2])); - }); - it("should transfer ETH with 1 confirmation for 2 guardians", async () => { - await transferToken(ETH_TOKEN, [owner, guardian1]); - }); - }); - describe("3 guardians", () => { - beforeEach(async () => { - await addGuardians(await createSmartContractGuardians([guardian1, guardian2, guardian3])); - }); - it("should not transfer ETH with 1 confirmation for 3 guardians", async () => { - await expectFailingTransferToken(ETH_TOKEN, [owner, guardian1], "RM: Wrong number of signatures"); - }); - it("should transfer ETH with 2 confirmations for 3 guardians", async () => { - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - }); - it("should not transfer ERC20 with 1 confirmations for 3 guardians", async () => { - await expectFailingTransferToken(erc20.address, [owner, guardian1], "RM: Wrong number of signatures"); - }); - it("should transfer ERC20 with 2 confirmations for 3 guardians", async () => { - await transferToken(erc20.address, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - }); - }); - }); - - describe("Approved by EOA and smart-contract guardians", () => { - it("should transfer ETH with 1 EOA guardian and 2 smart-contract guardians", async () => { - await addGuardians([guardian1, ...(await createSmartContractGuardians([guardian2, guardian3]))]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian3])]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian2, guardian3])]); - }); - it("should transfer ETH with 2 EOA guardians and 1 smart-contract guardian", async () => { - await addGuardians([guardian1, guardian2, ...await createSmartContractGuardians([guardian3])]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian1, guardian3])]); - await transferToken(ETH_TOKEN, [owner, ...sortWalletByAddress([guardian2, guardian3])]); - }); - }); - }); - - describe("Contract call", () => { - it("should fail to call contract on a wallet with no guardian", async () => { - contract = await TestContract.new(); - const dataToTransfer = contract.contract.methods.setState([1]).encodeABI(); - - await truffleAssert.reverts( - manager.relay( - approvedTransfer, - "callContract", - [wallet.address, contract.address, amountToTransfer, dataToTransfer], - wallet, - [owner], - ), - "AT: no guardians set on wallet", - ); - }); - - describe("Approved by 1 EOA and 2 smart-contract guardians", () => { - beforeEach(async () => { - contract = await TestContract.new(); - assert.equal(await contract.state(), 0, "initial contract state should be 0"); - await addGuardians([guardian1, ...(await createSmartContractGuardians([guardian2, guardian3]))]); - }); - - it("should call a contract and transfer ETH with 1 EOA guardian and 2 smart-contract guardians", async () => { - await callContract([owner, ...sortWalletByAddress([guardian1, guardian2])]); - await callContract([owner, ...sortWalletByAddress([guardian1, guardian3])]); - await callContract([owner, ...sortWalletByAddress([guardian2, guardian3])]); - }); - - it("should not be able to call the wallet itself", async () => { - const txReceipt = await manager.relay(approvedTransfer, "callContract", - [wallet.address, wallet.address, amountToTransfer, ethers.constants.HashZero], - wallet, - [owner, ...sortWalletByAddress([guardian1, guardian2])]); - const { success, error } = parseRelayReceipt(txReceipt); - assert.isFalse(success); - assert.equal(error, "BT: Forbidden contract"); - }); - }); - }); - - describe("Approve token and Contract call", () => { - describe("Approved by 1 EOA and 2 smart-contract guardians", () => { - const amountToApprove = 10000; - - beforeEach(async () => { - contract = await TestContract.new(); - assert.equal(await contract.state(), 0, "initial contract state should be 0"); - await addGuardians([guardian1, ...(await createSmartContractGuardians([guardian2, guardian3]))]); - }); - - describe("Invalid Target", () => { - async function expectFailingApproveTokenAndCallContract(target) { - const invalidData = contract.contract.methods.setStateAndPayToken(2, erc20.address, amountToApprove).encodeABI(); - const txReceipt = await manager.relay(approvedTransfer, "approveTokenAndCallContract", - [wallet.address, erc20.address, wallet.address, amountToApprove, target.address, invalidData], - wallet, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - const { success, error } = parseRelayReceipt(txReceipt); - assert.isFalse(success); - assert.equal(error, "BT: Forbidden contract"); - } - - it("should revert when target contract is the wallet", async () => { - await expectFailingApproveTokenAndCallContract(wallet); - }); - - it("should revert when target contract is an authorised module", async () => { - await expectFailingApproveTokenAndCallContract(approvedTransfer); - }); - }); - - describe("Valid Target", () => { - async function approveTokenAndCallContract(_signers, _consumerAddress = contract.address, _wrapEth = false) { - const newState = parseInt((await contract.state()).toString(), 10) + 1; - const token = _wrapEth ? weth : erc20; - const fun = _consumerAddress === contract.address ? "setStateAndPayToken" : "setStateAndPayTokenWithConsumer"; - const data = contract.contract.methods[fun](newState, token.address, amountToApprove).encodeABI(); - const before = await token.balanceOf(contract.address); - const params = [wallet.address] - .concat(_wrapEth ? [] : [erc20.address]) - .concat([_consumerAddress, amountToApprove, contract.address, data]); - const method = _wrapEth ? "approveWethAndCallContract" : "approveTokenAndCallContract"; - await manager.relay( - approvedTransfer, - method, - params, - wallet, - _signers, - ); - const after = await token.balanceOf(contract.address); - assert.equal(after.sub(before).toNumber(), amountToApprove, "should have approved and transfered the token amount"); - assert.equal((await contract.state()).toNumber(), newState, "the state of the external contract should have been changed"); - } - - it("should approve token for a spender then call a contract with 3 guardians, spender = contract", async () => { - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian2])]); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian3])]); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian2, guardian3])]); - }); - - it("should approve WETH for a spender then call a contract with 3 guardians, spender = contract", async () => { - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian2])], contract.address, true); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian3])], contract.address, true); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian2, guardian3])], contract.address, true); - }); - - it("should approve token for a spender then call a contract with 3 guardians, spender != contract", async () => { - const consumer = await contract.tokenConsumer(); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian2])], consumer); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian1, guardian3])], consumer); - await approveTokenAndCallContract([owner, ...sortWalletByAddress([guardian2, guardian3])], consumer); - }); - - it("should restore the original approved amount", async () => { - const consumer = await contract.tokenConsumer(); - const allowanceBefore = await erc20.allowance(wallet.address, consumer); - const balanceBefore = await erc20.balanceOf(contract.address); - - const dataToTransfer = contract.contract.methods.setStateAndPayTokenWithConsumer(2, erc20.address, amountToApprove).encodeABI(); - await manager.relay(approvedTransfer, "approveTokenAndCallContract", - [wallet.address, erc20.address, consumer, amountToApprove, contract.address, dataToTransfer], - wallet, [owner, ...sortWalletByAddress([guardian1, guardian2])]); - - const balanceAfter = await erc20.balanceOf(contract.address); - assert.equal(balanceAfter.sub(balanceBefore).toNumber(), amountToApprove, "should have approved and transfered the token amount"); - assert.equal((await contract.state()).toNumber(), 2, "the state of the external contract should have been changed"); - - const allowanceAfter = await erc20.allowance(wallet.address, consumer); - assert.equal(allowanceAfter.toNumber(), allowanceBefore.toNumber()); - }); - }); - }); - }); - - describe("Daily Limit", () => { - beforeEach(async () => { - await addGuardians([guardian1]); - await limitFeature.setLimitAndDailySpent(wallet.address, 1000000, 500); - }); - - it("should change the limit immediately", async () => { - let limit = await limitFeature.getLimit(wallet.address); - assert.equal(limit.toNumber(), 1000000, "limit should be 1000000"); - await manager.relay(approvedTransfer, "changeLimit", [wallet.address, 4000000], wallet, [owner, guardian1]); - limit = await limitFeature.getLimit(wallet.address); - assert.equal(limit.toNumber(), 4000000, "limit should be changed immediately"); - }); - - it("should reset the daily consumption", async () => { - let dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 500, "dailySpent should be 500"); - await manager.relay(approvedTransfer, "resetDailySpent", [wallet.address], wallet, [owner, guardian1]); - dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 0, "dailySpent should be 0"); - }); - - it("should reset the daily consumption after a transfer", async () => { - let dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 500, "dailySpent should be 500"); - await transferToken(ETH_TOKEN, [owner, guardian1]); - dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 0, "dailySpent should be 0"); - }); - - it("should reset the daily consumption after a call contract", async () => { - let dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 500, "dailySpent should be 500"); - await callContract([owner, guardian1]); - dailySpent = await limitFeature.getDailySpent(wallet.address); - assert.equal(dailySpent.toNumber(), 0, "dailySpent should be 0"); - }); - }); -}); diff --git a/test/asset.js b/test/asset.js new file mode 100644 index 000000000..5ab040009 --- /dev/null +++ b/test/asset.js @@ -0,0 +1,245 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert, expect } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const Authoriser = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const ERC20 = artifacts.require("TestERC20"); +const ERC721 = artifacts.require("TestERC721"); +const CK = artifacts.require("CryptoKittyTest"); +const ERC1155 = artifacts.require("TestERC1155"); + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, addTrustedContact, initNonce } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("ArgentModule", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const sender = accounts[2]; + const guardian1 = accounts[3]; + const recipient = accounts[4]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let authoriser; + let factory; + + let erc20; + let erc721; + let erc1155; + let ck; + + const ckId = 0; + const erc721Id = 7; + const erc1155Id = 4; + + before(async () => { + registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + authoriser = await Authoriser.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + authoriser.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await authoriser.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + await wallet.send(web3.utils.toWei("1")); + // ERC20 + erc20 = await ERC20.new([infrastructure, sender], 10000000, 12); + // ERC721 + erc721 = await ERC721.new(); + await erc721.mint(sender, erc721Id); + // Crypto Kitty + ck = await CK.new(); + await ck.createDumbKitty(sender); + // ERC1155 + erc1155 = await ERC1155.new(); + await erc1155.mint(sender, erc1155Id, 10000000); + }); + + describe("send and receive assets", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); + }); + + it("should send and receive ETH", async () => { + // receive + let before = await utils.getBalance(wallet.address); + await wallet.send(web3.utils.toWei("1"), { from: sender }); + let after = await utils.getBalance(wallet.address); + expect(after.sub(before)).to.gt.BN(0); + // send + before = after; + const transaction = encodeTransaction(recipient, web3.utils.toWei("1"), ZERO_BYTES); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `sending ETH failed with "${error}"`); + after = await utils.getBalance(wallet.address); + expect(after.sub(before)).to.lt.BN(0); + }); + + it("should send and receive ERC20", async () => { + // receive + let before = await erc20.balanceOf(wallet.address); + await erc20.transfer(wallet.address, 10000, { from: sender }); + let after = await erc20.balanceOf(wallet.address); + expect(after.sub(before)).to.eq.BN(10000); + // send + before = after; + const data = await erc20.contract.methods.transfer(recipient, 10000).encodeABI(); + const transaction = encodeTransaction(erc20.address, 0, data, true); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + after = await erc20.balanceOf(wallet.address); + expect(before.sub(after)).to.eq.BN(10000); + }); + + it("should send and receive ERC721", async () => { + // receive + let before = await erc721.balanceOf(wallet.address); + await erc721.safeTransferFrom(sender, wallet.address, erc721Id, { from: sender }); + let after = await erc721.balanceOf(wallet.address); + expect(after.sub(before)).to.eq.BN(1); + // send + before = after; + const data = erc721.contract.methods.safeTransferFrom(wallet.address, recipient, erc721Id).encodeABI(); + const transaction = encodeTransaction(erc721.address, 0, data, true); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + after = await erc721.balanceOf(wallet.address); + expect(before.sub(after)).to.eq.BN(1); + }); + + it("should send and receive CryptoKitty", async () => { + // receive + let before = await ck.balanceOf(wallet.address); + await ck.transfer(wallet.address, ckId, { from: sender }); + let after = await ck.balanceOf(wallet.address); + expect(after.sub(before)).to.eq.BN(1); + // send + before = after; + const data = ck.contract.methods.transfer(recipient, ckId).encodeABI(); + const transaction = utils.encodeTransaction(ck.address, 0, data, true); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + after = await erc721.balanceOf(wallet.address); + expect(before.sub(after)).to.eq.BN(1); + }); + + it("should send and receive ERC1155", async () => { + // receive + let before = await erc1155.balanceOf(wallet.address, erc1155Id); + await erc1155.safeTransferFrom(sender, wallet.address, erc1155Id, 1000, ZERO_BYTES, { from: sender }); + let after = await erc1155.balanceOf(wallet.address, erc1155Id); + expect(after.sub(before)).to.eq.BN(1000); + // send + before = after; + const data = erc1155.contract.methods.safeTransferFrom(wallet.address, recipient, erc1155Id, 1000, ZERO_BYTES).encodeABI(); + const transaction = encodeTransaction(erc1155.address, 0, data, true); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + after = await erc1155.balanceOf(wallet.address, erc1155Id); + expect(before.sub(after)).to.eq.BN(1000); + }); + }); +}); diff --git a/test/authorisation.js b/test/authorisation.js new file mode 100644 index 000000000..358bd1781 --- /dev/null +++ b/test/authorisation.js @@ -0,0 +1,467 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ERC20 = artifacts.require("TestERC20"); +const TestContract = artifacts.require("TestContract"); +const Filter = artifacts.require("TestFilter"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const { assert } = require("chai"); +const truffleAssert = require("truffle-assertions"); + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, initNonce, encodeCalls } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const CUSTOM_REGISTRY_ID = 12; + +const RelayManager = require("../utils/relay-manager"); + +contract("Authorisation", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const nonwhitelisted = accounts[2]; + const guardian1 = accounts[3]; + const recipient = accounts[4]; + const registryOwner = accounts[5]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let erc20; + let filter; + let filter2; + let dappRegistry; + let contract; + let contract2; + let uniswapRouter; + + before(async () => { + registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + uniswapRouter = await UniswapV2Router01.new(); + + contract = await TestContract.new(); + contract2 = await TestContract.new(); + assert.equal(await contract.state(), 0, "initial contract state should be 0"); + assert.equal(await contract2.state(), 0, "initial contract2 state should be 0"); + filter = await Filter.new(); + filter2 = await Filter.new(); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + async function setupRegistries() { + dappRegistry = await DappRegistry.new(SECURITY_PERIOD); + await dappRegistry.createRegistry(CUSTOM_REGISTRY_ID, registryOwner); + await dappRegistry.addDapp(0, contract.address, filter.address); + await dappRegistry.addDapp(CUSTOM_REGISTRY_ID, contract2.address, filter.address, { from: registryOwner }); + await dappRegistry.addDapp(0, recipient, ZERO_ADDRESS); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + await utils.increaseTime(SECURITY_PERIOD + 1); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + } + + beforeEach(async () => { + await setupRegistries(); + }); + + async function enableCustomRegistry() { + assert.equal(await dappRegistry.isEnabledRegistry(wallet.address, CUSTOM_REGISTRY_ID), false, "custom registry should not be enabled"); + const data = dappRegistry.contract.methods.toggleRegistry(CUSTOM_REGISTRY_ID, true).encodeABI(); + const transaction = encodeTransaction(dappRegistry.address, 0, data); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `toggleRegistry failed with "${error}"`); + assert.equal(await dappRegistry.isEnabledRegistry(wallet.address, CUSTOM_REGISTRY_ID), true, "custom registry should be enabled"); + console.log("Gas to call toggleRegistry: ", txReceipt.gasUsed); + } + + async function setupWallet() { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + const decimals = 12; // number of decimal for TOKN contract + + erc20 = await ERC20.new([infrastructure, wallet.address], 10000000, decimals); // TOKN contract with 10M tokens (5M TOKN for wallet and 5M TOKN for account[0]) + await wallet.send(new BN("1000000000000000000")); + + // authorise "DappRegistry.toggleRegistry()" + const txReceipt = await manager.relay(module, "addToWhitelist", [wallet.address, dappRegistry.address], wallet, [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `adding dapp registry to whitelist failed with "${error}"`); + await utils.increaseTime(SECURITY_PERIOD + 1); + + await enableCustomRegistry(); + } + + // wallet-centric functions + + describe("call (un)authorised contract", () => { + beforeEach(async () => { + await setupWallet(); + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + it("should send ETH to authorised address", async () => { + const transaction = encodeTransaction(recipient, 100, ZERO_BYTES); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "call failed"); + console.log("Gas to send ETH: ", txReceipt.gasUsed); + }); + + it("should call authorised contract when filter passes (argent registry)", async () => { + const data = contract.contract.methods.setState(4).encodeABI(); + const transaction = encodeTransaction(contract.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 10, + ETH_TOKEN, + recipient); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "call failed"); + assert.equal(await contract.state(), 4, "contract state should be 4"); + console.log("Gas to call contract: ", txReceipt.gasUsed); + }); + + it("should call authorised contract when filter passes (community registry)", async () => { + const data = contract2.contract.methods.setState(4).encodeABI(); + const transaction = encodeTransaction(contract2.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "call failed"); + assert.equal(await contract.state(), 4, "contract state should be 4"); + console.log("Gas to call contract: ", txReceipt.gasUsed); + }); + + it("should block call to authorised contract when filter doesn't pass", async () => { + const data = contract.contract.methods.setState(5).encodeABI(); + const transaction = encodeTransaction(contract.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isFalse(success, "call should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not send ETH to unauthorised address", async () => { + const transaction = encodeTransaction(nonwhitelisted, 100, ZERO_BYTES); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isFalse(success, "transfer should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + }); + + describe("approve token and call authorised contract", () => { + beforeEach(async () => { + await setupWallet(); + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + it("should call authorised contract when filter pass", async () => { + const transactions = []; + + let data = erc20.contract.methods.approve(contract.address, 100).encodeABI(); + let transaction = encodeTransaction(erc20.address, 0, data); + transactions.push(transaction); + + data = contract.contract.methods.setStateAndPayToken(4, erc20.address, 100).encodeABI(); + transaction = encodeTransaction(contract.address, 0, data); + transactions.push(transaction); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "call failed"); + assert.equal(await contract.state(), 4, "contract state should be 4"); + console.log("Gas to approve token and call contract: ", txReceipt.gasUsed); + }); + + it("should block call to authorised contract when filter doesn't pass", async () => { + const transactions = []; + + let data = erc20.contract.methods.approve(contract.address, 100).encodeABI(); + let transaction = encodeTransaction(erc20.address, 0, data); + transactions.push(transaction); + + data = contract.contract.methods.setStateAndPayToken(5, erc20.address, 100).encodeABI(); + transaction = encodeTransaction(contract.address, 0, data); + transactions.push(transaction); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 10, + ETH_TOKEN, + relayer); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isFalse(success, "call should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + }); + + describe("enable/disable registry for wallet", () => { + beforeEach(async () => { + await setupWallet(); + }); + + it("should allow disabling the Argent Registry", async () => { + const assertToggle = async (enable) => { + const toggle = encodeCalls([[dappRegistry, "toggleRegistry", [0, enable]]]); + const txReceipt = await manager.relay( + module, "multiCall", [wallet.address, toggle], wallet, [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `toggleRegistry failed with "${error}"`); + }; + assert.equal(await dappRegistry.isEnabledRegistry(wallet.address, 0), true, "Argent Registry should be enabled by default"); + await assertToggle(false); + assert.isFalse(await dappRegistry.isEnabledRegistry(wallet.address, 0), "Argent Registry should be disabled"); + await assertToggle(true); + assert.isTrue(await dappRegistry.isEnabledRegistry(wallet.address, 0), "Argent Registry isn't re-enabled"); + }); + + it("should not enable non-existing registry", async () => { + const data = dappRegistry.contract.methods.toggleRegistry(66, true).encodeABI(); + const transaction = encodeTransaction(dappRegistry.address, 0, data); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + assert.isFalse(success, "toggleRegistry should have failed"); + assert.equal(error, "DR: unknown registry"); + }); + }); + + // management of registry contract + + describe("add registry", () => { + it("should not create a duplicate registry", async () => { + await truffleAssert.reverts( + dappRegistry.createRegistry(CUSTOM_REGISTRY_ID, registryOwner, { from: infrastructure }), "DR: duplicate registry" + ); + }); + it("should not create a registry without owner", async () => { + await truffleAssert.reverts( + dappRegistry.createRegistry(CUSTOM_REGISTRY_ID, ZERO_ADDRESS, { from: infrastructure }), "DR: registry owner is 0" + ); + }); + }); + + describe("owner change", () => { + it("changes a registry owner", async () => { + await dappRegistry.changeOwner(0, recipient); + let regOwner = await dappRegistry.registryOwners(0, { from: infrastructure }); + assert.equal(regOwner, recipient, "registry owner change failed"); + await dappRegistry.changeOwner(0, infrastructure, { from: recipient }); + regOwner = await dappRegistry.registryOwners(0); + assert.equal(regOwner, infrastructure, "registry owner change failed"); + }); + + it("can't change a registry to null owner", async () => { + await truffleAssert.reverts( + dappRegistry.changeOwner(0, ZERO_ADDRESS, { from: infrastructure }), "DR: new registry owner is 0" + ); + }); + }); + + describe("timelock change", () => { + it("can change the timelock", async () => { + const tl = (await dappRegistry.timelockPeriod()).toNumber(); + const requestedTl = 12; + await dappRegistry.requestTimelockChange(requestedTl, { from: infrastructure }); + await truffleAssert.reverts( + dappRegistry.confirmTimelockChange(), "DR: can't (yet) change timelock" + ); + let newTl = (await dappRegistry.timelockPeriod()).toNumber(); + assert.equal(newTl, tl, "timelock shouldn't have changed"); + await utils.increaseTime(requestedTl); + await dappRegistry.confirmTimelockChange(); + newTl = (await dappRegistry.timelockPeriod()).toNumber(); + assert.equal(newTl, requestedTl, "timelock change failed"); + + await dappRegistry.requestTimelockChange(tl, { from: infrastructure }); + await utils.increaseTime(newTl); + }); + }); + + // management of registry content + + describe("add/remove dapp", () => { + it("should allow registry owner to add a dapp", async () => { + const { validAfter } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.equal(validAfter.toNumber(), 0); + await dappRegistry.addDapp(0, contract2.address, ZERO_ADDRESS, { from: infrastructure }); + const { validAfter: validAfter2 } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.isTrue(validAfter2.gt(0), "Invalid validAfter after addDapp call"); + }); + it("should not allow registry owner to add duplicate dapp", async () => { + await dappRegistry.addDapp(0, contract2.address, filter.address, { from: infrastructure }); + await truffleAssert.reverts( + dappRegistry.addDapp(0, contract2.address, filter.address, { from: infrastructure }), "DR: dapp already added" + ); + }); + it("should not allow non-owner to add authorisation to the Argent registry", async () => { + await truffleAssert.reverts( + dappRegistry.addDapp(0, contract2.address, filter.address, { from: nonwhitelisted }), "DR: sender != registry owner" + ); + }); + it("should not allow adding authorisation to unknown registry", async () => { + await truffleAssert.reverts( + dappRegistry.addDapp(66, contract2.address, filter.address, { from: nonwhitelisted }), "DR: unknown registry" + ); + }); + it("should allow registry owner to remove a dapp (no pending filter update)", async () => { + await truffleAssert.reverts( + dappRegistry.removeDapp(0, contract2.address, { from: infrastructure }), "DR: unknown dapp" + ); + await dappRegistry.addDapp(0, contract2.address, ZERO_ADDRESS, { from: infrastructure }); + await dappRegistry.removeDapp(0, contract2.address, { from: infrastructure }); + const { validAfter } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.equal(validAfter.toNumber(), 0); + }); + it("should allow registry owner to remove a dapp (with pending filter update)", async () => { + await dappRegistry.addDapp(0, contract2.address, ZERO_ADDRESS, { from: infrastructure }); + await dappRegistry.requestFilterUpdate(0, contract2.address, filter2.address, { from: infrastructure }); + await dappRegistry.removeDapp(0, contract2.address, { from: infrastructure }); + const { validAfter } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.equal(validAfter.toNumber(), 0); + const pendingAuth = await dappRegistry.pendingFilterUpdates(0, contract2.address); + assert.equal(pendingAuth, 0); + }); + }); + + describe("update filter", () => { + it("should allow registry owner to change an existing filter", async () => { + await dappRegistry.addDapp(0, contract2.address, filter.address, { from: infrastructure }); + const { filter: filter_ } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.equal(filter_, filter.address); + await dappRegistry.requestFilterUpdate(0, contract2.address, filter2.address, { from: infrastructure }); + await truffleAssert.reverts( + dappRegistry.confirmFilterUpdate(0, contract2.address, { from: infrastructure }), + "DR: too early to confirm auth" + ); + const tl = (await dappRegistry.timelockPeriod()).toNumber(); + await utils.increaseTime(tl + 1); + await dappRegistry.confirmFilterUpdate(0, contract2.address, { from: infrastructure }); + const { filter: filter2_ } = await dappRegistry.getAuthorisation(0, contract2.address); + assert.equal(filter2_, filter2.address); + }); + it("should not allow changing filter for a non-existing dapp", async () => { + await truffleAssert.reverts( + dappRegistry.requestFilterUpdate(0, contract2.address, filter.address, { from: infrastructure }), + "DR: unknown dapp" + ); + }); + it("should not allow confirming change of a non-existing pending change", async () => { + await truffleAssert.reverts( + dappRegistry.confirmFilterUpdate(0, contract2.address, { from: infrastructure }), + "DR: no pending filter update" + ); + }); + }); +}); diff --git a/test/balancer.js b/test/balancer.js new file mode 100644 index 000000000..9e9816a79 --- /dev/null +++ b/test/balancer.js @@ -0,0 +1,197 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const Filter = artifacts.require("BalancerFilter"); +const BPool = artifacts.require("BPool"); +const ERC20 = artifacts.require("TestERC20"); + +// Utils +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeCalls, encodeTransaction } = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; +const DECIMALS = 18; // number of decimal for TOKEN_A, TOKEN_B contracts + +const RelayManager = require("../utils/relay-manager"); + +contract("Balancer Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const relayer = accounts[4]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let filter; + let dappRegistry; + + let uniswapRouter; + + let tokenA; + let tokenB; + let pool; + + before(async () => { + // Deploy Balancer Pool + pool = await BPool.new(); + + // Deploy test tokens + tokenA = await ERC20.new([infrastructure], web3.utils.toWei("1000"), DECIMALS); + tokenB = await ERC20.new([infrastructure], web3.utils.toWei("1000"), DECIMALS); + + // Setup Balancer Pool + await tokenA.approve(pool.address, web3.utils.toWei("1000")); + await tokenB.approve(pool.address, web3.utils.toWei("1000")); + await pool.bind(tokenA.address, web3.utils.toWei("1000"), web3.utils.toWei("1")); + await pool.bind(tokenB.address, web3.utils.toWei("1000"), web3.utils.toWei("1")); + await pool.finalize(); + + // Deploy and fund UniswapV2 + const weth = await WETH.new(); + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + filter = await Filter.new(); + await dappRegistry.addDapp(0, pool.address, filter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("0.1")); + await tokenA.mint(wallet.address, web3.utils.toWei("1000")); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + const amount = web3.utils.toWei("1"); + + const multiCall = async (transactions) => { + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + return utils.parseRelayReceipt(txReceipt); + }; + + const deposit = async () => multiCall(encodeCalls([ + [tokenA, "approve", [pool.address, amount]], + [pool, "joinswapExternAmountIn", [tokenA.address, amount, 1]] + ])); + + const withdraw = async ({ fixedOutAmount }) => { + const bpt = await pool.balanceOf(wallet.address); + return multiCall(encodeCalls([ + [pool, "approve", [pool.address, bpt.toString()]], + (fixedOutAmount ? [ + pool, "exitswapExternAmountOut", [tokenA.address, web3.utils.toWei("0.1"), bpt.toString()] + ] : [ + pool, "exitswapPoolAmountIn", [tokenA.address, bpt.toString(), 1] + ]) + ])); + }; + + it("should allow deposits", async () => { + const { success, error } = await deposit(); + assert.isTrue(success, `joinswapExternAmountIn failed: "${error}"`); + }); + + it("should allow withdrawals (exitswapExternAmountOut)", async () => { + await deposit(); + const { success, error } = await withdraw({ fixedOutAmount: true }); + assert.isTrue(success, `exitswapExternAmountOut failed: "${error}"`); + }); + + it("should allow withdrawals (exitswapPoolAmountIn)", async () => { + await deposit(); + const { success, error } = await withdraw({ fixedOutAmount: false }); + assert.isTrue(success, `exitswapPoolAmountIn failed: "${error}"`); + }); + + it("should not allow direct transfers to pool", async () => { + const { success, error } = await multiCall(encodeCalls([ + [tokenA, "transfer", [pool.address, amount]], + ])); + assert.isFalse(success, "transfer should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow unsupported method", async () => { + const { success, error } = await multiCall(encodeCalls([ + [pool, "swapExactAmountIn", [tokenA.address, web3.utils.toWei("0.1"), tokenB.address, 1, web3.utils.toWei("10000")]] + ])); + assert.isFalse(success, "swap should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow sending ETH to pool", async () => { + const { success, error } = await multiCall([encodeTransaction(pool.address, web3.utils.toWei("0.01"), "0x")]); + assert.isFalse(success, "sending ETH should have failed"); + assert.equal(error, "TM: call not authorised"); + }); +}); diff --git a/test/baseFeature.js b/test/baseFeature.js deleted file mode 100644 index 4be788f42..000000000 --- a/test/baseFeature.js +++ /dev/null @@ -1,89 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); - -const Registry = artifacts.require("ModuleRegistry"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const VersionManager = artifacts.require("VersionManager"); -const RelayerManager = artifacts.require("RelayerManager"); -const LockStorage = artifacts.require("LockStorage"); -const ERC20 = artifacts.require("TestERC20"); -const NonCompliantERC20 = artifacts.require("NonCompliantERC20"); - -contract("BaseFeature", (accounts) => { - const owner = accounts[1]; - - let registry; - let versionManager; - let guardianStorage; - let lockStorage; - let token; - let relayerManager; - - before(async () => { - }); - - beforeEach(async () => { - registry = await Registry.new(); - guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await versionManager.addVersion([relayerManager.address], []); - - token = await ERC20.new([owner], 10, 18); - }); - - describe("Recover tokens", async () => { - it("should be able to recover ERC20 tokens sent to the feature", async () => { - let balance = await token.balanceOf(relayerManager.address); - assert.equal(balance, 0); - - await token.transfer(relayerManager.address, 10000000, { from: owner }); - balance = await token.balanceOf(relayerManager.address); - assert.equal(balance, 10000000); - - await relayerManager.recoverToken(token.address); - - balance = await token.balanceOf(relayerManager.address); - assert.equal(balance, 0); - - const versionManagerBalance = await token.balanceOf(versionManager.address); - assert.equal(versionManagerBalance, 10000000); - - await versionManager.recoverToken(token.address); - - const adminBalance = await token.balanceOf(accounts[0]); - assert.equal(adminBalance, 10000000); - }); - - it("should be able to recover non-ERC20 compliant tokens sent to the feature", async () => { - const nonCompliantToken = await NonCompliantERC20.new(); - await nonCompliantToken.mint(relayerManager.address, 10000000); - let balance = await nonCompliantToken.balanceOf(relayerManager.address); - assert.equal(balance, 10000000); - - await relayerManager.recoverToken(nonCompliantToken.address); - - balance = await nonCompliantToken.balanceOf(relayerManager.address); - assert.equal(balance, 0); - - const versionManagerBalance = await nonCompliantToken.balanceOf(versionManager.address); - assert.equal(versionManagerBalance, 10000000); - - await versionManager.recoverToken(nonCompliantToken.address); - - const adminBalance = await nonCompliantToken.balanceOf(accounts[0]); - assert.equal(adminBalance, 10000000); - }); - }); -}); diff --git a/test/baseModule.js b/test/baseModule.js new file mode 100644 index 000000000..c2ea840aa --- /dev/null +++ b/test/baseModule.js @@ -0,0 +1,152 @@ +/* global artifacts */ +const ethers = require("ethers"); +const truffleAssert = require("truffle-assertions"); + +const Registry = artifacts.require("ModuleRegistry"); +const ERC20 = artifacts.require("TestERC20"); +const NonCompliantERC20 = artifacts.require("NonCompliantERC20"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); + +const utils = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +contract("BaseModule", (accounts) => { + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let dappRegistry; + let token; + let wallet; + let factory; + + beforeEach(async () => { + registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + token = await ERC20.new([owner], 10, 18); + }); + + describe("Core functionality", async () => { + it("should not be able to init module if not called by wallet", async () => { + await truffleAssert.reverts(module.init(wallet.address), "BM: caller must be wallet"); + }); + + it("should not be able to unlock module which is not locked", async () => { + await truffleAssert.reverts(module.unlock(wallet.address, { from: guardian1 }), "BM: wallet must be locked"); + }); + + it("should not be able to interact with module which is locked", async () => { + await module.lock(wallet.address, { from: guardian1 }); + + await truffleAssert.reverts(module.addModule(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + await truffleAssert.reverts(module.lock(wallet.address, { from: guardian1 }), "BM: wallet locked"); + await truffleAssert.reverts(module.addGuardian(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + await truffleAssert.reverts(module.confirmGuardianAddition(wallet.address, ZERO_ADDRESS), "BM: wallet locked"); + await truffleAssert.reverts(module.cancelGuardianAddition(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + await truffleAssert.reverts(module.cancelGuardianRevokation(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + await truffleAssert.reverts(module.addToWhitelist(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + await truffleAssert.reverts(module.removeFromWhitelist(wallet.address, ZERO_ADDRESS, { from: owner }), "BM: wallet locked"); + }); + + it("should not be able to call functions which are only relayed", async () => { + await truffleAssert.reverts(module.executeRecovery(wallet.address, ZERO_ADDRESS), "BM: must be module"); + await truffleAssert.reverts(module.cancelRecovery(wallet.address), "BM: must be module"); + await truffleAssert.reverts(module.transferOwnership(wallet.address, ZERO_ADDRESS), "BM: must be module"); + await truffleAssert.reverts(module.multiCall(wallet.address, []), "BM: must be module"); + await truffleAssert.reverts(module.multiCallWithSession(wallet.address, []), "BM: must be module"); + await truffleAssert.reverts(module.multiCallWithGuardians(wallet.address, []), "BM: must be module"); + await truffleAssert.reverts(module.multiCallWithGuardiansAndStartSession(wallet.address, [], ZERO_ADDRESS, 0), "BM: must be module"); + }); + }); + + describe("Recover tokens", async () => { + it("should be able to recover ERC20 tokens sent to the module", async () => { + let balance = await token.balanceOf(module.address); + assert.equal(balance, 0); + + await token.transfer(module.address, 10000000, { from: owner }); + balance = await token.balanceOf(module.address); + assert.equal(balance, 10000000); + + await module.recoverToken(token.address); + + balance = await token.balanceOf(module.address); + assert.equal(balance, 0); + + const registryBalance = await token.balanceOf(registry.address); + assert.equal(registryBalance, 10000000); + + await registry.recoverToken(token.address); + + const adminBalance = await token.balanceOf(accounts[0]); + assert.equal(adminBalance, 10000000); + }); + + it.skip("should be able to recover non-ERC20 compliant tokens sent to the module", async () => { + const nonCompliantToken = await NonCompliantERC20.new(); + await nonCompliantToken.mint(module.address, 10000000); + let balance = await nonCompliantToken.balanceOf(module.address); + assert.equal(balance, 10000000); + + await module.recoverToken(nonCompliantToken.address); + + balance = await nonCompliantToken.balanceOf(module.address); + assert.equal(balance, 0); + + const registryBalance = await nonCompliantToken.balanceOf(registry.address); + assert.equal(registryBalance, 10000000); + + await registry.recoverToken(nonCompliantToken.address); + + const adminBalance = await nonCompliantToken.balanceOf(accounts[0]); + assert.equal(adminBalance, 10000000); + }); + }); +}); diff --git a/test/baseWallet.js b/test/baseWallet.js index a1c5fdfb6..88a4897e3 100644 --- a/test/baseWallet.js +++ b/test/baseWallet.js @@ -1,29 +1,40 @@ /* global artifacts */ const ethers = require("ethers"); + const truffleAssert = require("truffle-assertions"); const TruffleContract = require("@truffle/contract"); -const OldWalletV13Contract = require("../build-legacy/v1.3.0/BaseWallet"); -const OldWalletV16Contract = require("../build-legacy/v1.6.0/BaseWallet"); +const LegacyWalletV16Contract = require("../build-legacy/v1.6.0/BaseWallet"); +const LegacyWalletV13Contract = require("../build-legacy/v1.3.0/BaseWallet"); -const OldWalletV13 = TruffleContract(OldWalletV13Contract); -const OldWalletV16 = TruffleContract(OldWalletV16Contract); +const LegacyWalletV16 = TruffleContract(LegacyWalletV16Contract); +const LegacyWalletV13 = TruffleContract(LegacyWalletV13Contract); const Proxy = artifacts.require("Proxy"); const BaseWallet = artifacts.require("BaseWallet"); -const VersionManager = artifacts.require("VersionManager"); const Registry = artifacts.require("ModuleRegistry"); -const SimpleUpgrader = artifacts.require("SimpleUpgrader"); const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const TestFeature = artifacts.require("TestFeature"); +const TransferStorage = artifacts.require("TransferStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const ERC20 = artifacts.require("TestERC20"); + +const utils = require("../utils/utilities.js"); + +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; -const { getBalance } = require("../utils/utilities.js"); +const RelayManager = require("../utils/relay-manager"); contract("BaseWallet", (accounts) => { const owner = accounts[1]; - const nonowner = accounts[2]; + const recipient = accounts[7]; + + const ZERO_ADDRESS = ethers.constants.AddressZero; let wallet; let walletImplementation; @@ -31,39 +42,50 @@ contract("BaseWallet", (accounts) => { let module1; let module2; let module3; - let feature1; let guardianStorage; - let lockStorage; + let transferStorage; + let dappRegistry; + let uniswapRouter; + let token; + let manager; async function deployTestModule() { - const module = await VersionManager.new( + const _module = await ArgentModule.new( registry.address, - lockStorage.address, guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - const feature = await TestFeature.new( - lockStorage.address, - module.address, - 42); - await module.addVersion([feature.address], []); - return { module, feature }; + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + return _module; } before(async () => { - OldWalletV13.defaults({ from: accounts[0] }); - OldWalletV13.setProvider(web3.currentProvider); - OldWalletV16.defaults({ from: accounts[0] }); - OldWalletV16.setProvider(web3.currentProvider); + LegacyWalletV16.defaults({ from: accounts[0] }); + LegacyWalletV16.setProvider(web3.currentProvider); + LegacyWalletV13.defaults({ from: accounts[0] }); + LegacyWalletV13.setProvider(web3.currentProvider); registry = await Registry.new(); guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - const mod = await deployTestModule(); - [module1, feature1] = [mod.module, mod.feature]; - module2 = (await deployTestModule()).module; - module3 = (await deployTestModule()).module; + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + uniswapRouter = await UniswapV2Router01.new(); + + module1 = await deployTestModule(); + module2 = await deployTestModule(); + module3 = await deployTestModule(); walletImplementation = await BaseWallet.new(); + + await registry.registerModule(module1.address, ethers.utils.formatBytes32String("ArgentModule")); + + token = await ERC20.new([accounts[0]], web3.utils.toWei("1000"), 18); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); }); beforeEach(async () => { @@ -74,166 +96,137 @@ contract("BaseWallet", (accounts) => { describe("Registering modules", () => { it("should register a module with the correct info", async () => { const name = ethers.utils.formatBytes32String("module1"); - await registry.registerModule(module1.address, name); - const isRegistered = await registry.contract.methods["isRegisteredModule(address)"](module1.address).call(); - assert.isTrue(isRegistered, "module should be registered"); - const info = await registry.moduleInfo(module1.address); - assert.equal(name, info, "name should be correct"); + await registry.registerModule(module2.address, name); + const isRegistered = await registry.contract.methods["isRegisteredModule(address)"](module2.address).call(); + assert.isTrue(isRegistered); + const info = await registry.moduleInfo(module2.address); + assert.equal(name, info); }); it("should deregister a module", async () => { const name = ethers.utils.formatBytes32String("module2"); - await registry.registerModule(module2.address, name); - let isRegistered = await registry.contract.methods["isRegisteredModule(address)"](module2.address).call(); - assert.isTrue(isRegistered, "module should be registered"); - await registry.deregisterModule(module2.address); - isRegistered = await registry.contract.methods["isRegisteredModule(address)"](module2.address).call(); - assert.isFalse(isRegistered, "module should be deregistered"); + await registry.registerModule(module3.address, name); + await registry.deregisterModule(module3.address); + const isRegistered = await registry.contract.methods["isRegisteredModule(address)"](module3.address).call(); + assert.isFalse(isRegistered); }); it("should register an upgrader with the correct info", async () => { const name = ethers.utils.formatBytes32String("upgrader1"); await registry.registerUpgrader(module1.address, name); const isRegistered = await registry.isRegisteredUpgrader(module1.address); - assert.isTrue(isRegistered, "module should be registered"); + assert.isTrue(isRegistered); const info = await registry.upgraderInfo(module1.address); - assert.equal(name, info, "name should be correct"); + assert.equal(name, info); }); it("should deregister an upgrader", async () => { const name = ethers.utils.formatBytes32String("upgrader2"); await registry.registerUpgrader(module2.address, name); - let isRegistered = await registry.isRegisteredUpgrader(module2.address); - assert.isTrue(isRegistered, "upgrader should be registered"); + await registry.deregisterUpgrader(module2.address); - isRegistered = await registry.isRegisteredUpgrader(module2.address); + const isRegistered = await registry.isRegisteredUpgrader(module2.address); assert.isFalse(isRegistered, "upgrader should be deregistered"); }); + + it("should not let a non-module deauthorise a module", async () => { + await wallet.init(owner, [module1.address]); + await truffleAssert.reverts(wallet.authoriseModule(module1.address, false), "BW: sender not authorized"); + }); }); describe("Initialize Wallets", () => { - describe("wallet init", () => { - it("should create a wallet with the correct owner", async () => { - let walletOwner = await wallet.owner(); - assert.equal(walletOwner, "0x0000000000000000000000000000000000000000", "owner should be null before init"); - await wallet.init(owner, [module1.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); - walletOwner = await wallet.owner(); - assert.equal(walletOwner, owner, "owner should be the owner after init"); - }); - - it("should create a wallet with the correct modules", async () => { - await wallet.init(owner, [module1.address, module2.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); - await module2.upgradeWallet(wallet.address, await module2.lastVersion(), { from: owner }); - const module1IsAuthorised = await wallet.authorised(module1.address); - const module2IsAuthorised = await wallet.authorised(module2.address); - const module3IsAuthorised = await wallet.authorised(module3.address); - assert.equal(module1IsAuthorised, true, "module1 should be authorised"); - assert.equal(module2IsAuthorised, true, "module2 should be authorised"); - assert.equal(module3IsAuthorised, false, "module3 should not be authorised"); - }); - - it("should not reinitialize a wallet", async () => { - await wallet.init(owner, [module1.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); - await truffleAssert.reverts(wallet.init(owner, [module1.address]), "BW: wallet already initialised"); - }); - - it("should not initialize a wallet with no module", async () => { - await truffleAssert.reverts(wallet.init(owner, []), "BW: construction requires at least 1 module"); - }); - - it("should not initialize a wallet with duplicate modules", async () => { - await truffleAssert.reverts(wallet.init(owner, [module1.address, module1.address]), "BW: module is already added"); - }); + it("should create a wallet with the correct owner", async () => { + let walletOwner = await wallet.owner(); + assert.equal(walletOwner, "0x0000000000000000000000000000000000000000"); + await wallet.init(owner, [module1.address]); + walletOwner = await wallet.owner(); + assert.equal(walletOwner, owner); }); - describe("Receiving ETH", () => { - it("should accept ETH", async () => { - const before = await getBalance(wallet.address); - await wallet.send(50000000); - const after = await getBalance(wallet.address); - assert.equal(after.sub(before).toNumber(), 50000000, "should have received ETH"); - }); - - it("should accept ETH with data", async () => { - const before = await getBalance(wallet.address); - await web3.eth.sendTransaction({ from: accounts[0], to: wallet.address, data: "0x1234", value: 50000000 }); - const after = await getBalance(wallet.address); - assert.equal(after.sub(before).toNumber(), 50000000, "should have received ETH"); - }); + it("should create a wallet with the correct modules", async () => { + await wallet.init(owner, [module1.address, module2.address]); + const module1IsAuthorised = await wallet.authorised(module1.address); + const module2IsAuthorised = await wallet.authorised(module2.address); + const module3IsAuthorised = await wallet.authorised(module3.address); + assert.equal(module1IsAuthorised, true); + assert.equal(module2IsAuthorised, true); + assert.equal(module3IsAuthorised, false); }); - describe("Authorisations", () => { - it("should not let a non-module deauthorise a module", async () => { - await wallet.init(owner, [module1.address]); - await truffleAssert.reverts(wallet.authoriseModule(module1.address, false), "BW: msg.sender not an authorized module"); - }); - - it("should not let a feature set the owner to address(0)", async () => { - await wallet.init(owner, [module1.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); + it("should not reinitialize a wallet", async () => { + await wallet.init(owner, [module1.address]); + await truffleAssert.reverts(wallet.init(owner, [module1.address]), "BW: wallet already initialised"); + }); - await truffleAssert.reverts(feature1.invalidOwnerChange(wallet.address), "BW: address cannot be null"); - }); + it("should not initialize a wallet with no module", async () => { + await truffleAssert.reverts(wallet.init(owner, []), "BW: empty modules"); }); - describe("Static calls", () => { - it("should delegate static calls to the modules", async () => { - await wallet.init(owner, [module1.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); - const module1IsAuthorised = await wallet.authorised(module1.address); - assert.equal(module1IsAuthorised, true, "module1 should be authorised"); - const walletAsFeature = await TestFeature.at(wallet.address); - const boolVal = await walletAsFeature.getBoolean(); - const uintVal = await walletAsFeature.getUint(); - const addressVal = await walletAsFeature.getAddress(nonowner); - assert.equal(boolVal, true, "should have the correct bool"); - assert.equal(uintVal, 42, "should have the correct uint"); - assert.equal(addressVal, nonowner, "should have the address"); - }); - - it("should not delegate static calls to no longer authorised modules ", async () => { - await wallet.init(owner, [module1.address, module2.address]); - await module1.upgradeWallet(wallet.address, await module1.lastVersion(), { from: owner }); - let module1IsAuthorised = await wallet.authorised(module1.address); - assert.equal(module1IsAuthorised, true, "module1 should be authorised"); - - // removing module 1 - const upgrader = await SimpleUpgrader.new( - registry.address, lockStorage.address, [module1.address], []); - await registry.registerModule(upgrader.address, ethers.utils.formatBytes32String("Removing module1")); - await module1.addModule(wallet.address, upgrader.address, { from: owner }); - module1IsAuthorised = await wallet.authorised(module1.address); - assert.equal(module1IsAuthorised, false, "module1 should not be authorised"); - - // trying to execute static call delegated to module1 (it should fail) - const walletAsModule = await TestFeature.at(wallet.address); - await truffleAssert.reverts(walletAsModule.getBoolean(), "BW: must be an authorised module for static call"); - }); + it("should not initialize a wallet with duplicate modules", async () => { + await truffleAssert.reverts(wallet.init(owner, [module1.address, module1.address]), "BW: module is already added"); }); }); - describe("Old BaseWallet V1.3", () => { - it("should work with new modules", async () => { - const oldWallet = await OldWalletV13.new(); - await oldWallet.init(owner, [module1.address]); - await module1.upgradeWallet(oldWallet.address, await module1.lastVersion(), { from: owner }); - await feature1.callDapp(oldWallet.address); - await feature1.callDapp2(oldWallet.address, 2, false); - await truffleAssert.reverts(feature1.fail(oldWallet.address, "just because")); + describe("Receiving ETH", () => { + it("should accept ETH", async () => { + const before = await utils.getBalance(wallet.address); + await wallet.send(50000000); + const after = await utils.getBalance(wallet.address); + assert.equal(after.sub(before).toNumber(), 50000000, "should have received ETH"); + }); + + it("should accept ETH with data", async () => { + const before = await utils.getBalance(wallet.address); + await web3.eth.sendTransaction({ from: accounts[0], to: wallet.address, data: "0x1234", value: 50000000 }); + const after = await utils.getBalance(wallet.address); + assert.equal(after.sub(before).toNumber(), 50000000, "should have received ETH"); }); }); - describe("Old BaseWallet V1.6", () => { - it("should work with new modules", async () => { - const oldWallet = await OldWalletV16.new(); - await oldWallet.init(owner, [module1.address]); - await module1.upgradeWallet(oldWallet.address, await module1.lastVersion(), { from: owner }); - await feature1.callDapp(oldWallet.address); - await feature1.callDapp2(oldWallet.address, 2, true); - await truffleAssert.reverts(feature1.fail(oldWallet.address, "just because")); + describe("Should support legacy wallets", () => { + it("should work with v1.6", async () => { + const walletV16 = await LegacyWalletV16.new(); + await walletV16.init(owner, [module1.address]); + // Fund wallet with 100 tokens + await token.transfer(walletV16.address, 100); + // Make trusted the recipient account + await utils.addTrustedContact(walletV16, recipient, module1, SECURITY_PERIOD); + + // Test relaying an ERC20 transfer with refund in ETH using new `multiCall` + const data = token.contract.methods.transfer(recipient, 100).encodeABI(); + const transaction = utils.encodeTransaction(token.address, 0, data); + + const txReceipt = await manager.relay( + module1, + "multiCall", + [walletV16.address, [transaction]], + walletV16, + [owner]); + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `ERC20 transfer failed with "${error}"`); + }); + + it("should work with v1.3", async () => { + const walletV13 = await LegacyWalletV13.new(); + await walletV13.init(owner, [module1.address]); + // Fund wallet with 100 tokens + await token.transfer(walletV13.address, 100); + // Make trusted the recipient account + await utils.addTrustedContact(walletV13, recipient, module1, SECURITY_PERIOD); + + // Test relaying an ERC20 transfer with refund in ETH using new `multiCall` + const data = token.contract.methods.transfer(recipient, 100).encodeABI(); + const transaction = utils.encodeTransaction(token.address, 0, data); + + const txReceipt = await manager.relay( + module1, + "multiCall", + [walletV13.address, [transaction]], + walletV13, + [owner]); + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, `ERC20 transfer failed with "${error}"`); }); }); }); diff --git a/test/compound.js b/test/compound.js new file mode 100644 index 000000000..e097492fe --- /dev/null +++ b/test/compound.js @@ -0,0 +1,356 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); +const { formatBytes32String } = require("ethers").utils; + +const { expect, assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const CompoundCTokenFilter = artifacts.require("CompoundCTokenFilter"); + +// Compound +const Unitroller = artifacts.require("Unitroller"); +const PriceOracle = artifacts.require("SimplePriceOracle"); +const PriceOracleProxy = artifacts.require("PriceOracleProxy"); +const Comptroller = artifacts.require("Comptroller"); +const InterestModel = artifacts.require("WhitePaperInterestRateModel"); +const CEther = artifacts.require("CEther"); +const CErc20 = artifacts.require("CErc20"); + +const ERC20 = artifacts.require("TestERC20"); +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, initNonce, assertFailedWithError } = require("../utils/utilities.js"); + +const WAD = new BN("1000000000000000000"); // 10**18 +const ETH_EXCHANGE_RATE = new BN("200000000000000000000000000"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("Compound V2", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const liquidityProvider = accounts[3]; + const borrower = accounts[4]; + const relayer = accounts[5]; + const refundAddress = accounts[7]; + + let wallet; + let factory; + let registry; + let transferStorage; + let guardianStorage; + let module; + let dappRegistry; + let token; + let cToken; + let cEther; + let comptroller; + let oracleProxy; + + before(async () => { + /* Deploy Compound V2 Architecture */ + + // deploy price oracle + const oracle = await PriceOracle.new(); + + // deploy comptroller + const comptrollerProxy = await Unitroller.new(); + const comptrollerImpl = await Comptroller.new(); + await comptrollerProxy._setPendingImplementation(comptrollerImpl.address); + await comptrollerImpl._become(comptrollerProxy.address, oracle.address, WAD.divn(10), 5, false); + comptroller = await Comptroller.at(comptrollerProxy.address); + // deploy Interest rate model + const interestModel = await InterestModel.new(WAD.muln(250).divn(10000), WAD.muln(2000).divn(10000)); + // deploy CEther + cEther = await CEther.new( + comptrollerProxy.address, + interestModel.address, + ETH_EXCHANGE_RATE, + formatBytes32String("Compound Ether"), + formatBytes32String("cETH"), + 8, + ); + + // deploy token + token = await ERC20.new([infrastructure, liquidityProvider, borrower], 10000000, 18); + // deploy CToken + cToken = await CErc20.new( + token.address, + comptroller.address, + interestModel.address, + ETH_EXCHANGE_RATE, + "Compound Token", + "cTOKEN", + 18, + ); + // add price to Oracle + await oracle.setUnderlyingPrice(cToken.address, WAD.divn(10)); + // list cToken in Comptroller + await comptroller._supportMarket(cEther.address); + await comptroller._supportMarket(cToken.address); + // deploy Price Oracle proxy + oracleProxy = await PriceOracleProxy.new(comptroller.address, oracle.address, cEther.address); + await comptroller._setPriceOracle(oracleProxy.address); + // set collateral factor + await comptroller._setCollateralFactor(cToken.address, WAD.divn(10)); + await comptroller._setCollateralFactor(cEther.address, WAD.divn(10)); + + // add liquidity to tokens + const tenEther = await web3.utils.toWei("10"); + await cEther.mint({ value: tenEther, from: liquidityProvider }); + await token.approve(cToken.address, tenEther, { from: liquidityProvider }); + await cToken.mint(web3.utils.toWei("1"), { from: liquidityProvider }); + + /* Deploy Argent Architecture */ + + registry = await Registry.new(); + + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + + const cEtherFilter = await CompoundCTokenFilter.new(ZERO_ADDRESS); + const cErc20Filter = await CompoundCTokenFilter.new(token.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + await dappRegistry.addDapp(0, cEther.address, cEtherFilter.address); + await dappRegistry.addDapp(0, cToken.address, cErc20Filter.address); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + await wallet.send(web3.utils.toWei("1")); + await token.transfer(wallet.address, web3.utils.toWei("1")); + }); + + describe("Environment", () => { + it("should deploy the environment correctly", async () => { + const cOracle = await comptroller.oracle(); + assert.isTrue(cOracle === oracleProxy.address, "oracle should be registered"); + const cTokenPrice = await oracleProxy.getUnderlyingPrice(cToken.address); + expect(cTokenPrice).to.eq.BN(WAD.divn(10)); + const cEtherPrice = await oracleProxy.getUnderlyingPrice(cEther.address); + expect(cEtherPrice).to.eq.BN(WAD); + }); + }); + + describe("Mint and redeem", () => { + async function addInvestment(tokenAddress, amount, useFallback = false) { + const transactions = []; + let tokenBefore; + let tokenAfter; + let cTokenBefore; + let cTokenAfter; + + if (tokenAddress === ETH_TOKEN) { + tokenBefore = await utils.getBalance(wallet.address); + cTokenBefore = await cEther.balanceOf(wallet.address); + if (useFallback) { + transactions.push(encodeTransaction(cEther.address, amount, ZERO_BYTES)); + } else { + const data = cEther.contract.methods.mint().encodeABI(); + transactions.push(encodeTransaction(cEther.address, amount, data)); + } + } else { + tokenBefore = await token.balanceOf(wallet.address); + cTokenBefore = await cToken.balanceOf(wallet.address); + let data = token.contract.methods.approve(cToken.address, amount).encodeABI(); + let transaction = encodeTransaction(token.address, 0, data); + transactions.push(transaction); + + data = cToken.contract.methods.mint(amount).encodeABI(); + transaction = encodeTransaction(cToken.address, 0, data); + transactions.push(transaction); + } + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + if (!success) { + console.log(error); + } + assert.isTrue(success, "mint failed"); + + if (tokenAddress === ETH_TOKEN) { + await utils.hasEvent(txReceipt, cEther, "Mint"); + tokenAfter = await utils.getBalance(wallet.address); + cTokenAfter = await cEther.balanceOf(wallet.address); + } else { + await utils.hasEvent(txReceipt, cToken, "Mint"); + tokenAfter = await token.balanceOf(wallet.address); + cTokenAfter = await cToken.balanceOf(wallet.address); + } + + expect(tokenBefore.sub(tokenAfter)).to.gt.BN(0); + expect(cTokenAfter.sub(cTokenBefore)).to.gt.BN(0); + + return txReceipt; + } + + async function removeInvestment(tokenAddress, amount) { + const transactions = []; + let tokenBefore; + let tokenAfter; + let cTokenBefore; + let cTokenAfter; + + if (tokenAddress === ETH_TOKEN) { + tokenBefore = await utils.getBalance(wallet.address); + cTokenBefore = await cEther.balanceOf(wallet.address); + const data = cEther.contract.methods.redeem(amount).encodeABI(); + const transaction = await encodeTransaction(cEther.address, 0, data); + transactions.push(transaction); + } else { + tokenBefore = await token.balanceOf(wallet.address); + cTokenBefore = await cToken.balanceOf(wallet.address); + const data = cToken.contract.methods.redeem(amount).encodeABI(); + const transaction = await encodeTransaction(cToken.address, 0, data); + transactions.push(transaction); + } + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner]); + const { success, error } = await utils.parseRelayReceipt(txReceipt); + if (!success) { + console.log(error); + } + assert.isTrue(success, "redeem failed"); + + if (tokenAddress === ETH_TOKEN) { + await utils.hasEvent(txReceipt, cEther, "Redeem"); + tokenAfter = await utils.getBalance(wallet.address); + cTokenAfter = await cEther.balanceOf(wallet.address); + } else { + await utils.hasEvent(txReceipt, cToken, "Redeem"); + tokenAfter = await token.balanceOf(wallet.address); + cTokenAfter = await cToken.balanceOf(wallet.address); + } + + expect(tokenAfter.sub(tokenBefore)).to.gt.BN(0); + expect(cTokenBefore.sub(cTokenAfter)).to.gt.BN(0); + + return txReceipt; + } + + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + it("should mint cETH", async () => { + const txReceipt = await addInvestment(ETH_TOKEN, web3.utils.toWei("1", "finney")); + console.log("Gas to mint cETH: ", txReceipt.gasUsed); + }); + + it("should mint cETH with the fallback", async () => { + const txReceipt = await addInvestment(ETH_TOKEN, web3.utils.toWei("1", "finney"), true); + console.log("Gas to mint cETH: ", txReceipt.gasUsed); + }); + + it("should mint cErc20", async () => { + const txReceipt = await addInvestment(token.address, web3.utils.toWei("1", "finney")); + console.log("Gas to mint cErc20: ", txReceipt.gasUsed); + }); + + it("should redeem cETH", async () => { + const amount = web3.utils.toWei("1", "finney"); + await addInvestment(ETH_TOKEN, amount); + const cTokenBalance = await cEther.balanceOf(wallet.address); + const txReceipt = await removeInvestment(ETH_TOKEN, cTokenBalance.divn(2).toNumber()); + console.log("Gas to redeem cETH: ", txReceipt.gasUsed); + }); + + it("should redeem cErc20", async () => { + const amount = web3.utils.toWei("1", "finney"); + await addInvestment(token.address, amount); + const cTokenBalance = await cToken.balanceOf(wallet.address); + const txReceipt = await removeInvestment(token.address, cTokenBalance.divn(2).toNumber()); + console.log("Gas to redeem cErc20: ", txReceipt.gasUsed); + }); + + it("should fail to send ETH to a cErc20", async () => { + const transaction = await encodeTransaction(cToken.address, web3.utils.toWei("1", "finney"), ZERO_BYTES); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should fail to call an unauthorised method", async () => { + const data = await cToken.contract.methods.repayBorrowBehalf(borrower, 10000).encodeABI(); + const transaction = await encodeTransaction(cToken.address, 0, data); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should fail to approve a token for cEther", async () => { + const amount = web3.utils.toWei("1", "finney"); + const data = token.contract.methods.approve(cEther.address, amount).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data, true); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should fail to approve a token not the underlying of a cToken", async () => { + const amount = web3.utils.toWei("1", "finney"); + const notUnderlying = await ERC20.new([infrastructure, wallet.address], 10000000, 18); + const data = notUnderlying.contract.methods.approve(cToken.address, amount).encodeABI(); + const transaction = encodeTransaction(notUnderlying.address, 0, data, true); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + }); +}); diff --git a/test/compoundManager_invest.js b/test/compoundManager_invest.js deleted file mode 100644 index f1a7488dd..000000000 --- a/test/compoundManager_invest.js +++ /dev/null @@ -1,341 +0,0 @@ -/* global artifacts */ -const truffleAssert = require("truffle-assertions"); -const { formatBytes32String } = require("ethers").utils; -const ethers = require("ethers"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); -const utils = require("../utils/utilities.js"); - -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const Registry = artifacts.require("ModuleRegistry"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const RelayerManager = artifacts.require("RelayerManager"); -const CompoundManager = artifacts.require("CompoundManager"); -const VersionManager = artifacts.require("VersionManager"); - -// Compound -const Unitroller = artifacts.require("Unitroller"); -const PriceOracle = artifacts.require("SimplePriceOracle"); -const PriceOracleProxy = artifacts.require("PriceOracleProxy"); -const Comptroller = artifacts.require("Comptroller"); -const InterestModel = artifacts.require("WhitePaperInterestRateModel"); -const CEther = artifacts.require("CEther"); -const CErc20 = artifacts.require("CErc20"); -const CToken = artifacts.require("CToken"); -const CompoundRegistry = artifacts.require("CompoundRegistry"); - -const WAD = new BN("1000000000000000000"); // 10**18 -const ETH_EXCHANGE_RATE = new BN("200000000000000000000000000"); - -const ERC20 = artifacts.require("TestERC20"); - -const { ETH_TOKEN } = require("../utils/utilities.js"); -const RelayManager = require("../utils/relay-manager"); - -contract("Invest Manager with Compound", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const liquidityProvider = accounts[2]; - const borrower = accounts[3]; - - let wallet; - let walletImplementation; - let investManager; - let relayerManager; - let compoundRegistry; - let token; - let cToken; - let cEther; - let comptroller; - let oracleProxy; - let versionManager; - - before(async () => { - /* Deploy Compound V2 Architecture */ - - // deploy price oracle - const oracle = await PriceOracle.new(); - - // deploy comptroller - const comptrollerProxy = await Unitroller.new(); - const comptrollerImpl = await Comptroller.new(); - await comptrollerProxy._setPendingImplementation(comptrollerImpl.address); - await comptrollerImpl._become(comptrollerProxy.address, oracle.address, WAD.divn(10), 5, false); - comptroller = await Comptroller.at(comptrollerProxy.address); - // deploy Interest rate model - const interestModel = await InterestModel.new(WAD.muln(250).divn(10000), WAD.muln(2000).divn(10000)); - // deploy CEther - cEther = await CEther.new( - comptrollerProxy.address, - interestModel.address, - ETH_EXCHANGE_RATE, - formatBytes32String("Compound Ether"), - formatBytes32String("cETH"), - 8, - ); - - // deploy token - token = await ERC20.new([infrastructure, liquidityProvider, borrower], 10000000, 18); - // deploy CToken - cToken = await CErc20.new( - token.address, - comptroller.address, - interestModel.address, - ETH_EXCHANGE_RATE, - "Compound Token", - "cTOKEN", - 18, - ); - // add price to Oracle - await oracle.setUnderlyingPrice(cToken.address, WAD.divn(10)); - // list cToken in Comptroller - await comptroller._supportMarket(cEther.address); - await comptroller._supportMarket(cToken.address); - // deploy Price Oracle proxy - oracleProxy = await PriceOracleProxy.new(comptroller.address, oracle.address, cEther.address); - await comptroller._setPriceOracle(oracleProxy.address); - // set collateral factor - await comptroller._setCollateralFactor(cToken.address, WAD.divn(10)); - await comptroller._setCollateralFactor(cEther.address, WAD.divn(10)); - - // add liquidity to tokens - const tenEther = await web3.utils.toWei("10"); - await cEther.mint({ value: tenEther, from: liquidityProvider }); - await token.approve(cToken.address, tenEther, { from: liquidityProvider }); - await cToken.mint(web3.utils.toWei("1"), { from: liquidityProvider }); - - /* Deploy Argent Architecture */ - - compoundRegistry = await CompoundRegistry.new(); - await compoundRegistry.addCToken(ETH_TOKEN, cEther.address); - await compoundRegistry.addCToken(token.address, cToken.address); - const registry = await Registry.new(); - const guardianStorage = await GuardianStorage.new(); - const lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - investManager = await CompoundManager.new( - lockStorage.address, - comptroller.address, - compoundRegistry.address, - versionManager.address, - ); - - walletImplementation = await BaseWallet.new(); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - await versionManager.addVersion([ - investManager.address, - relayerManager.address, - ], []); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - }); - - describe("Environment", () => { - it("should deploy the environment correctly", async () => { - const getCToken = await compoundRegistry.getCToken(token.address); - assert.isTrue(getCToken === cToken.address, "cToken should be registered"); - const getCEther = await compoundRegistry.getCToken(ETH_TOKEN); - assert.isTrue(getCEther === cEther.address, "cEther should be registered"); - const cOracle = await comptroller.oracle(); - assert.isTrue(cOracle === oracleProxy.address, "oracle should be registered"); - const cTokenPrice = await oracleProxy.getUnderlyingPrice(cToken.address); - expect(cTokenPrice).to.eq.BN(WAD.divn(10)); - const cEtherPrice = await oracleProxy.getUnderlyingPrice(cEther.address); - expect(cEtherPrice).to.eq.BN(WAD); - }); - }); - - describe("Investment", () => { - async function accrueInterests(days, investInEth) { - let tx; - // generate borrows to create interests - await comptroller.enterMarkets([cEther.address, cToken.address], { from: borrower }); - - if (investInEth) { - await token.approve(cToken.address, web3.utils.toWei("20"), { from: borrower }); - await cToken.mint(web3.utils.toWei("20"), { from: borrower }); - tx = await cEther.borrow(web3.utils.toWei("0.1"), { from: borrower }); - await utils.hasEvent(tx.receipt, CToken, "Borrow"); - } else { - await cEther.mint({ value: web3.utils.toWei("2"), from: borrower }); - tx = await cToken.borrow(web3.utils.toWei("0.1"), { from: borrower }); - await utils.hasEvent(tx.receipt, CToken, "Borrow"); - } - // increase time to accumulate interests - await utils.increaseTime(3600 * 24 * days); - await cToken.accrueInterest(); - await cEther.accrueInterest(); - } - - async function addInvestment(tokenAddress, amount, days, relay = false) { - let tx; - let txReceipt; - const investInEth = (tokenAddress === ETH_TOKEN); - - if (investInEth) { - tx = await wallet.send(amount); - } else { - await token.transfer(wallet.address, amount); - } - const params = [wallet.address, tokenAddress, amount, 0]; - if (relay) { - txReceipt = await manager.relay(investManager, "addInvestment", params, wallet, [owner]); - } else { - tx = await investManager.addInvestment(...params, { from: owner }); - txReceipt = tx.receipt; - } - - await utils.hasEvent(txReceipt, investManager, "InvestmentAdded"); - - await accrueInterests(days, investInEth); - - const output = await investManager.getInvestment(wallet.address, tokenAddress); - assert.isTrue(output._tokenValue > amount, "investment should have gained value"); - - return output._tokenValue; - } - - async function removeInvestment(tokenAddress, fraction, relay = false) { - let tx; let - txReceipt; - const investInEth = (tokenAddress === ETH_TOKEN); - - await addInvestment(tokenAddress, web3.utils.toWei("0.1"), 365, false); - const before = investInEth ? await cEther.balanceOf(wallet.address) : await cToken.balanceOf(wallet.address); - - const params = [wallet.address, tokenAddress, fraction]; - if (relay) { - txReceipt = await manager.relay(investManager, "removeInvestment", params, wallet, [owner]); - } else { - tx = await investManager.removeInvestment(...params, { from: owner }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, investManager, "InvestmentRemoved"); - - // TODO: Manual division result rounding up until https://github.com/indutny/bn.js/issues/79 is added to BN.js - const result = before.muln(10000 - fraction); - const divisionRemainder = new BN(result.modn(10000)); - - let divisionResult = result.divn(10000); - if (!divisionRemainder.isZero()) { - divisionResult = divisionResult.iaddn(1); - } - - const after = investInEth ? await cEther.balanceOf(wallet.address) : await cToken.balanceOf(wallet.address); - expect(after).to.eq.BN(divisionResult); - } - - describe("Add Investment", () => { - // Successes - - it("should invest in ERC20 for 1 year and gain interests (blockchain tx)", async () => { - await addInvestment(token.address, web3.utils.toWei("1"), 365, false); - }); - - it("should invest in ERC20 for 1 year and gain interests (relay tx)", async () => { - await addInvestment(token.address, web3.utils.toWei("1"), 365, true); - }); - - it("should invest in ETH for 1 year and gain interests (blockchain tx)", async () => { - await addInvestment(ETH_TOKEN, web3.utils.toWei("1"), 365, false); - }); - - it("should invest in ETH for 1 year and gain interests (relay tx)", async () => { - await addInvestment(ETH_TOKEN, web3.utils.toWei("1"), 365, true); - }); - - // Reverts - - it("should fail to invest in ERC20 with an unknown token", async () => { - const params = [wallet.address, ethers.constants.AddressZero, web3.utils.toWei("1"), 0]; - await truffleAssert.reverts(investManager.addInvestment(...params, { from: owner }), "CM: No market for target token"); - }); - - it("should fail to invest in ERC20 with an amount of zero", async () => { - const params = [wallet.address, token.address, 0, 0]; - await truffleAssert.reverts(investManager.addInvestment(...params, { from: owner }), "CM: amount cannot be 0"); - }); - - it("should fail to invest in ERC20 when not holding any ERC20", async () => { - const params = [wallet.address, token.address, web3.utils.toWei("1"), 0]; - await truffleAssert.reverts(investManager.addInvestment(...params, { from: owner }), "CM: mint failed"); - }); - }); - - describe("Remove Investment", () => { - // Successes - - function testRemoveERC20Investment(fraction, relay) { - it(`should remove ${fraction / 100}% of an ERC20 investment (${relay ? "relay" : "blockchain"} tx)`, async () => { - await removeInvestment(token.address, fraction, relay); - }); - } - function testRemoveETHInvestment(fraction, relay) { - it(`should remove ${fraction / 100}% of an ETH investment (${relay ? "relay" : "blockchain"} tx)`, async () => { - await removeInvestment(token.address, fraction, relay); - }); - } - - for (let i = 1; i < 6; i += 1) { - testRemoveERC20Investment(i * 2000, true); - testRemoveERC20Investment(i * 2000, false); - testRemoveETHInvestment(i * 2000, true); - testRemoveETHInvestment(i * 2000, false); - } - - // Reverts - - it("should fail to remove an ERC20 investment when passing an invalid fraction value", async () => { - const params = [wallet.address, token.address, 50000]; - await truffleAssert.reverts(investManager.removeInvestment(...params, { from: owner }), "CM: invalid fraction value"); - }); - - it("should fail to remove an ERC20 investment when not holding any of the corresponding cToken", async () => { - const params = [wallet.address, token.address, 5000]; - await truffleAssert.reverts(investManager.removeInvestment(...params, { from: owner }), "CM: amount cannot be 0"); - }); - - it("should fail to remove all of an ERC20 investment when it collateralizes a loan", async () => { - const collateralAmount = await web3.utils.toWei("1"); - const debtAmount = await web3.utils.toWei("0.001"); - await token.transfer(wallet.address, collateralAmount); - const openLoanParams = [ - wallet.address, - token.address, - collateralAmount, - ETH_TOKEN, - debtAmount]; - await investManager.openLoan(...openLoanParams, { from: owner }); - const removeInvestmentParams = [wallet.address, token.address, 10000]; - await truffleAssert.reverts(investManager.removeInvestment(...removeInvestmentParams, { from: owner }), "CM: redeem failed"); - }); - }); - }); -}); diff --git a/test/compoundManager_loan.js b/test/compoundManager_loan.js deleted file mode 100644 index 2bc05b37e..000000000 --- a/test/compoundManager_loan.js +++ /dev/null @@ -1,696 +0,0 @@ -/* global artifacts */ - -const truffleAssert = require("truffle-assertions"); -const ethers = require("ethers"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const Registry = artifacts.require("ModuleRegistry"); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const RelayerManager = artifacts.require("RelayerManager"); -const CompoundManager = artifacts.require("CompoundManager"); -const VersionManager = artifacts.require("VersionManager"); - -// Compound -const Unitroller = artifacts.require("Unitroller"); -const PriceOracle = artifacts.require("SimplePriceOracle"); -const PriceOracleProxy = artifacts.require("PriceOracleProxy"); -const Comptroller = artifacts.require("Comptroller"); -const InterestModel = artifacts.require("WhitePaperInterestRateModel"); -const CEther = artifacts.require("CEther"); -const CErc20 = artifacts.require("CErc20"); -const CompoundRegistry = artifacts.require("CompoundRegistry"); - -const ERC20 = artifacts.require("TestERC20"); - -const WAD = new BN("1000000000000000000"); // 10**18 -const ETH_EXCHANGE_RATE = new BN("200000000000000000000000000"); - -const RelayManager = require("../utils/relay-manager"); -const { ETH_TOKEN } = require("../utils/utilities.js"); -const utils = require("../utils/utilities.js"); - -const ZERO_BYTES32 = ethers.constants.HashZero; - -contract("Loan Module", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const liquidityProvider = accounts[2]; - const borrower = accounts[3]; - - let wallet; - let walletImplementation; - let loanManager; - let compoundRegistry; - let token1; - let token2; - let cToken1; - let cToken2; - let cEther; - let comptroller; - let oracle; - let oracleProxy; - let relayerManager; - let versionManager; - - before(async () => { - /* Deploy Compound V2 Architecture */ - - // deploy price oracle - oracle = await PriceOracle.new(); - // deploy comptroller - const comptrollerProxy = await Unitroller.new(); - const comptrollerImpl = await Comptroller.new(); - await comptrollerProxy._setPendingImplementation(comptrollerImpl.address); - await comptrollerImpl._become(comptrollerProxy.address, oracle.address, WAD.divn(10), 5, false); - comptroller = await Comptroller.at(comptrollerProxy.address); - // deploy Interest rate model - const interestModel = await InterestModel.new(WAD.muln(250).divn(10000), WAD.muln(2000).divn(10000)); - // deploy CEther - cEther = await CEther.new( - comptroller.address, - interestModel.address, - ETH_EXCHANGE_RATE, - "Compound Ether", - "cETH", - 8, - ); - - // deploy token - token1 = await ERC20.new([infrastructure, liquidityProvider, borrower], 10000000, 18); - token2 = await ERC20.new([infrastructure, liquidityProvider, borrower], 10000000, 18); - // deploy CToken - cToken1 = await CErc20.new( - token1.address, - comptroller.address, - interestModel.address, - ETH_EXCHANGE_RATE, - "Compound Token 1", - "cTOKEN1", - 18, - ); - cToken2 = await CErc20.new( - token2.address, - comptroller.address, - interestModel.address, - ETH_EXCHANGE_RATE, - "Compound Token 2", - "cTOKEN2", - 18, - ); - - // add price to Oracle - await oracle.setUnderlyingPrice(cToken1.address, WAD.divn(10)); - await oracle.setUnderlyingPrice(cToken2.address, WAD.divn(10)); - // list cToken in Comptroller - await comptroller._supportMarket(cEther.address); - await comptroller._supportMarket(cToken1.address); - await comptroller._supportMarket(cToken2.address); - // deploy Price Oracle proxy - oracleProxy = await PriceOracleProxy.new(comptroller.address, oracle.address, cEther.address); - await comptroller._setPriceOracle(oracleProxy.address); - // set collateral factor - await comptroller._setCollateralFactor(cToken1.address, WAD.divn(10)); - await comptroller._setCollateralFactor(cToken2.address, WAD.divn(10)); - await comptroller._setCollateralFactor(cEther.address, WAD.divn(10)); - - // add liquidity to tokens - await cEther.mint({ value: web3.utils.toWei("100"), from: liquidityProvider }); - await token1.approve(cToken1.address, web3.utils.toWei("10"), { from: liquidityProvider }); - await cToken1.mint(web3.utils.toWei("10"), { from: liquidityProvider }); - await token2.approve(cToken2.address, web3.utils.toWei("10"), { from: liquidityProvider }); - await cToken2.mint(web3.utils.toWei("10"), { from: liquidityProvider }); - - /* Deploy Argent Architecture */ - - compoundRegistry = await CompoundRegistry.new(); - await compoundRegistry.addCToken(ETH_TOKEN, cEther.address); - await compoundRegistry.addCToken(token1.address, cToken1.address); - await compoundRegistry.addCToken(token2.address, cToken2.address); - const registry = await Registry.new(); - const guardianStorage = await GuardianStorage.new(); - const lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - loanManager = await CompoundManager.new( - lockStorage.address, - comptroller.address, - compoundRegistry.address, - versionManager.address, - ); - - walletImplementation = await BaseWallet.new(); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - await versionManager.addVersion([ - loanManager.address, - relayerManager.address, - ], []); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - }); - - async function fundWallet({ ethAmount, token1Amount, token2Amount = 0 }) { - if (ethAmount > 0) await wallet.send(ethAmount); - if (token1Amount > 0) await token1.transfer(wallet.address, token1Amount); - if (token2Amount > 0) await token2.transfer(wallet.address, token2Amount); - } - - describe("Loan", () => { - async function testOpenLoan({ - collateral, collateralAmount, debt, debtAmount, relayed, - }) { - const collateralBefore = (collateral === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await collateral.balanceOf(wallet.address); - const debtBefore = (debt === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await debt.balanceOf(wallet.address); - - const params = [ - wallet.address, - (collateral === ETH_TOKEN) ? ETH_TOKEN : collateral.address, - collateralAmount, - (debt === ETH_TOKEN) ? ETH_TOKEN : debt.address, - debtAmount]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(loanManager, "openLoan", params, wallet, [owner]); - } else { - const tx = await loanManager.openLoan(...params, { from: owner }); - txReceipt = tx.receipt; - } - - const event = await utils.getEvent(txReceipt, loanManager, "LoanOpened"); - const loanId = event.args._loanId; - assert.isDefined(loanId, "Loan ID should be defined"); - - const collateralAfter = (collateral === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await collateral.balanceOf(wallet.address); - const debtAfter = (debt === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await debt.balanceOf(wallet.address); - - expect(collateralBefore.sub(collateralAfter)).to.eq.BN(new BN(collateralAmount)); - expect(debtAfter.sub(debtBefore)).to.eq.BN(new BN(debtAmount)); - - return loanId; - } - - async function testChangeCollateral({ - loanId, collateral, amount, add, relayed, - }) { - const collateralBalanceBefore = (collateral === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await collateral.balanceOf(wallet.address); - - const method = add ? "addCollateral" : "removeCollateral"; - const params = [ - wallet.address, - loanId, - (collateral === ETH_TOKEN) ? ETH_TOKEN : collateral.address, - amount]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(loanManager, method, params, wallet, [owner]); - } else { - const tx = await loanManager[method](...params, { from: owner }); - txReceipt = tx.receipt; - } - const collateralBalanceAfter = (collateral === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await collateral.balanceOf(wallet.address); - if (add) { - await utils.hasEvent(txReceipt, loanManager, "CollateralAdded"); - // Wallet collateral should have decreased by `amount` - expect(collateralBalanceBefore.sub(new BN(amount))).to.eq.BN(collateralBalanceAfter); - } else { - await utils.hasEvent(txReceipt, loanManager, "CollateralRemoved"); - // Wallet collateral should have increased by `amount` - expect(collateralBalanceBefore.add(new BN(amount))).to.eq.BN(collateralBalanceAfter); - } - } - - async function testChangeDebt({ - loanId, debtToken, amount, add, relayed, - }) { - const debtBalanceBefore = (debtToken === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await debtToken.balanceOf(wallet.address); - - const method = add ? "addDebt" : "removeDebt"; - const params = [ - wallet.address, - loanId, - (debtToken === ETH_TOKEN) ? ETH_TOKEN : debtToken.address, - amount]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(loanManager, method, params, wallet, [owner]); - } else { - const tx = await loanManager[method](...params, { from: owner }); - txReceipt = tx.receipt; - } - const debtBalanceAfter = (debtToken === ETH_TOKEN) ? await utils.getBalance(wallet.address) - : await debtToken.balanceOf(wallet.address); - if (add) { - await utils.hasEvent(txReceipt, loanManager, "DebtAdded"); - // Wallet debt should have increase by `amount` - expect(debtBalanceAfter).to.eq.BN(debtBalanceBefore.add(new BN(amount))); - } else { - await utils.hasEvent(txReceipt, loanManager, "DebtRemoved"); - assert.isTrue( - debtBalanceAfter.eq(debtBalanceBefore.sub(new BN(amount))) || new BN(amount).eq(new BN(ethers.constants.MaxUint256.toString())), - `wallet debt should have decreased by ${amount} (relayed: ${relayed})`, - ); - } - } - - describe("Open Loan", () => { - it("should borrow token with ETH as collateral (blockchain tx)", async () => { - const collateralAmount = await web3.utils.toWei("0.1"); - const debtAmount = await web3.utils.toWei("0.05"); - await fundWallet({ ethAmount: collateralAmount, token1Amount: 0 }); - await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount, debt: token1, debtAmount, relayed: false, - }); - }); - - it("should borrow ETH with token as collateral (blockchain tx)", async () => { - const collateralAmount = await web3.utils.toWei("0.5"); - const debtAmount = await web3.utils.toWei("0.001"); - await fundWallet({ ethAmount: 0, token1Amount: collateralAmount }); - await testOpenLoan({ - collateral: token1, collateralAmount, debt: ETH_TOKEN, debtAmount, relayed: false, - }); - }); - - it("should borrow token with ETH as collateral (relay tx)", async () => { - const collateralAmount = await web3.utils.toWei("0.1"); - const debtAmount = await web3.utils.toWei("0.05"); - await fundWallet({ ethAmount: collateralAmount, token1Amount: 0 }); - await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount, debt: token1, debtAmount, relayed: true, - }); - }); - - it("should borrow ETH with token as collateral (relay tx)", async () => { - const collateralAmount = await web3.utils.toWei("0.5"); - const debtAmount = await web3.utils.toWei("0.001"); - await fundWallet({ ethAmount: 0, token1Amount: collateralAmount }); - await testOpenLoan({ - collateral: token1, collateralAmount, debt: ETH_TOKEN, debtAmount, relayed: true, - }); - }); - - it("should get the info of a loan", async () => { - const collateralAmount = await web3.utils.toWei("0.1"); - const debtAmount = await web3.utils.toWei("0.01"); - await fundWallet({ ethAmount: collateralAmount, token1Amount: 0 }); - await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount, debt: token1, debtAmount, relayed: false, - }); - let loan = await loanManager.getLoan(wallet.address, ZERO_BYTES32); - // Should obtain the liquidity info of the loan - expect(loan._status).to.eq.BN(new BN(1)); - expect(loan._ethValue).to.be.gt.BN(0); - - await oracle.setUnderlyingPrice(cToken1.address, WAD.muln(10)); - loan = await loanManager.getLoan(wallet.address, ZERO_BYTES32); - // Should obtain the shortfall info of the loan - expect(loan._status).to.eq.BN(new BN(2)); - expect(loan._ethValue).to.be.gt.BN(0); - - await oracle.setUnderlyingPrice(cToken1.address, 0); - await truffleAssert.reverts(loanManager.getLoan(wallet.address, ZERO_BYTES32), "CM: failed to get account liquidity"); - - await oracle.setUnderlyingPrice(cToken1.address, WAD.divn(10)); - loan = await loanManager.getLoan(ethers.constants.AddressZero, ZERO_BYTES32); - // Should obtain (0,0) for non-existing loan info - expect(loan._status).to.be.zero; - expect(loan._ethValue).to.be.zero; - }); - }); - - describe("Add/Remove Collateral", () => { - // Successes - - it("should add ETH collateral to a loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: await web3.utils.toWei("0.2"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.1"), debt: token1, debtAmount: web3.utils.toWei("0.05"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: ETH_TOKEN, amount: web3.utils.toWei("0.1"), add: true, relayed: false, - }); - }); - - it("should add ETH collateral to a loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.2"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.1"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: ETH_TOKEN, amount: web3.utils.toWei("0.1"), add: true, relayed: true, - }); - }); - - it("should remove ETH collateral from a loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.2"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.2"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: ETH_TOKEN, amount: web3.utils.toWei("0.001"), add: false, relayed: false, - }); - }); - - it("should remove ETH collateral from a loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.2"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.1"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: ETH_TOKEN, amount: web3.utils.toWei("0.001"), add: false, relayed: true, - }); - }); - - it("should add token collateral to a loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.6") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: token1, amount: web3.utils.toWei("0.1"), add: true, relayed: false, - }); - }); - - it("should add token collateral to a loan (relayed tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.6") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: token1, amount: web3.utils.toWei("0.1"), add: true, relayed: true, - }); - }); - - it("should remove token collateral from a loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: token1, amount: web3.utils.toWei("0.1"), add: false, relayed: false, - }); - }); - - it("should remove token collateral from a loan (relayed tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeCollateral({ - loanId, collateral: token1, amount: web3.utils.toWei("0.1"), add: false, relayed: true, - }); - }); - - // Reverts - - it("should fail to borrow an unknown token", async () => { - const params = [wallet.address, ZERO_BYTES32, ethers.constants.AddressZero, web3.utils.toWei("1")]; - await truffleAssert.reverts(loanManager.addDebt(...params, { from: owner }), "CM: No market for target token"); - }); - - it("should fail to borrow 0 token", async () => { - const params = [wallet.address, ZERO_BYTES32, ETH_TOKEN, 0]; - await truffleAssert.reverts(loanManager.addDebt(...params, { from: owner }), "CM: amount cannot be 0"); - }); - - it("should fail to borrow token with no collateral", async () => { - const params = [wallet.address, ZERO_BYTES32, ETH_TOKEN, web3.utils.toWei("1")]; - await truffleAssert.reverts(loanManager.addDebt(...params, { from: owner }), "CM: borrow failed"); - }); - - it("should fail to repay an unknown token", async () => { - const params = [wallet.address, ZERO_BYTES32, ethers.constants.AddressZero, web3.utils.toWei("1")]; - await truffleAssert.reverts(loanManager.removeDebt(...params, { from: owner }), "CM: No market for target token"); - }); - - it("should fail to repay 0 token", async () => { - const params = [wallet.address, ZERO_BYTES32, ETH_TOKEN, 0]; - await truffleAssert.reverts(loanManager.removeDebt(...params, { from: owner }), "CM: amount cannot be 0"); - }); - - it("should fail to repay too much debt token", async () => { - const collateralAmount = await web3.utils.toWei("1"); - const debtAmount = await web3.utils.toWei("0.001"); - await fundWallet({ ethAmount: collateralAmount, token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount, debt: token1, debtAmount, relayed: false, - }); - const removeDebtParams = [wallet.address, loanId, token1.address, web3.utils.toWei("0.002")]; - await truffleAssert.reverts(loanManager.removeDebt(...removeDebtParams, { from: owner }), "CM: repayBorrow failed"); - }); - - it("should fail to remove an unknown collateral token", async () => { - const params = [wallet.address, ZERO_BYTES32, ethers.constants.AddressZero, web3.utils.toWei("1")]; - await truffleAssert.reverts(loanManager.removeCollateral(...params, { from: owner }), "CM: No market for target token"); - }); - - it("should fail to remove 0 collateral token", async () => { - const params = [wallet.address, ZERO_BYTES32, ETH_TOKEN, web3.utils.toWei("0")]; - await truffleAssert.reverts(loanManager.removeCollateral(...params, { from: owner }), "CM: amount cannot be 0"); - }); - - it("should fail to remove too much collateral token", async () => { - const collateralAmount = await web3.utils.toWei("1"); - const debtAmount = await web3.utils.toWei("0.001"); - await fundWallet({ ethAmount: collateralAmount, token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount, debt: token1, debtAmount, relayed: false, - }); - const removeDebtParams = [wallet.address, loanId, token1.address, web3.utils.toWei("0.002")]; - await truffleAssert.reverts(loanManager.removeCollateral(...removeDebtParams, { from: owner }), "CM: redeemUnderlying failed"); - }); - }); - - describe("Increase/Decrease Debt", () => { - it("should increase ETH debt to a token1/ETH loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: ETH_TOKEN, amount: web3.utils.toWei("0.001"), add: true, relayed: false, - }); - }); - - it("should increase ETH debt to a token1/ETH loan (relayed tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: ETH_TOKEN, amount: web3.utils.toWei("0.001"), add: true, relayed: true, - }); - }); - - it("should increase token1 debt to a ETH/token1 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token1, amount: web3.utils.toWei("0.01"), add: true, relayed: false, - }); - }); - - it("should increase token1 debt to a ETH/token1 loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token1, amount: web3.utils.toWei("0.01"), add: true, relayed: true, - }); - }); - - it("should increase token2 debt to a ETH/token1 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token2, amount: web3.utils.toWei("0.01"), add: true, relayed: false, - }); - }); - - it("should increase token2 debt to a ETH/token1 loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token2, amount: web3.utils.toWei("0.01"), add: true, relayed: true, - }); - }); - - it("should repay ETH debt to a token1/ETH loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: ETH_TOKEN, amount: web3.utils.toWei("0.0005"), add: false, relayed: false, - }); - }); - - it("should repay ETH debt to a token1/ETH loan (relay tx)", async () => { - await fundWallet({ ethAmount: 0, token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: ETH_TOKEN, amount: web3.utils.toWei("0.0005"), add: false, relayed: true, - }); - }); - - it("should repay token1 debt to a ETH/token1 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token1, amount: web3.utils.toWei("0.005"), add: false, relayed: false, - }); - }); - - it("should repay token1 debt to a ETH/token1 loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: 0 }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token1, amount: web3.utils.toWei("0.005"), add: false, relayed: true, - }); - }); - - it("should repay the full token1 debt to a ETH/token1 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: web3.utils.toWei("0.01") }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.5"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token1, amount: ethers.constants.MaxUint256.toString(), add: false, relayed: false, - }); - }); - }); - - describe("Close Loan", () => { - async function testCloseLoan({ loanId, relayed, debtMarkets = 1 }) { - const marketsBefore = await comptroller.getAssetsIn(wallet.address); - const method = "closeLoan"; - const params = [wallet.address, loanId]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(loanManager, method, params, wallet, [owner]); - } else { - const tx = await loanManager[method](...params, { from: owner }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, loanManager, "LoanClosed"); - - const marketsAfter = await comptroller.getAssetsIn(wallet.address); - assert.isTrue(marketsAfter.length === marketsBefore.length - debtMarkets, `should have exited ${debtMarkets} market (relayed: ${relayed})`); - } - - it("should close an ETH/token1 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.1"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testCloseLoan({ loanId, relayed: false }); - }); - - it("should close an ETH/token1 loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.1"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testCloseLoan({ loanId, relayed: true }); - }); - - it("should close an token1/ETH loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.1"), token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testCloseLoan({ loanId, relayed: false }); - }); - - it("should close an token1/ETH loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.1"), token1Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.5"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.001"), relayed: false, - }); - await testCloseLoan({ loanId, relayed: true }); - }); - - it("should close a loan collateralized with ETH when there is a pre-existing loan collateralized with token1", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("0.5"), token1Amount: web3.utils.toWei("0.5") }); - await testOpenLoan({ - collateral: token1, collateralAmount: web3.utils.toWei("0.4"), debt: ETH_TOKEN, debtAmount: web3.utils.toWei("0.0000001"), relayed: false, - }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.4"), debt: token1, debtAmount: web3.utils.toWei("0.0000001"), relayed: false, - }); - // should not exit any market - await testCloseLoan({ loanId, relayed: false, debtMarkets: 0 }); - }); - - it("should close an ETH/token1+token2 loan (blockchain tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("1"), token1Amount: web3.utils.toWei("0.5"), token2Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.2"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token2, amount: web3.utils.toWei("0.001"), add: true, relayed: false, - }); - await testCloseLoan({ loanId, relayed: false, debtMarkets: 2 }); - }); - - it("should close an ETH/token1+token2 loan (relayed tx)", async () => { - await fundWallet({ ethAmount: web3.utils.toWei("1"), token1Amount: web3.utils.toWei("0.5"), token2Amount: web3.utils.toWei("0.5") }); - const loanId = await testOpenLoan({ - collateral: ETH_TOKEN, collateralAmount: web3.utils.toWei("0.2"), debt: token1, debtAmount: web3.utils.toWei("0.01"), relayed: false, - }); - await testChangeDebt({ - loanId, debtToken: token2, amount: web3.utils.toWei("0.001"), add: true, relayed: false, - }); - await testCloseLoan({ loanId, relayed: true, debtMarkets: 2 }); - }); - }); - }); -}); diff --git a/test/dsr.js b/test/dsr.js new file mode 100644 index 000000000..d5b811b55 --- /dev/null +++ b/test/dsr.js @@ -0,0 +1,180 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const PotFilter = artifacts.require("PotFilter"); +const VatFilter = artifacts.require("VatFilter"); +const DaiJoinFilter = artifacts.require("DaiJoinFilter"); + +// Utils +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeCalls, encodeTransaction } = require("../utils/utilities.js"); +const { deployMaker, WAD } = require("../utils/defi-deployer"); + +const DAI_SENT = WAD.div(new BN(100000000)); +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("DSR Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const relayer = accounts[4]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let dappRegistry; + let uniswapRouter; + let pot; + let dai; + let daiJoin; + let vat; + + before(async () => { + // Deploy Maker + const m = await deployMaker(infrastructure); + [pot, dai, vat, daiJoin] = [m.pot, m.dai, m.vat, m.daiJoin]; + + // Deploy and fund UniswapV2 + const weth = await WETH.new(); + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + + await dappRegistry.addDapp(0, pot.address, (await PotFilter.new()).address); + await dappRegistry.addDapp(0, daiJoin.address, (await DaiJoinFilter.new()).address); + await dappRegistry.addDapp(0, vat.address, (await VatFilter.new(daiJoin.address, pot.address)).address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("0.1")); + await dai.mint(wallet.address, DAI_SENT.muln(20)); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + const multiCall = async (transactions) => { + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + return utils.parseRelayReceipt(txReceipt); + }; + + const deposit = async () => multiCall(encodeCalls([ + [pot, "drip"], + [dai, "approve", [daiJoin.address, DAI_SENT.toString()]], + [daiJoin, "join", [wallet.address, DAI_SENT.toString()]], + [vat, "hope", [pot.address]], + [pot, "join", [DAI_SENT.toString()]], + ])); + + const withdraw = async () => multiCall(encodeCalls([ + [pot, "drip"], + [pot, "exit", [DAI_SENT.toString()]], + [vat, "hope", [daiJoin.address]], + [daiJoin, "exit", [wallet.address, DAI_SENT.toString()]], + ])); + + it("should allow deposits", async () => { + const { success, error } = await deposit(); + assert.isTrue(success, `deposit failed: "${error}"`); + }); + + it("should allow withdrawals", async () => { + await deposit(); + const { success, error } = await withdraw({ all: false }); + assert.isTrue(success, `withdraw failed: "${error}"`); + }); + + it("should not allow direct transfers to pot, vat or daiJoin", async () => { + for (const to of [pot.address, vat.address, daiJoin.address]) { + const { success, error } = await multiCall(encodeCalls([[dai, "transfer", [to, DAI_SENT.toString()]]])); + assert.isFalse(success, "transfer should have failed"); + assert.equal(error, "TM: call not authorised"); + } + }); + + it("should not allow unsupported method call to pot, vat or daiJoin", async () => { + for (const [to, method] of [[pot, "cage"], [vat, "vice"], [daiJoin, "live"]]) { + const { success, error } = await multiCall(encodeCalls([[to, method]])); + assert.isFalse(success, `${method}() should have failed`); + assert.equal(error, "TM: call not authorised"); + } + }); + + it("should not allow sending ETH to pot, vat or daiJoin", async () => { + for (const to of [pot.address, vat.address, daiJoin.address]) { + const { success, error } = await multiCall([encodeTransaction(to, web3.utils.toWei("0.01"), "0x")]); + assert.isFalse(success, "sending ETH should have failed"); + assert.equal(error, "TM: call not authorised"); + } + }); +}); diff --git a/test/ens.js b/test/ens.js index c9ce817d6..f57bd2685 100644 --- a/test/ens.js +++ b/test/ens.js @@ -1,5 +1,6 @@ /* global artifacts */ const ethers = require("ethers"); +const TruffleContract = require("@truffle/contract"); const ENSRegistry = artifacts.require("ENSRegistry"); const ENSRegistryWithFallback = artifacts.require("ENSRegistryWithFallback"); @@ -7,16 +8,38 @@ const ENSManager = artifacts.require("ArgentENSManager"); const ENSResolver = artifacts.require("ArgentENSResolver"); const ENSReverseRegistrar = artifacts.require("ReverseRegistrar"); +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const Filter = artifacts.require("TestFilter"); + const truffleAssert = require("truffle-assertions"); -const utilities = require("../utils/utilities.js"); +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction } = require("../utils/utilities.js"); +const RelayManager = require("../utils/relay-manager"); + +const WalletFactoryV16Contract = require("../build-legacy/v1.6.0/WalletFactory"); +const BaseWalletV16Contract = require("../build-legacy/v1.6.0/BaseWallet"); + +const WalletFactoryV16 = TruffleContract(WalletFactoryV16Contract); +const BaseWalletV16 = TruffleContract(BaseWalletV16Contract); const ZERO_BYTES32 = ethers.constants.HashZero; +const ZERO_ADDRESS = ethers.constants.AddressZero; contract("ENS contracts", (accounts) => { const infrastructure = accounts[0]; const owner = accounts[1]; - const amanager = accounts[2]; - const anonmanager = accounts[3]; + const guardian1 = accounts[2]; + const amanager = accounts[3]; + const anonmanager = accounts[4]; + const recipient = accounts[5]; + const refundAddress = accounts[7]; const root = "xyz"; const subnameWallet = "argent"; @@ -26,6 +49,14 @@ contract("ENS contracts", (accounts) => { let ensResolver; let ensReverse; let ensManager; + let factory; + + before(async () => { + WalletFactoryV16.defaults({ from: accounts[0] }); + WalletFactoryV16.setProvider(web3.currentProvider); + BaseWalletV16.defaults({ from: accounts[0] }); + BaseWalletV16.setProvider(web3.currentProvider); + }); beforeEach(async () => { const ensRegistryWithoutFallback = await ENSRegistry.new(); @@ -58,10 +89,15 @@ contract("ENS contracts", (accounts) => { assert.equal(ensResolverOnManager, ensResolver.address, "should have the correct ENSResolver addrress"); }); + it("should return correct ENSReeverseRegistrar", async () => { + const reverseRegistrar = await ensManager.getENSReverseRegistrar(); + assert.equal(reverseRegistrar, ensReverse.address); + }); + it("should register an ENS name", async () => { const label = "wallet"; const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); - await ensManager.register(label, owner); + await ensManager.register(label, owner, "0x"); const recordExists = await ensRegistry.recordExists(labelNode); assert.isTrue(recordExists); @@ -71,6 +107,75 @@ contract("ENS contracts", (accounts) => { assert.equal(res, ensResolver.address); }); + it("should not be able to register an ENS name twice", async () => { + const label = "wallet"; + await ensManager.register(label, owner, "0x"); + + await truffleAssert.reverts(ensManager.register(label, owner, "0x"), "AEM: label is already owned"); + }); + + it("should not be able to register an empty ENS label", async () => { + const label = ""; + await truffleAssert.reverts(ensManager.register(label, owner, "0x"), "AEM: ENS label must be defined"); + }); + + it("should register an ENS name with manager signature", async () => { + const label = "walletа"; + const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); + + const message = `0x${[ + owner, + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(label)), + ].map((hex) => hex.slice(2)).join("")}`; + const managerSig = await utils.signMessage(ethers.utils.keccak256(message), infrastructure); + + const data = await ensManager.contract.methods["register(string,address,bytes)"](label, owner, managerSig).encodeABI(); + await ensManager.sendTransaction({ data, from: anonmanager }); + + const recordExists = await ensRegistry.recordExists(labelNode); + assert.isTrue(recordExists); + const nodeOwner = await ensRegistry.owner(labelNode); + assert.equal(nodeOwner, owner); + + const ensRecord = await ensResolver.addr(labelNode); + assert.equal(ensRecord, owner); + + // check ens reverse record in not set + const node = await ensReverse.node(owner); + const name = await ensResolver.name(node); + assert.equal(name, ""); + }); + + it("should register wallet with reverse registrar when required", async () => { + // Register with ENSReverse registrar first + await ensReverse.claimWithResolver(ensManager.address, ensResolver.address, { from: owner }); + + const label = "wallet"; + const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); + + const message = `0x${[ + owner, + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(label)), + ].map((hex) => hex.slice(2)).join("")}`; + const managerSig = await utils.signMessage(ethers.utils.keccak256(message), infrastructure); + + const data = await ensManager.contract.methods["register(string,address,bytes)"](label, owner, managerSig).encodeABI(); + await ensManager.sendTransaction({ data, from: anonmanager }); + + // check ens record + const recordExists = await ensRegistry.recordExists(labelNode); + assert.isTrue(recordExists); + const nodeOwner = await ensRegistry.owner(labelNode); + assert.equal(nodeOwner, owner); + const ensRecord = await ensResolver.addr(labelNode); + assert.equal(ensRecord, owner); + + // check ens reverse record + const reverseNode = await ensReverse.node(owner); + const name = await ensResolver.name(reverseNode); + assert.equal(name, "wallet.argent.xyz"); + }); + it("should return the correct availability for a subnode", async () => { const label = "wallet"; const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); @@ -80,7 +185,7 @@ contract("ENS contracts", (accounts) => { assert.isTrue(available); // then we register it - await ensManager.register(label, owner); + await ensManager.register(label, owner, "0x"); const nodeOwner = await ensRegistry.owner(labelNode); assert.equal(nodeOwner, owner); @@ -94,42 +199,45 @@ contract("ENS contracts", (accounts) => { const label = "wallet"; const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); await ensManager.addManager(amanager); - await ensManager.register(label, owner, { from: amanager }); + const data = await ensManager.contract.methods["register(string,address,bytes)"](label, owner, "0x").encodeABI(); + await ensManager.sendTransaction({ data, from: amanager }); + const nodeOwner = await ensRegistry.owner(labelNode); assert.equal(nodeOwner, owner, "new manager should have registered the ens name"); }); it("should fail to register an ENS name when the caller is not a manager", async () => { const label = "wallet"; - await truffleAssert.reverts(ensManager.register(label, owner, { from: anonmanager }), "M: Must be manager"); + const data = await ensManager.contract.methods["register(string,address,bytes)"](label, owner, "0x").encodeABI(); + await truffleAssert.reverts(ensManager.sendTransaction({ data, from: anonmanager }), "AEM: user is not manager"); }); it("should be able to change the root node owner", async () => { - const randomAddress = await utilities.getRandomAddress(); + const randomAddress = await utils.getRandomAddress(); await ensManager.changeRootnodeOwner(randomAddress); const rootNodeOwner = await ensRegistry.owner(walletNode); assert.equal(rootNodeOwner, randomAddress); }); it("should not be able to change the root node owner if not the owner", async () => { - const randomAddress = await utilities.getRandomAddress(); + const randomAddress = await utils.getRandomAddress(); await truffleAssert.reverts(ensManager.changeRootnodeOwner(randomAddress, { from: amanager }), "Must be owner"); }); it("should be able to change the ens resolver", async () => { - const randomAddress = await utilities.getRandomAddress(); + const randomAddress = await utils.getRandomAddress(); await ensManager.changeENSResolver(randomAddress); const resolver = await ensManager.ensResolver(); assert.equal(resolver, randomAddress); }); it("should not be able to change the ens resolver if not owner", async () => { - const randomAddress = await utilities.getRandomAddress(); + const randomAddress = await utils.getRandomAddress(); await truffleAssert.reverts(ensManager.changeENSResolver(randomAddress, { from: amanager }), "Must be owner"); }); it("should not be able to change the ens resolver to an empty address", async () => { - await truffleAssert.reverts(ensManager.changeENSResolver(ethers.constants.AddressZero), "WF: address cannot be null"); + await truffleAssert.reverts(ensManager.changeENSResolver(ethers.constants.AddressZero), "AEM: cannot set empty resolver"); }); }); @@ -155,10 +263,133 @@ contract("ENS contracts", (accounts) => { it("should resolve a name", async () => { const label = "wallet"; - await ensManager.register(label, owner); + const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); - const node = await ensReverse.node(owner); - const name = await ensResolver.name(node); + await ensManager.register(label, owner, "0x"); + + const ensRecord = await ensResolver.addr(labelNode); + assert.equal(ensRecord, owner); + }); + }); + + describe("ENS Integrations", () => { + let registry; + let wallet; + let module; + let manager; + + beforeEach(async () => { + registry = await Registry.new(); + const guardianStorage = await GuardianStorage.new(); + const transferStorage = await TransferStorage.new(); + const dappRegistry = await DappRegistry.new(0); + const filter = await Filter.new(); + + await dappRegistry.addDapp(0, ensReverse.address, filter.address); + await dappRegistry.addDapp(0, ensManager.address, filter.address); + await dappRegistry.addDapp(0, recipient, ZERO_ADDRESS); + + const uniswapRouter = await UniswapV2Router01.new(); + const SECURITY_PERIOD = 2; + const SECURITY_WINDOW = 2; + const LOCK_PERIOD = 4; + const RECOVERY_PERIOD = 4; + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + LOCK_PERIOD, + RECOVERY_PERIOD); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + await wallet.send(web3.utils.toWei("1")); + }); + + it("should be able to register label via a relayed multiCall", async () => { + const transactions = []; + // build the claimWithResolver call + let data = ensReverse.contract.methods.claimWithResolver(ensManager.address, ensResolver.address).encodeABI(); + let transaction = encodeTransaction(ensReverse.address, 0, data); + transactions.push(transaction); + + // build the ens register call + const label = "wallet"; + const message = `0x${[ + wallet.address, + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(label)), + ].map((hex) => hex.slice(2)).join("")}`; + const managerSig = await utils.signMessage(ethers.utils.keccak256(message), infrastructure); + data = ensManager.contract.methods.register(label, wallet.address, managerSig).encodeABI(); + transaction = encodeTransaction(ensManager.address, 0, data, false); + transactions.push(transaction); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + recipient); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "call failed"); + + const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); + const recordExists = await ensRegistry.recordExists(labelNode); + assert.isTrue(recordExists); + const nodeOwner = await ensRegistry.owner(labelNode); + assert.equal(nodeOwner, wallet.address); + const res = await ensRegistry.resolver(labelNode); + assert.equal(res, ensResolver.address); + + // check ens reverse record + const reverseNode = await ensReverse.node(wallet.address); + const name = await ensResolver.name(reverseNode); + assert.equal(name, "wallet.argent.xyz"); + + console.log("Gas to register ENS label: ", txReceipt.gasUsed); + }); + + it("should support registering ens for wallets created using the legacy wallet factory v1.6", async () => { + const walletImpl = await BaseWalletV16.new(); + const factoryV16 = await WalletFactoryV16.new(registry.address, walletImpl.address, ensManager.address); + await factoryV16.addManager(infrastructure); + await ensManager.addManager(factoryV16.address); + const label = "wallet"; + const tx = await factoryV16.createWallet(owner, [module.address], label, { from: infrastructure }); + const event = await utils.getEvent(tx.receipt, factoryV16, "WalletCreated"); + const walletAddr = event.args.wallet; + + const labelNode = ethers.utils.namehash(`${label}.${subnameWallet}.${root}`); + const recordExists = await ensRegistry.recordExists(labelNode); + assert.isTrue(recordExists); + const nodeOwner = await ensRegistry.owner(labelNode); + assert.equal(nodeOwner, walletAddr); + const res = await ensRegistry.resolver(labelNode); + assert.equal(res, ensResolver.address); + + // check ens reverse record + const reverseNode = await ensReverse.node(walletAddr); + const name = await ensResolver.name(reverseNode); assert.equal(name, "wallet.argent.xyz"); }); }); diff --git a/test/factory.js b/test/factory.js index 042101ec1..03ebe19b3 100644 --- a/test/factory.js +++ b/test/factory.js @@ -2,18 +2,25 @@ const ethers = require("ethers"); const truffleAssert = require("truffle-assertions"); +const Registry = artifacts.require("ModuleRegistry"); const BaseWallet = artifacts.require("BaseWallet"); -const VersionManager = artifacts.require("VersionManager"); -const ModuleRegistry = artifacts.require("ModuleRegistry"); const Factory = artifacts.require("WalletFactory"); const GuardianStorage = artifacts.require("GuardianStorage"); +const TransferStorage = artifacts.require("TransferStorage"); +const ArgentModule = artifacts.require("ArgentModule"); const ERC20 = artifacts.require("TestERC20"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); const utils = require("../utils/utilities.js"); const { ETH_TOKEN } = require("../utils/utilities.js"); const ZERO_ADDRESS = ethers.constants.AddressZero; -const ZERO_BYTES32 = ethers.constants.HashZero; +const ZERO_BYTES = "0x"; + +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; contract("WalletFactory", (accounts) => { const infrastructure = accounts[0]; @@ -23,36 +30,46 @@ contract("WalletFactory", (accounts) => { const refundAddress = accounts[7]; let implementation; - let moduleRegistry; let guardianStorage; let factory; - let versionManager; + let transferStorage; + let modules; + let module; + let registry; before(async () => { + const uniswapRouter = await UniswapV2Router01.new(); + implementation = await BaseWallet.new(); - moduleRegistry = await ModuleRegistry.new(); guardianStorage = await GuardianStorage.new(); factory = await Factory.new( - moduleRegistry.address, implementation.address, guardianStorage.address, refundAddress); await factory.addManager(infrastructure); - }); - async function deployVersionManager() { - const vm = await VersionManager.new( - moduleRegistry.address, + registry = await Registry.new(); + transferStorage = await TransferStorage.new(); + + module = await ArgentModule.new( + registry.address, guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - await vm.addVersion([], []); - return vm; - } + transferStorage.address, + ZERO_ADDRESS, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); - async function signRefund(amount, token, signer) { + modules = [module.address]; + }); + + async function signRefund(wallet, amount, token, signer) { const message = `0x${[ + wallet, ethers.utils.hexZeroPad(ethers.utils.hexlify(amount), 32), token, ].map((hex) => hex.slice(2)).join("")}`; @@ -61,52 +78,30 @@ contract("WalletFactory", (accounts) => { } beforeEach(async () => { - // Restore the good state of factory (we set these to bad addresses in some tests) - await factory.changeModuleRegistry(moduleRegistry.address); + // Restore the good state of factory (we use bad addresses in some tests) await factory.changeRefundAddress(refundAddress); - - versionManager = await deployVersionManager(); - await moduleRegistry.registerModule(versionManager.address, ethers.utils.formatBytes32String("versionManager")); }); describe("Create and configure the factory", () => { - it("should not allow to be created with empty ModuleRegistry", async () => { - await truffleAssert.reverts(Factory.new( - ZERO_ADDRESS, - implementation.address, - guardianStorage.address, - refundAddress), "WF: ModuleRegistry address not defined"); - }); - it("should not allow to be created with empty WalletImplementation", async () => { await truffleAssert.reverts(Factory.new( - moduleRegistry.address, ZERO_ADDRESS, guardianStorage.address, - refundAddress), "WF: WalletImplementation address not defined"); + refundAddress), "WF: empty wallet implementation"); }); it("should not allow to be created with empty GuardianStorage", async () => { await truffleAssert.reverts(Factory.new( - moduleRegistry.address, implementation.address, ZERO_ADDRESS, - refundAddress), "WF: GuardianStorage address not defined"); + refundAddress), "WF: empty guardian storage"); }); it("should not allow to be created with empty refund address", async () => { await truffleAssert.reverts(Factory.new( - moduleRegistry.address, implementation.address, guardianStorage.address, - ZERO_ADDRESS), "WF: refund address not defined"); - }); - - it("should allow owner to change the module registry", async () => { - const randomAddress = utils.getRandomAddress(); - await factory.changeModuleRegistry(randomAddress); - const updatedModuleRegistry = await factory.moduleRegistry(); - assert.equal(updatedModuleRegistry, randomAddress); + ZERO_ADDRESS), "WF: empty refund address"); }); it("should allow owner to change the refund address", async () => { @@ -116,17 +111,8 @@ contract("WalletFactory", (accounts) => { assert.equal(updatedRefundAddress, randomAddress); }); - it("should not allow owner to change the module registry to zero address", async () => { - await truffleAssert.reverts(factory.changeModuleRegistry(ZERO_ADDRESS), "WF: address cannot be null"); - }); - it("should not allow owner to change the refund address to zero address", async () => { - await truffleAssert.reverts(factory.changeRefundAddress(ZERO_ADDRESS), "WF: address cannot be null"); - }); - - it("should not allow non-owner to change the module registry", async () => { - const randomAddress = utils.getRandomAddress(); - await truffleAssert.reverts(factory.changeModuleRegistry(randomAddress, { from: other }), "Must be owner"); + await truffleAssert.reverts(factory.changeRefundAddress(ZERO_ADDRESS), "WF: cannot set to empty"); }); it("should not allow non-owner to change the refund address", async () => { @@ -136,109 +122,88 @@ contract("WalletFactory", (accounts) => { }); describe("Create wallets with CREATE2", () => { - beforeEach(async () => { - versionManager = await deployVersionManager(); - await moduleRegistry.registerModule(versionManager.address, ethers.utils.formatBytes32String("versionManager")); - }); - - it("should create a wallet at the correct address", async () => { + it("should let a manager create a wallet with the correct (owner, modules, guardian) properties", async () => { const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); - // we create the wallet - const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, - ); - const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); - const walletAddr = event.args.wallet; - // we test that the wallet is at the correct address - assert.equal(futureAddr, walletAddr, "should have the correct address"); - }); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); - it("should create with the correct owner", async () => { - const salt = utils.generateSaltValue(); + const managerSig = "0x"; // we create the wallet const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, - ); + owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, managerSig, { from: infrastructure }); + const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; + // we test that the wallet is at the correct address + assert.equal(futureAddr, walletAddr, "should have the correct address"); - // we test that the wallet has the correct owner const wallet = await BaseWallet.at(walletAddr); const walletOwner = await wallet.owner(); assert.equal(walletOwner, owner, "should have the correct owner"); + // we test that the wallet has the correct modules + const isAuthorised = await wallet.authorised(module.address); + assert.equal(isAuthorised, true, "module should be authorised"); + const count = await wallet.modules(); + assert.equal(count, 1, "1 module should be authorised"); + // we test that the wallet has the correct guardian + const success = await guardianStorage.isGuardian(walletAddr, guardian); + assert.equal(success, true, "should have the correct guardian"); }); - it("should create with the correct modules", async () => { + it("should let anyone (possessing the right signature) create a wallet with the correct (owner, modules, guardian) properties", async () => { const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); - // we create the wallet - const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, - ); - const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); - const walletAddr = event.args.wallet; - // we test that the wallet is at the correct address - assert.equal(futureAddr, walletAddr, "should have the correct address"); - // we test that the wallet has the correct modules - const wallet = await await BaseWallet.at(walletAddr); - const isAuthorised = await wallet.authorised(versionManager.address); - assert.equal(isAuthorised, true, "versionManager should be authorised"); - }); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); - it("should create when the target version was blacklisted", async () => { - const badVersion = await versionManager.lastVersion(); - await versionManager.addVersion([], []); - await versionManager.setMinVersion(await versionManager.lastVersion()); + const msg = ethers.utils.hexZeroPad(futureAddr, 32); + const managerSig = await utils.signMessage(msg, infrastructure); - const salt = utils.generateSaltValue(); - // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet( - owner, versionManager.address, guardian, salt, badVersion, - ); // we create the wallet const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, badVersion, 0, ZERO_ADDRESS, ZERO_BYTES32, - ); + owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, managerSig, { from: owner }); + const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; // we test that the wallet is at the correct address assert.equal(futureAddr, walletAddr, "should have the correct address"); }); - it("should create with the correct guardian", async () => { + it("should create with the correct static calls", async () => { const salt = utils.generateSaltValue(); - // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); - // we create the wallet + + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); + + const msg = ethers.utils.hexZeroPad(futureAddr, 32); + const managerSig = await utils.signMessage(msg, infrastructure); const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, - ); + owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, managerSig, { from: owner }); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; - // we test that the wallet is at the correct address - assert.equal(futureAddr, walletAddr, "should have the correct address"); - // we test that the wallet has the correct guardian - const success = await guardianStorage.isGuardian(walletAddr, guardian); - assert.equal(success, true, "should have the correct guardian"); + const wallet = await BaseWallet.at(walletAddr); + + const ERC1271_ISVALIDSIGNATURE_BYTES32 = utils.sha3("isValidSignature(bytes32,bytes)").slice(0, 10); + const isValidSignatureDelegate = await wallet.enabled(ERC1271_ISVALIDSIGNATURE_BYTES32); + assert.equal(isValidSignatureDelegate, module.address); + + const ERC721_RECEIVED = utils.sha3("onERC721Received(address,address,uint256,bytes)").slice(0, 10); + const isERC721Received = await wallet.enabled(ERC721_RECEIVED); + assert.equal(isERC721Received, module.address); }); it("should create and refund in ETH when a valid signature is provided", async () => { const refundAmount = 1000; const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // We send ETH to the address await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr, value: refundAmount }); // we get the owner signature for the refund - const ownerSig = await signRefund(refundAmount, ETH_TOKEN, owner); + const ownerSig = await signRefund(futureAddr, refundAmount, ETH_TOKEN, owner); // we create the wallet const balanceBefore = await utils.getBalance(refundAddress); const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, refundAmount, ETH_TOKEN, ownerSig, + owner, modules, guardian, salt, refundAmount, ETH_TOKEN, ownerSig, "0x", ); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; @@ -256,15 +221,15 @@ contract("WalletFactory", (accounts) => { const refundAmount = 1000; const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // We create an ERC20 token and give some to the wallet const token = await ERC20.new([infrastructure, futureAddr], 10000000, 12); // we get the owner signature for the refund - const ownerSig = await signRefund(refundAmount, token.address, owner); + const ownerSig = await signRefund(futureAddr, refundAmount, token.address, owner); // we create the wallet const balanceBefore = await token.balanceOf(refundAddress); const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, refundAmount, token.address, ownerSig, + owner, modules, guardian, salt, refundAmount, token.address, ownerSig, "0x" ); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; @@ -283,15 +248,15 @@ contract("WalletFactory", (accounts) => { const refundAmount = 1000; const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // We send ETH to the address await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr, value: refundAmount }); // we get the owner signature for the refund - const ownerSig = await signRefund(refundAmount, ETH_TOKEN, owner); + const ownerSig = await signRefund(futureAddr, refundAmount, ETH_TOKEN, owner); // we create the wallet const balanceBefore = await utils.getBalance(refundAddress); const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 2 * refundAmount, ETH_TOKEN, ownerSig, + owner, modules, guardian, salt, 2 * refundAmount, ETH_TOKEN, ownerSig, "0x" ); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; @@ -306,39 +271,63 @@ contract("WalletFactory", (accounts) => { const refundAmount = 1000; const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // We send ETH to the address await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr, value: refundAmount }); // we get the owner signature for the refund - const otherSig = await signRefund(refundAmount, ETH_TOKEN, other); + const otherSig = await signRefund(futureAddr, refundAmount, ETH_TOKEN, other); // we create the wallet const balanceBefore = await utils.getBalance(refundAddress); const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, refundAmount, ETH_TOKEN, otherSig, + owner, modules, guardian, salt, refundAmount, ETH_TOKEN, otherSig, "0x" ); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); const walletAddr = event.args.wallet; const balanceAfter = await utils.getBalance(refundAddress); // we test that the wallet is at the correct address assert.equal(futureAddr, walletAddr, "should have the correct address"); - // we test that the creation was refunded + // we test that the creation was not refunded + assert.equal(balanceAfter.sub(balanceBefore), 0, "should not have refunded"); + }); + + it("should create but not refund when a replayed owner signature is provided", async () => { + const refundAmount = 1000; + // Create the signature for the first wallet with owner account + const salt1 = utils.generateSaltValue(); + const futureAddr1 = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt1); + const ownerSig = await signRefund(futureAddr1, refundAmount, ETH_TOKEN, owner); + + // Create a second wallet with the same ownerSig + const salt2 = utils.generateSaltValue(); + const futureAddr2 = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt2); + await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr2, value: refundAmount }); + + const balanceBefore = await utils.getBalance(refundAddress); + const tx2 = await factory.createCounterfactualWallet( + owner, modules, guardian, salt2, refundAmount, ETH_TOKEN, ownerSig, "0x", + ); + const event = await utils.getEvent(tx2.receipt, factory, "WalletCreated"); + const walletAddr = event.args.wallet; + assert.equal(futureAddr2, walletAddr, "should have the correct address"); + const balanceAfter = await utils.getBalance(refundAddress); + // we test that the creation was not refunded assert.equal(balanceAfter.sub(balanceBefore), 0, "should not have refunded"); }); it("should fail to create a wallet at an existing address", async () => { const salt = utils.generateSaltValue(); // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // we create the first wallet const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, + owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x" ); const event = await utils.getEvent(tx.receipt, factory, "WalletCreated"); // we test that the wallet is at the correct address assert.equal(futureAddr, event.args.wallet, "should have the correct address"); // we create the second wallet await truffleAssert.reverts( - factory.createCounterfactualWallet(owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32) + factory.createCounterfactualWallet(owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x") ); }); @@ -346,14 +335,14 @@ contract("WalletFactory", (accounts) => { const refundAmount = 1000; const salt = utils.generateSaltValue(); - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // Send less ETH than the refund await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr, value: 900 }); // we get the owner signature for a refund - const ownerSig = await signRefund(refundAmount, ETH_TOKEN, owner); + const ownerSig = await signRefund(futureAddr, refundAmount, ETH_TOKEN, owner); await truffleAssert.reverts( - factory.createCounterfactualWallet(owner, versionManager.address, guardian, salt, 1, refundAmount, ETH_TOKEN, ownerSig) + factory.createCounterfactualWallet(owner, modules, guardian, salt, refundAmount, ETH_TOKEN, ownerSig, "0x") ); }); @@ -361,17 +350,39 @@ contract("WalletFactory", (accounts) => { const salt = utils.generateSaltValue(); await truffleAssert.reverts( factory.createCounterfactualWallet( - owner, ethers.constants.AddressZero, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, - ), - "WF: invalid _versionManager", + owner, [ethers.constants.AddressZero], guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x" + )); + }); + + it("should fail to create when the owner is empty", async () => { + const salt = utils.generateSaltValue(); + await truffleAssert.reverts( + factory.createCounterfactualWallet(ZERO_ADDRESS, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x"), + "WF: empty owner address", ); }); it("should fail to create when the guardian is empty", async () => { const salt = utils.generateSaltValue(); await truffleAssert.reverts( - factory.createCounterfactualWallet(owner, versionManager.address, ZERO_ADDRESS, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32,), - "WF: guardian cannot be null", + factory.createCounterfactualWallet(owner, modules, ZERO_ADDRESS, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x"), + "WF: empty guardian", + ); + }); + + it("should fail to create when the owner is the guardian", async () => { + const salt = utils.generateSaltValue(); + await truffleAssert.reverts( + factory.createCounterfactualWallet(owner, modules, owner, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x"), + "WF: owner cannot be guardian", + ); + }); + + it("should fail to create by a non-manager without a manager's signature", async () => { + const salt = utils.generateSaltValue(); + await truffleAssert.reverts( + factory.createCounterfactualWallet(owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x", { from: other }), + "WF: unauthorised wallet creation", ); }); @@ -379,12 +390,12 @@ contract("WalletFactory", (accounts) => { const salt = utils.generateSaltValue(); const amount = 10000000000000; // we get the future address - const futureAddr = await factory.getAddressForCounterfactualWallet(owner, versionManager.address, guardian, salt, 1); + const futureAddr = await factory.getAddressForCounterfactualWallet(owner, modules, guardian, salt); // We send ETH to the address await web3.eth.sendTransaction({ from: infrastructure, to: futureAddr, value: amount }); // we create the wallet const tx = await factory.createCounterfactualWallet( - owner, versionManager.address, guardian, salt, 1, 0, ZERO_ADDRESS, ZERO_BYTES32, + owner, modules, guardian, salt, 0, ZERO_ADDRESS, ZERO_BYTES, "0x" ); const wallet = await BaseWallet.at(futureAddr); const event = await utils.getEvent(tx.receipt, wallet, "Received"); @@ -395,9 +406,35 @@ contract("WalletFactory", (accounts) => { it("should fail to get an address when the guardian is empty", async () => { const salt = utils.generateSaltValue(); await truffleAssert.reverts( - factory.getAddressForCounterfactualWallet(owner, versionManager.address, ZERO_ADDRESS, salt, 1), - "WF: guardian cannot be null", + factory.getAddressForCounterfactualWallet(owner, modules, ZERO_ADDRESS, salt), + "WF: empty guardian", ); }); }); + + describe("Managed-like contract logic", () => { + it("should not be able to revoke a manager", async () => { + await truffleAssert.reverts(factory.revokeManager(infrastructure), "WF: managers can't be revoked"); + }); + + it("should not be able to add manager if not called by owner", async () => { + await truffleAssert.reverts(factory.addManager(other, { from: other }), "Must be owner"); + }); + + it("should not be able to set manager to zero address", async () => { + await truffleAssert.reverts(factory.addManager(ethers.constants.AddressZero), "M: Address must not be null"); + }); + + it("should be able to set manager twice without error", async () => { + // Set manager once + await factory.addManager(other); + let isManager = await factory.managers(other); + assert.isTrue(isManager); + + // Set manager twice + await factory.addManager(other); + isManager = await factory.managers(other); + assert.isTrue(isManager); + }); + }); }); diff --git a/test/guardianManager.js b/test/guardianManager.js deleted file mode 100644 index 7acde8b3d..000000000 --- a/test/guardianManager.js +++ /dev/null @@ -1,446 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); - -const GuardianManager = artifacts.require("GuardianManager"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); -const Registry = artifacts.require("ModuleRegistry"); -const DumbContract = artifacts.require("TestContract"); -const NonCompliantGuardian = artifacts.require("NonCompliantGuardian"); - -const RelayManager = require("../utils/relay-manager"); -const utilities = require("../utils/utilities.js"); - -contract("GuardianManager", (accounts) => { - const manager = new RelayManager(); - - const owner = accounts[1]; - const guardian1 = accounts[2]; - const guardian2 = accounts[3]; - const guardian3 = accounts[4]; - const guardian4 = accounts[5]; - const guardian5 = accounts[6]; - const nonowner = accounts[7]; - - let wallet; - let walletImplementation; - let lockStorage; - let guardianStorage; - let guardianManager; - let relayerManager; - let versionManager; - - before(async () => { - walletImplementation = await BaseWallet.new(); - }); - - beforeEach(async () => { - const registry = await Registry.new(); - lockStorage = await LockStorage.new(); - guardianStorage = await GuardianStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, - 12); - await versionManager.addVersion([guardianManager.address, relayerManager.address], []); - await manager.setRelayerManager(relayerManager); - - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - }); - - describe("Adding Guardians", () => { - describe("EOA Guardians", () => { - it("should let the owner add EOA Guardians (blockchain transaction)", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isTrue(active, "first guardian should be active"); - assert.equal(count, 1, "1 guardian should be active"); - - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardian2); - assert.isFalse(active, "second guardian should not yet be active"); - assert.equal(count, 1, "second guardian should be pending during security period"); - - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardian2); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardian2); - const guardians = await guardianManager.getGuardians(wallet.address); - assert.isTrue(active, "second guardian should be active"); - assert.equal(count, 2, "2 guardians should be active after security period"); - assert.equal(guardian1, guardians[0], "should return first guardian address"); - assert.equal(guardian2, guardians[1], "should return second guardian address"); - }); - - it("should not let the owner add EOA Guardians after two security periods (blockchain transaction)", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - - await utilities.increaseTime(48); // 42 == 2 * security_period - await truffleAssert.reverts(guardianManager.confirmGuardianAddition(wallet.address, guardian2), - "GM: Too late to confirm guardian addition"); - - const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - const active = await guardianManager.isGuardian(wallet.address, guardian2); - assert.isFalse(active, "second guardian should not be active (addition confirmation was too late)"); - assert.equal(count, 1, "1 guardian should be active after two security periods (addition confirmation was too late)"); - }); - - it("should not allow confirming too early", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - await truffleAssert.reverts(guardianManager.confirmGuardianAddition(wallet.address, guardian2), - "GM: Too early to confirm guardian addition"); - }); - - it("should let the owner re-add EOA Guardians after missing the confirmation window (blockchain transaction)", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - - // first time - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - - await utilities.increaseTime(48); // 42 == 2 * security_period - await truffleAssert.reverts(guardianManager.confirmGuardianAddition(wallet.address, guardian2), - "GM: Too late to confirm guardian addition"); - - // second time - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardian(wallet.address, guardian2); - assert.isFalse(active, "second guardian should not yet be active"); - assert.equal(count, 1, "second guardian should be pending during security period"); - - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardian2); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardian2); - assert.isTrue(active, "second guardian should be active"); - assert.equal(count, 2, "2 guardians should be active after security period"); - }); - - it("should only let the owner add an EOA guardian", async () => { - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, guardian1, { from: nonowner }), - "BF: must be owner or feature"); - }); - - it("should not allow adding wallet owner as guardian", async () => { - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, owner, { from: owner }), - "GM: target guardian cannot be owner"); - }); - - it("should not allow adding an existing guardian twice", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, guardian1, { from: owner }), - "GM: target is already a guardian"); - }); - - it("should not allow adding a duplicate request to add a guardian to the request queue", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, guardian2, { from: owner }), - "GM: addition of target as guardian is already pending"); - }); - - it("should let the owner add an EOA guardian (relayed transaction)", async () => { - await manager.relay(guardianManager, "addGuardian", [wallet.address, guardian1], wallet, [owner]); - const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - const active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isTrue(active, "first guardian should be active"); - assert.equal(count, 1, "1 guardian should be active"); - }); - - it("should add many Guardians (blockchain transaction)", async () => { - const guardians = [guardian1, guardian2, guardian3, guardian4, guardian5]; - let count; - let active; - for (let i = 1; i <= 5; i += 1) { - await guardianManager.addGuardian(wallet.address, guardians[i - 1], { from: owner }); - if (i > 1) { - await utilities.increaseTime(31); - await guardianManager.confirmGuardianAddition(wallet.address, guardians[i - 1]); - } - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardians[i - 1]); - assert.equal(count, i, `guardian ${i} should be added`); - assert.isTrue(active, `guardian ${i} should be active`); - } - }); - - it("should add many Guardians (relayed transaction)", async () => { - const guardians = [guardian1, guardian2, guardian3, guardian4, guardian5]; - let count; - let active; - for (let i = 1; i <= 3; i += 1) { - await manager.relay(guardianManager, "addGuardian", [wallet.address, guardians[i - 1]], wallet, [owner]); - if (i > 1) { - await utilities.increaseTime(30); - await manager.relay(guardianManager, "confirmGuardianAddition", [wallet.address, guardians[i - 1]], wallet, []); - } - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardians[i - 1]); - assert.equal(count, i, `guardian ${i} should be added`); - assert.isTrue(active, `guardian ${i} should be active`); - } - }); - }); - - describe("Smart Contract Guardians", () => { - let guardianWallet1; - let guardianWallet2; - let dumbContract; - - beforeEach(async () => { - const proxy1 = await Proxy.new(walletImplementation.address); - guardianWallet1 = await BaseWallet.at(proxy1.address); - await guardianWallet1.init(guardian1, [versionManager.address]); - - const proxy2 = await Proxy.new(walletImplementation.address); - guardianWallet2 = await BaseWallet.at(proxy2.address); - await guardianWallet2.init(guardian2, [versionManager.address]); - dumbContract = await DumbContract.new(); - }); - - it("should let the owner add Smart Contract Guardians (blockchain transaction)", async () => { - await guardianManager.addGuardian(wallet.address, guardianWallet1.address, { from: owner }); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardianOrGuardianSigner(wallet.address, guardian1); - assert.isTrue(active, "first guardian owner should be recognized as guardian"); - active = await guardianManager.isGuardian(wallet.address, guardianWallet1.address); - assert.isTrue(active, "first guardian should be recognized as guardian"); - assert.equal(count, 1, "1 guardian should be active"); - - await guardianManager.addGuardian(wallet.address, guardianWallet2.address, { from: owner }); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardianOrGuardianSigner(wallet.address, guardian2); - assert.isFalse(active, "second guardian owner should not yet be active"); - active = await guardianManager.isGuardian(wallet.address, guardianWallet2.address); - assert.isFalse(active, "second guardian should not yet be active"); - assert.equal(count, 1, "second guardian should be pending during security period"); - - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardianWallet2.address); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - assert.equal(count, 2, "2 guardians should be active after security period"); - active = await guardianManager.isGuardianOrGuardianSigner(wallet.address, guardian2); - assert.isTrue(active, "second guardian owner should be active"); - active = await guardianManager.isGuardian(wallet.address, guardianWallet2.address); - assert.isTrue(active, "second guardian should be active"); - }); - - it("should let the owner add a Smart Contract guardian (relayed transaction)", async () => { - await manager.relay(guardianManager, "addGuardian", [wallet.address, guardianWallet1.address], wallet, [owner]); - const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardian(wallet.address, guardianWallet1.address); - assert.isTrue(active, "first guardian should be active"); - active = await guardianManager.isGuardianOrGuardianSigner(wallet.address, guardian1); - assert.isTrue(active, "first guardian owner should be active"); - assert.equal(count, 1, "1 guardian should be active"); - }); - - it("should not let owner add a Smart Contract guardian that does not have an owner manager", async () => { - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, dumbContract.address, { from: owner }), - "GM: guardian must be EOA or implement owner()"); - }); - - it("it should fail to add a non-compliant guardian", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - const nonCompliantGuardian = await NonCompliantGuardian.new(); - await truffleAssert.reverts(guardianManager.addGuardian(wallet.address, nonCompliantGuardian.address, { from: owner }), - "GM: guardian must be EOA or implement owner()"); - }); - }); - }); - - describe("Revoking Guardians", () => { - beforeEach(async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardian2); - const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - assert.equal(count, 2, "2 guardians should be added"); - }); - - it("should revoke a guardian (blockchain transaction)", async () => { - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isTrue(active, "the revoked guardian should still be active during the security period"); - assert.equal(count, 2, "the revoked guardian should go through a security period"); - - await utilities.increaseTime(30); - await guardianManager.confirmGuardianRevokation(wallet.address, guardian1); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isFalse(active, "the revoked guardian should no longer be active after the security period"); - assert.equal(count, 1, "the revoked guardian should be removed after the security period"); - }); - - it("should not be able to revoke a nonexistent guardian", async () => { - await truffleAssert.reverts(guardianManager.revokeGuardian(wallet.address, nonowner, { from: owner }), - "GM: must be an existing guardian"); - }); - - it("should not confirm a guardian revokation too early", async () => { - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - await truffleAssert.reverts(guardianManager.confirmGuardianRevokation(wallet.address, guardian1), - "GM: Too early to confirm guardian revokation"); - }); - - it("should not confirm a guardian revokation after two security periods (blockchain transaction)", async () => { - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - - await utilities.increaseTime(48); // 48 == 2 * security_period - await truffleAssert.reverts(guardianManager.confirmGuardianRevokation(wallet.address, guardian1), - "GM: Too late to confirm guardian revokation"); - }); - - it("should not be able to revoke a guardian twice", async () => { - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - await truffleAssert.reverts(guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }), - "GM: revokation of target as guardian is already pending"); - }); - - it("should revoke a guardian again after missing the confirmation window the first time (blockchain transaction)", async () => { - // first time - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - - await utilities.increaseTime(48); // 48 == 2 * security_period - await truffleAssert.reverts(guardianManager.confirmGuardianRevokation(wallet.address, guardian1), - "GM: Too late to confirm guardian revokation"); - - // second time - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - let active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isTrue(active, "the revoked guardian should still be active during the security period"); - assert.equal(count, 2, "the revoked guardian should go through a security period"); - - await utilities.increaseTime(30); - await guardianManager.confirmGuardianRevokation(wallet.address, guardian1); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - active = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isFalse(active, "the revoked guardian should no longer be active after the security period"); - assert.equal(count, 1, "the revoked guardian should be removed after the security period"); - }); - - it("should add a guardian after a revoke (blockchain transaction)", async () => { - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - await utilities.increaseTime(30); - await guardianManager.confirmGuardianRevokation(wallet.address, guardian1); - let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - assert.equal(count, 1, "there should be 1 guardian left"); - - await guardianManager.addGuardian(wallet.address, guardian3, { from: owner }); - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardian3); - count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); - assert.equal(count, 2, "there should be 2 guardians again"); - }); - - it("should be able to remove a guardian that is the last in the list", async () => { - await guardianManager.addGuardian(wallet.address, guardian3, { from: owner }); - await utilities.increaseTime(30); - await guardianManager.confirmGuardianAddition(wallet.address, guardian3); - let count = await guardianStorage.guardianCount(wallet.address); - assert.equal(count.toNumber(), 3, "there should be 3 guardians"); - - const guardians = await guardianStorage.getGuardians(wallet.address); - await guardianManager.revokeGuardian(wallet.address, guardians[2], { from: owner }); - await utilities.increaseTime(30); - await guardianManager.confirmGuardianRevokation(wallet.address, guardians[2]); - count = await guardianStorage.guardianCount(wallet.address); - assert.equal(count.toNumber(), 2, "there should be 2 guardians left"); - }); - }); - - describe("Cancelling Pending Guardians", () => { - beforeEach(async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - const count = (await guardianManager.guardianCount(wallet.address)).toNumber(); - assert.equal(count, 1, "1 guardian should be added"); - }); - - it("owner should be able to cancel pending addition of guardian (blockchain transaction)", async () => { - // Add guardian 2 and cancel its addition - await guardianManager.addGuardian(wallet.address, guardian2, { from: owner }); - await guardianManager.cancelGuardianAddition(wallet.address, guardian2, { from: owner }); - await utilities.increaseTime(30); - await truffleAssert.reverts(guardianManager.confirmGuardianAddition(wallet.address, guardian2), - "GM: no pending addition as guardian for target"); - }); - - it("owner should not be able to cancel a nonexistent addition of a guardian request", async () => { - await truffleAssert.reverts(guardianManager.cancelGuardianAddition(wallet.address, guardian2, { from: owner }), - "GM: no pending addition as guardian for target"); - }); - - it("owner should be able to cancel pending revokation of guardian (blockchain transaction)", async () => { - // Revoke guardian 1 and cancel its revokation - await guardianManager.revokeGuardian(wallet.address, guardian1, { from: owner }); - await guardianManager.cancelGuardianRevokation(wallet.address, guardian1, { from: owner }); - await utilities.increaseTime(30); - await truffleAssert.reverts(guardianManager.confirmGuardianRevokation(wallet.address, guardian1), - "GM: no pending guardian revokation for target"); - }); - - it("owner should not be able to cancel a nonexistent pending revokation of guardian", async () => { - await truffleAssert.reverts(guardianManager.cancelGuardianRevokation(wallet.address, nonowner, { from: owner }), - "GM: no pending guardian revokation for target"); - }); - - it("owner should be able to cancel pending addition of guardian (relayed transaction)", async () => { - // Add guardian 2 and cancel its addition - await manager.relay(guardianManager, "addGuardian", [wallet.address, guardian2], wallet, [owner]); - await manager.relay(guardianManager, "cancelGuardianAddition", [wallet.address, guardian2], wallet, [owner]); - await utilities.increaseTime(30); - await truffleAssert.reverts(guardianManager.confirmGuardianAddition(wallet.address, guardian2), - "GM: no pending addition as guardian for target"); - }); - - it("owner should be able to cancel pending revokation of guardian (relayed transaction)", async () => { - // Revoke guardian 1 and cancel its revokation - await manager.relay(guardianManager, "revokeGuardian", [wallet.address, guardian1], wallet, [owner]); - await manager.relay(guardianManager, "cancelGuardianRevokation", [wallet.address, guardian1], wallet, [owner]); - await utilities.increaseTime(30); - await truffleAssert.reverts(guardianManager.confirmGuardianRevokation(wallet.address, guardian1), - "GM: no pending guardian revokation for target"); - }); - }); - - describe("Guardian Storage", () => { - it("should not allow non modules to addGuardian", async () => { - await truffleAssert.reverts(guardianStorage.addGuardian(wallet.address, guardian4), - "TS: must be an authorized module to call this method"); - }); - - it("should not allow non modules to revokeGuardian", async () => { - await truffleAssert.reverts(guardianStorage.revokeGuardian(wallet.address, guardian1), - "TS: must be an authorized module to call this method"); - }); - }); -}); diff --git a/test/guardians.js b/test/guardians.js new file mode 100644 index 000000000..4b0e771be --- /dev/null +++ b/test/guardians.js @@ -0,0 +1,402 @@ +/* global artifacts */ +const truffleAssert = require("truffle-assertions"); +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const Proxy = artifacts.require("Proxy"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const DumbContract = artifacts.require("TestContract"); + +const utils = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 24; +const SECURITY_WINDOW = 12; +const LOCK_PERIOD = 50; +const RECOVERY_PERIOD = 36; + +const RelayManager = require("../utils/relay-manager"); + +contract("GuardianManager", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const guardian2 = accounts[3]; + const guardian3 = accounts[4]; + const guardian4 = accounts[5]; + const guardian5 = accounts[6]; + const nonowner = accounts[7]; + const refundAddress = accounts[8]; + const relayer = accounts[9]; + + let registry; + let guardianStorage; + let transferStorage; + let module; + let wallet; + let walletImplementation; + let factory; + let dappRegistry; + + before(async () => { + registry = await Registry.new(); + + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + await wallet.send(new BN("1000000000000000000")); + }); + + describe("Adding Guardians", () => { + describe("EOA Guardians", () => { + it("should let the owner add EOA Guardians", async () => { + await module.addGuardian(wallet.address, guardian2, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian2); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + let active = await module.isGuardian(wallet.address, guardian2); + assert.isTrue(active, "second guardian should be active"); + assert.equal(count, 2, "2 guardians should be active"); + + await module.addGuardian(wallet.address, guardian3, { from: owner }); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian3); + assert.isFalse(active, "third guardian should not yet be active"); + assert.equal(count, 2, "third guardian should be pending during security period"); + + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian3); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian3); + const guardians = await module.getGuardians(wallet.address); + assert.isTrue(active, "second guardian should be active"); + assert.equal(count, 3, "3 guardians should be active after security period"); + assert.equal(guardian1, guardians[0], "should return first guardian address"); + assert.equal(guardian2, guardians[1], "should return second guardian address"); + assert.equal(guardian3, guardians[2], "should return third guardian address"); + }); + + it("should not let the owner add EOA Guardians after two security periods (blockchain transaction)", async () => { + await module.addGuardian(wallet.address, guardian2, { from: owner }); + + await utils.increaseTime(48); // 42 == 2 * security_period + await truffleAssert.reverts(module.confirmGuardianAddition(wallet.address, guardian2), + "SM: pending addition expired"); + + const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + const active = await module.isGuardian(wallet.address, guardian2); + assert.isFalse(active, "second guardian should not be active (addition confirmation was too late)"); + assert.equal(count, 1, "1 guardian should be active after two security periods (addition confirmation was too late)"); + }); + + it("should not allow confirming too early", async () => { + await module.addGuardian(wallet.address, guardian2, { from: owner }); + await truffleAssert.reverts(module.confirmGuardianAddition(wallet.address, guardian2), + "SM: pending addition not over"); + }); + + it("should let the owner re-add EOA Guardians after missing the confirmation window", async () => { + // first time + await module.addGuardian(wallet.address, guardian2, { from: owner }); + + await utils.increaseTime(48); // 42 == 2 * security_period + await truffleAssert.reverts(module.confirmGuardianAddition(wallet.address, guardian2), + "SM: pending addition expired"); + + // second time + await module.addGuardian(wallet.address, guardian2, { from: owner }); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + let active = await module.isGuardian(wallet.address, guardian2); + assert.isFalse(active, "second guardian should not yet be active"); + assert.equal(count, 1, "second guardian should be pending during security period"); + + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian2); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian2); + assert.isTrue(active, "second guardian should be active"); + assert.equal(count, 2, "2 guardians should be active after security period"); + }); + + it("should only let the owner add an EOA guardian", async () => { + await truffleAssert.reverts(module.addGuardian(wallet.address, guardian2, { from: nonowner }), + "BM: must be wallet owner/self"); + }); + + it("should not allow adding wallet owner as guardian", async () => { + await truffleAssert.reverts(module.addGuardian(wallet.address, owner, { from: owner }), + "SM: guardian cannot be owner"); + }); + + it("should not allow adding an existing guardian twice", async () => { + await truffleAssert.reverts(module.addGuardian(wallet.address, guardian1, { from: owner }), + "SM: duplicate guardian"); + }); + + it("should not allow adding a duplicate request to add a guardian to the request queue", async () => { + await module.addGuardian(wallet.address, guardian2, { from: owner }); + await truffleAssert.reverts(module.addGuardian(wallet.address, guardian2, { from: owner }), + "SM: duplicate pending addition"); + }); + + it("should add many Guardians", async () => { + const guardians = [guardian1, guardian2, guardian3, guardian4, guardian5]; + let count; + let active; + for (let i = 2; i <= 3; i += 1) { + await manager.relay(module, "addGuardian", [wallet.address, guardians[i - 1]], wallet, [owner]); + if (i > 1) { + await utils.increaseTime(30); + await manager.relay(module, "confirmGuardianAddition", [wallet.address, guardians[i - 1]], wallet, []); + } + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardians[i - 1]); + assert.equal(count, i, `guardian ${i} should be added`); + assert.isTrue(active, `guardian ${i} should be active`); + } + }); + }); + + describe("Smart Contract Guardians", () => { + let guardianWallet2; + let nonCompliantGuardian; + + beforeEach(async () => { + const proxy2 = await Proxy.new(walletImplementation.address); + guardianWallet2 = await BaseWallet.at(proxy2.address); + await guardianWallet2.init(guardian2, [module.address]); + nonCompliantGuardian = await DumbContract.new(); + }); + + it("should let the owner add a Smart Contract guardian", async () => { + await manager.relay(module, "addGuardian", [wallet.address, guardianWallet2.address], wallet, [owner]); + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardianWallet2.address); + const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + const active = await module.isGuardianOrGuardianSigner(wallet.address, guardian2); + assert.isTrue(active, "second guardian owner should be active"); + assert.equal(count, 2, "2 guardians should be active"); + }); + + it("it should fail to add a non-compliant guardian", async () => { + await truffleAssert.reverts(module.addGuardian(wallet.address, nonCompliantGuardian.address, { from: owner }), + "SM: must be EOA/Argent wallet"); + }); + }); + }); + + describe("Revoking Guardians", () => { + beforeEach(async () => { + await module.addGuardian(wallet.address, guardian2, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian2); + const count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + assert.equal(count, 2, "2 guardians should be added"); + }); + + it("should revoke a guardian (blockchain transaction)", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + let active = await module.isGuardian(wallet.address, guardian1); + assert.isTrue(active, "the revoked guardian should still be active during the security period"); + assert.equal(count, 2, "the revoked guardian should go through a security period"); + + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardian1); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian1); + assert.isFalse(active, "the revoked guardian should no longer be active after the security period"); + assert.equal(count, 1, "the revoked guardian should be removed after the security period"); + }); + + it("should revoke a guardian (relayed tx)", async () => { + await manager.relay(module, "revokeGuardian", [wallet.address, guardian1], wallet, [owner]); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + let active = await module.isGuardian(wallet.address, guardian1); + assert.isTrue(active, "the revoked guardian should still be active during the security period"); + assert.equal(count, 2, "the revoked guardian should go through a security period"); + + await utils.increaseTime(30); + await manager.relay(module, "confirmGuardianRevokation", [wallet.address, guardian1], wallet, []); + + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian1); + assert.isFalse(active, "the revoked guardian should no longer be active after the security period"); + assert.equal(count, 1, "the revoked guardian should be removed after the security period"); + }); + + it("should not be able to revoke a nonexistent guardian", async () => { + await truffleAssert.reverts(module.revokeGuardian(wallet.address, nonowner, { from: owner }), + "SM: must be existing guardian"); + }); + + it("should not confirm a guardian revokation too early", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await truffleAssert.reverts(module.confirmGuardianRevokation(wallet.address, guardian1), + "SM: pending revoke not over"); + }); + + it("should not confirm a guardian revokation after two security periods (blockchain transaction)", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + + await utils.increaseTime(48); // 48 == 2 * security_period + await truffleAssert.reverts(module.confirmGuardianRevokation(wallet.address, guardian1), + "SM: pending revoke expired"); + }); + + it("should not be able to revoke a guardian twice", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await truffleAssert.reverts(module.revokeGuardian(wallet.address, guardian1, { from: owner }), + "SM: duplicate pending revoke"); + }); + + it("should revoke a guardian again after missing the confirmation window the first time (blockchain transaction)", async () => { + // first time + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + + await utils.increaseTime(48); // 48 == 2 * security_period + await truffleAssert.reverts(module.confirmGuardianRevokation(wallet.address, guardian1), + "SM: pending revoke expired"); + + // second time + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + let active = await module.isGuardian(wallet.address, guardian1); + assert.isTrue(active, "the revoked guardian should still be active during the security period"); + assert.equal(count, 2, "the revoked guardian should go through a security period"); + + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardian1); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + active = await module.isGuardian(wallet.address, guardian1); + assert.isFalse(active, "the revoked guardian should no longer be active after the security period"); + assert.equal(count, 1, "the revoked guardian should be removed after the security period"); + }); + + it("should add a guardian after a revoke (blockchain transaction)", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardian1); + let count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + assert.equal(count, 1, "there should be 1 guardian left"); + + await module.addGuardian(wallet.address, guardian3, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian3); + count = (await guardianStorage.guardianCount(wallet.address)).toNumber(); + assert.equal(count, 2, "there should be 2 guardians again"); + }); + + it("should be able to remove a guardian that is the last in the list", async () => { + await module.addGuardian(wallet.address, guardian3, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianAddition(wallet.address, guardian3); + let count = await guardianStorage.guardianCount(wallet.address); + assert.equal(count.toNumber(), 3, "there should be 3 guardians"); + + const guardians = await guardianStorage.getGuardians(wallet.address); + await module.revokeGuardian(wallet.address, guardians[2], { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardians[2]); + count = await guardianStorage.guardianCount(wallet.address); + assert.equal(count.toNumber(), 2, "there should be 2 guardians left"); + }); + }); + + describe("Cancelling Pending Guardians", () => { + it("owner should be able to cancel pending addition of guardian", async () => { + // Add guardian 2 and cancel its addition + await module.addGuardian(wallet.address, guardian2, { from: owner }); + await module.cancelGuardianAddition(wallet.address, guardian2, { from: owner }); + await utils.increaseTime(30); + await truffleAssert.reverts(module.confirmGuardianAddition(wallet.address, guardian2), + "SM: unknown pending addition"); + }); + + it("owner should not be able to cancel a nonexistent addition of a guardian request", async () => { + await truffleAssert.reverts(module.cancelGuardianAddition(wallet.address, guardian2, { from: owner }), + "SM: unknown pending addition"); + }); + + it("owner should be able to cancel pending revokation of guardian (relayed)", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await manager.relay(module, "cancelGuardianRevokation", [wallet.address, guardian1], wallet, [owner]); + + await utils.increaseTime(30); + await truffleAssert.reverts(module.confirmGuardianRevokation(wallet.address, guardian1), + "SM: unknown pending revoke"); + }); + + it("owner should be able to cancel pending revokation of guardian", async () => { + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await module.cancelGuardianRevokation(wallet.address, guardian1, { from: owner }); + await utils.increaseTime(30); + await truffleAssert.reverts(module.confirmGuardianRevokation(wallet.address, guardian1), + "SM: unknown pending revoke"); + }); + + it("owner should not be able to cancel a nonexistent pending revokation of guardian", async () => { + await truffleAssert.reverts(module.cancelGuardianRevokation(wallet.address, nonowner, { from: owner }), + "SM: unknown pending revoke"); + }); + }); + + describe("Guardian Storage", () => { + it("should not allow non modules to addGuardian", async () => { + await truffleAssert.reverts(guardianStorage.addGuardian(wallet.address, guardian4), + "TS: must be an authorized module to call this method"); + }); + + it("should not allow non modules to revokeGuardian", async () => { + await truffleAssert.reverts(guardianStorage.revokeGuardian(wallet.address, guardian1), + "TS: must be an authorized module to call this method"); + }); + }); +}); diff --git a/test/kyber.js b/test/kyber.js deleted file mode 100644 index 5601cdf6a..000000000 --- a/test/kyber.js +++ /dev/null @@ -1,78 +0,0 @@ -/* global artifacts */ -const KyberNetwork = artifacts.require("KyberNetworkTest"); -const ERC20 = artifacts.require("TestERC20"); - -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const { ETH_TOKEN, getBalance } = require("../utils/utilities.js"); - -const ERC20_SUPPLY = 10000000; -const ERC20_DECIMALS = 18; -const ERC20_RATE = 51 * 10 ** 13; // 1 ERC20 = 0.00051 ETH - -contract("KyberNetwork", (accounts) => { - const trader = accounts[1]; - - let erc20; - let kyber; - - beforeEach(async () => { - kyber = await KyberNetwork.new(); - erc20 = await ERC20.new([kyber.address], ERC20_SUPPLY, ERC20_DECIMALS); - await kyber.addToken(erc20.address, ERC20_RATE, ERC20_DECIMALS); - }); - - it("should return the expected rate for a token pair", async () => { - const rate = await kyber.getExpectedRate(erc20.address, ETH_TOKEN, 1); - assert.equal(rate[0], ERC20_RATE, "rate should be correct"); - }); - - it("should exchange ETH for ERC20", async () => { - const beforeERC20 = await erc20.balanceOf(trader); - const beforeETH = await getBalance(trader); - // trader should have no ERC20 - expect(beforeERC20).to.be.zero; - await kyber.trade(ETH_TOKEN, 10000, erc20.address, trader, - new BN("10000000000000000000000"), 1, "0x0000000000000000000000000000000000000000", { value: 10000, from: trader }); - const afterERC20 = await erc20.balanceOf(trader); - const afterETH = await getBalance(trader); - // trader should have exchanged 10000 wei - expect(beforeETH.sub(afterETH)).to.be.gt.BN(10000); - // trader should have received ERC20 - expect(afterERC20).to.be.gt.BN(0); - }); - - it("should exchange ERC20 for ETH", async () => { - // provision ERC20 to trader - await kyber.trade( - ETH_TOKEN, - new BN("1000000000000000000"), - erc20.address, - trader, - new BN("10000000000000000000000"), - 1, - "0x0000000000000000000000000000000000000000", - { value: new BN("1000000000000000000") }, - ); - const beforeERC20 = await erc20.balanceOf(trader); - const beforeETH = await getBalance(trader); - // trader should have some ERC20 - expect(beforeERC20).to.be.gt.BN(0); - // exchange ERC20 - const srcAmount = beforeERC20.div(new BN(2)); - await erc20.approve(kyber.address, srcAmount, { from: trader }); - await kyber.trade(erc20.address, srcAmount, ETH_TOKEN, trader, - new BN("10000000000000000000000"), 1, "0x0000000000000000000000000000000000000000", { from: trader }); - const afterERC20 = await erc20.balanceOf(trader); - const afterETH = await getBalance(trader); - // trader should have exchanged ERC20 - expect(beforeERC20.sub(afterERC20)).to.eq.BN(srcAmount); - // trader should have received wei - expect(afterETH.sub(beforeETH)).to.be.gt.BN(0); - }); -}); diff --git a/test/lockManager.js b/test/lockManager.js deleted file mode 100644 index 5ec9c5da6..000000000 --- a/test/lockManager.js +++ /dev/null @@ -1,219 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); - -const RelayerManager = artifacts.require("RelayerManager"); -const GuardianManager = artifacts.require("GuardianManager"); -const LockManager = artifacts.require("LockManager"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const Registry = artifacts.require("ModuleRegistry"); -const RecoveryManager = artifacts.require("RecoveryManager"); -const VersionManager = artifacts.require("VersionManager"); - -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const utilities = require("../utils/utilities.js"); -const RelayManager = require("../utils/relay-manager"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -contract("LockManager", (accounts) => { - const manager = new RelayManager(); - - const owner = accounts[1]; - const guardian1 = accounts[2]; - const nonguardian = accounts[3]; - - let guardianManager; - let guardianStorage; - let lockStorage; - let lockManager; - let recoveryManager; - let wallet; - let walletImplementation; - let relayerManager; - let versionManager; - - before(async () => { - walletImplementation = await BaseWallet.new(); - }); - - beforeEach(async () => { - const registry = await Registry.new(); - guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, 12); - lockManager = await LockManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24 * 5); - recoveryManager = await RecoveryManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 36, 24 * 5); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await versionManager.addVersion([ - guardianManager.address, - lockManager.address, - recoveryManager.address, - relayerManager.address, - ], []); - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - }); - - describe("(Un)Lock by EOA guardians", () => { - beforeEach(async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - const count = await guardianManager.guardianCount(wallet.address); - expect(count).to.be.eq.BN(1); - const isGuardian = await guardianManager.isGuardian(wallet.address, guardian1); - assert.isTrue(isGuardian); - const isLocked = await lockManager.isLocked(wallet.address); - assert.isFalse(isLocked); - }); - - it("should be locked/unlocked by EOA guardians (blockchain transaction)", async () => { - // lock - await lockManager.lock(wallet.address, { from: guardian1 }); - let state = await lockManager.isLocked(wallet.address); - assert.isTrue(state); - let releaseTime = await lockManager.getLock(wallet.address); - expect(releaseTime).to.be.gt.BN(0); - const guardianStorageLock = await guardianStorage.getLock(wallet.address); - const guardianStorageLocker = await guardianStorage.getLocker(wallet.address); - // legacy guardianStorage's lock should be unused - expect(guardianStorageLock).to.be.zero; - assert.isTrue(guardianStorageLocker === ethers.constants.AddressZero, "legacy guardianStorage's locker should be unused"); - // unlock - await lockManager.unlock(wallet.address, { from: guardian1 }); - state = await lockManager.isLocked(wallet.address); - assert.isFalse(state, "should be unlocked by guardian"); - releaseTime = await lockManager.getLock(wallet.address); - expect(releaseTime).to.be.zero; - }); - - it("should be locked/unlocked by EOA guardians (relayed transaction)", async () => { - await manager.relay(lockManager, "lock", [wallet.address], wallet, [guardian1]); - let state = await lockManager.isLocked(wallet.address); - assert.isTrue(state, "should be locked by guardian"); - - await manager.relay(lockManager, "unlock", [wallet.address], wallet, [guardian1]); - state = await lockManager.isLocked(wallet.address); - assert.isFalse(state, "should be unlocked by guardian"); - }); - - it("should fail to lock/unlock by non-guardian EOAs (blockchain transaction)", async () => { - await truffleAssert.reverts(lockManager.lock(wallet.address, { from: nonguardian }), "LM: must be guardian or feature"); - - await lockManager.lock(wallet.address, { from: guardian1 }); - const state = await lockManager.isLocked(wallet.address); - assert.isTrue(state, "should be locked by guardian1"); - - await truffleAssert.reverts(lockManager.unlock(wallet.address, { from: nonguardian }), "LM: must be guardian or feature"); - }); - }); - - describe("(Un)Lock by Smart Contract guardians", () => { - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - const guardianWallet = await BaseWallet.at(proxy.address); - - await guardianWallet.init(guardian1, [versionManager.address]); - await versionManager.upgradeWallet(guardianWallet.address, await versionManager.lastVersion(), { from: guardian1 }); - await guardianManager.addGuardian(wallet.address, guardianWallet.address, { from: owner }); - const count = await guardianManager.guardianCount(wallet.address); - expect(count).to.be.eq.BN(1); - const isGuardian = await guardianManager.isGuardian(wallet.address, guardianWallet.address); - assert.isTrue(isGuardian, "guardian1 should be a guardian of the wallet"); - const isLocked = await lockManager.isLocked(wallet.address); - assert.isFalse(isLocked, "should be unlocked by default"); - }); - - it("should be locked/unlocked by Smart Contract guardians (relayed transaction)", async () => { - await manager.relay(lockManager, "lock", [wallet.address], wallet, [guardian1]); - let state = await lockManager.isLocked(wallet.address); - assert.isTrue(state, "should be locked by guardian"); - - await manager.relay(lockManager, "unlock", [wallet.address], wallet, [guardian1]); - state = await lockManager.isLocked(wallet.address); - assert.isFalse(state, "should be unlocked by locker"); - }); - - it("should fail to lock/unlock by Smart Contract guardians when signer is not authorized (relayed transaction)", async () => { - await truffleAssert.reverts(manager.relay(lockManager, "lock", [wallet.address], wallet, [nonguardian]), "RM: Invalid signatures"); - }); - }); - - describe("Auto-unlock", () => { - it("should auto-unlock after lock period", async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - await lockManager.lock(wallet.address, { from: guardian1 }); - let state = await lockManager.isLocked(wallet.address); - assert.isTrue(state, "should be locked by guardian"); - let releaseTime = await lockManager.getLock(wallet.address); - assert.isTrue(releaseTime > 0, "releaseTime should be positive"); - - await utilities.increaseTime(125); // 24 * 5 + 5 - state = await lockManager.isLocked(wallet.address); - assert.isFalse(state, "should be unlocked by guardian"); - releaseTime = await lockManager.getLock(wallet.address); - expect(releaseTime).to.be.zero; - }); - }); - - describe("Unlocking wallets", () => { - beforeEach(async () => { - await guardianManager.addGuardian(wallet.address, guardian1, { from: owner }); - }); - - it("should not be able to unlock, an already unlocked wallet", async () => { - // lock - await lockManager.lock(wallet.address, { from: guardian1 }); - // unlock - await lockManager.unlock(wallet.address, { from: guardian1 }); - // try to unlock again - await truffleAssert.reverts(lockManager.unlock(wallet.address, { from: guardian1 }), - "LM: wallet must be locked"); - }); - - it("should not be able to unlock a wallet, locked by another feature", async () => { - // lock by putting the wallet in recovery mode - await manager.relay(recoveryManager, "executeRecovery", [wallet.address, accounts[5]], wallet, [guardian1]); - - // try to unlock - await truffleAssert.reverts(lockManager.unlock(wallet.address, { from: guardian1 }), - "LM: cannot unlock a wallet that was locked by another feature"); - }); - }); -}); diff --git a/test/locking.js b/test/locking.js new file mode 100644 index 000000000..994a8b19b --- /dev/null +++ b/test/locking.js @@ -0,0 +1,172 @@ +/* global artifacts */ +const ethers = require("ethers"); +const truffleAssert = require("truffle-assertions"); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const utils = require("../utils/utilities.js"); +const RelayManager = require("../utils/relay-manager"); + +const { expect } = chai; +chai.use(bnChai(BN)); + +contract("Locking", (accounts) => { + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const guardian2 = accounts[3]; + const nonguardian = accounts[4]; + const refundAddress = accounts[7]; + + const ZERO_ADDRESS = ethers.constants.AddressZero; + const SECURITY_PERIOD = 24; + const SECURITY_WINDOW = 12; + const LOCK_PERIOD = 24 * 5; + const RECOVERY_PERIOD = 36; + + let manager; + let module; + let wallet; + let factory; + let guardianStorage; + + before(async () => { + const registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + const transferStorage = await TransferStorage.new(); + const dappRegistry = await DappRegistry.new(0); + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + }); + + describe("(Un)Lock by EOA guardians", () => { + it("should be locked/unlocked by EOA guardians (blockchain transaction)", async () => { + // lock + await module.lock(wallet.address, { from: guardian1 }); + let state = await module.isLocked(wallet.address); + assert.isTrue(state); + let releaseTime = await module.getLock(wallet.address); + expect(releaseTime).to.be.gt.BN(0); + const guardianStorageLock = await guardianStorage.getLock(wallet.address); + // legacy guardianStorage's lock should be unused + expect(guardianStorageLock).to.be.zero; + // unlock + await module.unlock(wallet.address, { from: guardian1 }); + state = await module.isLocked(wallet.address); + assert.isFalse(state); + releaseTime = await module.getLock(wallet.address); + expect(releaseTime).to.be.zero; + }); + + it("should be locked/unlocked by EOA guardians (relayed transaction)", async () => { + await manager.relay(module, "lock", [wallet.address], wallet, [guardian1]); + let state = await module.isLocked(wallet.address); + assert.isTrue(state); + + await manager.relay(module, "unlock", [wallet.address], wallet, [guardian1]); + state = await module.isLocked(wallet.address); + assert.isFalse(state); + }); + + it("should fail to lock/unlock by non-guardian EOAs (blockchain transaction)", async () => { + await truffleAssert.reverts(module.lock(wallet.address, { from: nonguardian }), "SM: must be guardian/self"); + + await module.lock(wallet.address, { from: guardian1 }); + const state = await module.isLocked(wallet.address); + assert.isTrue(state); + + await truffleAssert.reverts(module.unlock(wallet.address, { from: nonguardian }), "SM: must be guardian/self"); + }); + }); + + describe("(Un)Lock by Smart Contract guardians", () => { + beforeEach(async () => { + const guardianWalletAddress = await utils.createWallet(factory.address, guardian1, [module.address], guardian2); + await module.addGuardian(wallet.address, guardianWalletAddress, { from: owner }); + }); + + it("should be locked/unlocked by Smart Contract guardians (relayed transaction)", async () => { + await manager.relay(module, "lock", [wallet.address], wallet, [guardian1]); + let state = await module.isLocked(wallet.address); + assert.isTrue(state); + + await manager.relay(module, "unlock", [wallet.address], wallet, [guardian1]); + state = await module.isLocked(wallet.address); + assert.isFalse(state); + }); + + it("should fail to lock/unlock by Smart Contract guardians when signer is not authorized (relayed transaction)", async () => { + await truffleAssert.reverts(manager.relay(module, "lock", [wallet.address], wallet, [nonguardian]), "RM: Invalid signatures"); + }); + }); + + describe("Auto-unlock", () => { + it("should auto-unlock after lock period", async () => { + await module.lock(wallet.address, { from: guardian1 }); + + await utils.increaseTime(125); // 24 * 5 + 5 + const state = await module.isLocked(wallet.address); + assert.isFalse(state); + const releaseTime = await module.getLock(wallet.address); + expect(releaseTime).to.be.zero; + }); + }); + + describe("Unlocking wallets", () => { + it("should not be able to unlock, an already unlocked wallet", async () => { + // lock + await module.lock(wallet.address, { from: guardian1 }); + // unlock + await module.unlock(wallet.address, { from: guardian1 }); + // try to unlock again + await truffleAssert.reverts(module.unlock(wallet.address, { from: guardian1 }), + "BM: wallet must be locked"); + }); + + it("should not be able to unlock a wallet, locked by another feature", async () => { + // lock by putting the wallet in recovery mode + await manager.relay(module, "executeRecovery", [wallet.address, accounts[5]], wallet, [guardian1]); + + // try to unlock + await truffleAssert.reverts(module.unlock(wallet.address, { from: guardian1 }), + "SM: cannot unlock"); + }); + }); +}); diff --git a/test/makerV2Manager_invest.js b/test/makerV2Manager_invest.js deleted file mode 100644 index 7ec5b9abd..000000000 --- a/test/makerV2Manager_invest.js +++ /dev/null @@ -1,168 +0,0 @@ -/* global artifacts */ - -const ethers = require("ethers"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const { deployMaker, deployUniswap, WAD, ETH_PER_DAI, ETH_PER_MKR } = require("../utils/defi-deployer"); -const RelayManager = require("../utils/relay-manager"); - -const Registry = artifacts.require("ModuleRegistry"); -const MakerV2Manager = artifacts.require("MakerV2Manager"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const MakerRegistry = artifacts.require("MakerRegistry"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); - -contract("MakerV2Invest", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const DAI_SENT = WAD.div(new BN(100000000)); - - let wallet; - let walletImplementation; - let relayerManager; - let versionManager; - let makerV2; - let sai; - let dai; - - before(async () => { - const m = await deployMaker(infrastructure); - [sai, dai] = [m.sai, m.dai]; - const { - migration, - pot, - jug, - vat, - gov, - } = m; - - const registry = await Registry.new(); - const guardianStorage = await GuardianStorage.new(); - const lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - const makerRegistry = await MakerRegistry.new(vat.address); - - // Deploy Uniswap - const uni = await deployUniswap(infrastructure, [gov, dai], [ETH_PER_MKR, ETH_PER_DAI]); - - makerV2 = await MakerV2Manager.new( - lockStorage.address, - migration.address, - pot.address, - jug.address, - makerRegistry.address, - uni.uniswapFactory.address, - versionManager.address, - ); - - walletImplementation = await BaseWallet.new(); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - await versionManager.addVersion([makerV2.address, relayerManager.address], []); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - await sai.mint(wallet.address, DAI_SENT.muln(20)); - await dai.mint(wallet.address, DAI_SENT.muln(20)); - }); - - async function exchangeWithPot({ toPot, relayed, all = false }) { - const walletBefore = (await dai.balanceOf(wallet.address)).add(await sai.balanceOf(wallet.address)); - const investedBefore = await makerV2.dsrBalance(wallet.address); - let method; - if (toPot) { - method = "joinDsr"; - } else if (all) { - method = "exitAllDsr"; - } else { - method = "exitDsr"; - } - const params = [wallet.address].concat(all ? [] : [DAI_SENT.toString()]); - - if (relayed) { - await manager.relay(makerV2, method, params, wallet, [owner]); - } else { - await makerV2[method](...params, { gasLimit: 2000000, from: owner }); - } - const walletAfter = (await dai.balanceOf(wallet.address)).add(await sai.balanceOf(wallet.address)); - const investedAfter = await makerV2.dsrBalance(wallet.address); - const deltaInvested = toPot ? investedAfter.sub(investedBefore) : investedBefore.sub(investedAfter); - const deltaWallet = toPot ? walletBefore.sub(walletAfter) : walletAfter.sub(walletBefore); - // DAI in DSR should have changed - expect(deltaInvested).to.be.gt.BN(0); - // DAI in wallet should have changed - expect(deltaWallet).to.be.gt.BN(0); - - if (all) { - // Pot should be emptied - expect(investedAfter).to.be.zero; - // DAI in wallet should have increased - expect(walletAfter).to.be.gt.BN(walletBefore); - } - } - - describe("Deposit", () => { - it("sends DAI to the pot (blockchain tx)", async () => { - await exchangeWithPot({ toPot: true, relayed: false }); - // do it a second time, when Vat authorisations have already been granted - await exchangeWithPot({ toPot: true, relayed: false }); - }); - - it("sends DAI to the pot (relayed tx)", async () => { - await exchangeWithPot({ toPot: true, relayed: true }); - // do it a second time, when Vat authorisations have already been granted - await exchangeWithPot({ toPot: true, relayed: true }); - }); - }); - - describe("Withdraw", () => { - beforeEach(async () => { - await exchangeWithPot({ toPot: true, relayed: false }); - }); - - it("withdraw DAI from the pot (blockchain tx)", async () => { - await exchangeWithPot({ toPot: false, relayed: false }); - }); - - it("withdraw DAI from the pot (relayed tx)", async () => { - await exchangeWithPot({ toPot: false, relayed: true }); - }); - - it("withdraw ALL DAI from the pot (blockchain tx)", async () => { - await exchangeWithPot({ toPot: false, relayed: false, all: true }); - }); - - it("withdraw ALL DAI from the pot (relayed tx)", async () => { - await exchangeWithPot({ toPot: false, relayed: true, all: true }); - }); - }); -}); diff --git a/test/makerV2Manager_loan.js b/test/makerV2Manager_loan.js deleted file mode 100644 index 78fe17536..000000000 --- a/test/makerV2Manager_loan.js +++ /dev/null @@ -1,781 +0,0 @@ -/* global artifacts */ - -const truffleAssert = require("truffle-assertions"); -const ethers = require("ethers"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const utils = require("../utils/utilities.js"); -const { ETH_TOKEN } = require("../utils/utilities.js"); -const { deployMaker, deployUniswap, RAY, ETH_PER_DAI, ETH_PER_MKR } = require("../utils/defi-deployer"); - -const { formatBytes32String } = ethers.utils; -const { AddressZero } = ethers.constants; -const RelayManager = require("../utils/relay-manager"); - -const GemJoin = artifacts.require("GemJoin"); -const Registry = artifacts.require("ModuleRegistry"); -const MakerV2Manager = artifacts.require("MakerV2Manager"); -const UpgradedMakerV2Manager = artifacts.require("TestUpgradedMakerV2Manager"); -const MakerRegistry = artifacts.require("MakerRegistry"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const FakeWallet = artifacts.require("FakeWallet"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const TransferStorage = artifacts.require("TransferStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const TransferManager = artifacts.require("TransferManager"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); -const BadFeature = artifacts.require("TestFeature"); - -contract("MakerV2Loan", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const owner2 = accounts[2]; - - let sai; - let dai; - let gov; - let bat; - let weth; - let vat; - let batJoin; - let cdpManager; - let pot; - let jug; - let migration; - let transferManager; - let guardianStorage; - let lockStorage; - let makerV2; - let wallet; - let walletImplementation; - let walletAddress; - let makerRegistry; - let uniswapFactory; - let relayerManager; - let versionManager; - - before(async () => { - // Deploy Maker - const mk = await deployMaker(infrastructure); - [sai, dai, gov, bat, weth, vat, batJoin, cdpManager, pot, jug, migration] = [ - mk.sai, mk.dai, mk.gov, mk.bat, mk.weth, mk.vat, mk.batJoin, mk.cdpManager, mk.pot, mk.jug, mk.migration, - ]; - const { wethJoin } = mk; - - // Deploy Uniswap - const uni = await deployUniswap(infrastructure, [gov, dai], [ETH_PER_MKR, ETH_PER_DAI]); - uniswapFactory = uni.uniswapFactory; - - // Deploy MakerV2Manager - const registry = await Registry.new(); - guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - makerRegistry = await MakerRegistry.new(vat.address); - await makerRegistry.addCollateral(wethJoin.address); - makerV2 = await MakerV2Manager.new( - lockStorage.address, - migration.address, - pot.address, - jug.address, - makerRegistry.address, - uniswapFactory.address, - versionManager.address, - ); - - // Deploy TransferManager - const transferStorage = await TransferStorage.new(); - const limitStorage = await LimitStorage.new(); - const tokenPriceRegistry = await TokenPriceRegistry.new(); - transferManager = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address, - 3600, - 3600, - 10000, - AddressZero, - AddressZero, - ); - - walletImplementation = await BaseWallet.new(); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - await versionManager.addVersion([ - makerV2.address, - transferManager.address, - relayerManager.address, - ], []); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - walletAddress = wallet.address; - await wallet.send(web3.utils.toWei("2.0")); - await dai.mint(walletAddress, web3.utils.toWei("10")); - }); - - async function getTestAmounts(tokenAddress) { - const tokenAddress_ = (tokenAddress === ETH_TOKEN) ? weth.address : tokenAddress; - const { ilk } = await makerRegistry.collaterals(tokenAddress_); - const { spot, dust } = await vat.ilks(ilk); - const daiAmount = dust.div(RAY); - const collateralAmount = dust.div(spot).muln(2); - return { daiAmount, collateralAmount }; - } - - async function testOpenLoan({ - collateralAmount, daiAmount, relayed, collateral = { address: ETH_TOKEN }, - }) { - const beforeCollateral = (collateral.address === ETH_TOKEN) - ? await utils.getBalance(walletAddress) - : await collateral.balanceOf(walletAddress); - - const beforeDAI = await dai.balanceOf(walletAddress); - const beforeDAISupply = await dai.totalSupply(); - - const params = [walletAddress, collateral.address, collateralAmount.toString(), dai.address, daiAmount.toString()]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(makerV2, "openLoan", params, wallet, [owner]); - const { success } = utils.parseRelayReceipt(txReceipt); - assert.isTrue(success, "Relayed tx should succeed"); - } else { - const tx = await makerV2.openLoan(...params, { gasLimit: 2000000, from: owner }); - txReceipt = tx.receipt; - } - const eventLoanOpened = await utils.getEvent(txReceipt, makerV2, "LoanOpened"); - const loanId = eventLoanOpened.args._loanId; - assert.isDefined(loanId, "Loan ID should be defined"); - - const afterCollateral = (collateral.address === ETH_TOKEN) - ? await utils.getBalance(walletAddress) - : await collateral.balanceOf(walletAddress); - const afterDAI = await dai.balanceOf(walletAddress); - const afterDAISupply = await dai.totalSupply(); - - // wallet should have ${collateralAmount} less collateral (relayed: ${relayed}) - expect(beforeCollateral.sub(afterCollateral)).to.eq.BN(collateralAmount); - // wallet should have ${daiAmount} more DAI (relayed: ${relayed}) - expect(afterDAI.sub(beforeDAI)).to.eq.BN(daiAmount); - // ${daiAmount} DAI should have been minted (relayed: ${relayed}) - expect(afterDAISupply.sub(beforeDAISupply)).to.eq.BN(daiAmount); - - return loanId; - } - - describe("Open Loan", () => { - let daiAmount; - let collateralAmount; - before(async () => { - const testAmounts = await getTestAmounts(ETH_TOKEN); - daiAmount = testAmounts.daiAmount; - collateralAmount = testAmounts.collateralAmount; - }); - - it("should open a Loan (blockchain tx)", async () => { - await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - }); - - it("should open a Loan (relayed tx)", async () => { - await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - }); - - it("should open>close>reopen a Loan (blockchain tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await makerV2.closeLoan(walletAddress, loanId, { gasLimit: 4500000, from: owner }); - await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - }); - - it("should open>close>reopen a Loan (relayed tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - await makerV2.closeLoan(walletAddress, loanId, { gasLimit: 4500000, from: owner }); - await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - }); - - it("should not open a loan for the wrong debt token", async () => { - await truffleAssert.reverts( - makerV2.openLoan(walletAddress, ETH_TOKEN, collateralAmount, sai.address, daiAmount, { from: owner }), - "MV2: debt token not DAI", - ); - }); - - it("should not open a loan for an unsupported collateral token", async () => { - await truffleAssert.reverts( - makerV2.openLoan(walletAddress, sai.address, collateralAmount, dai.address, daiAmount, { from: owner }), - "MV2: unsupported collateral", - ); - }); - }); - - async function testChangeCollateral({ - loanId, collateralAmount, add, relayed, collateral = { address: ETH_TOKEN }, makerV2Manager = makerV2, - }) { - const beforeCollateral = (collateral.address === ETH_TOKEN) - ? await utils.getBalance(walletAddress) - : await collateral.balanceOf(walletAddress); - - const method = add ? "addCollateral" : "removeCollateral"; - const params = [wallet.address, loanId, collateral.address, collateralAmount.toString()]; - if (relayed) { - const txR = await manager.relay(makerV2Manager, method, params, wallet, [owner]); - const txExecutedEvent = await utils.getEvent(txR, relayerManager, "TransactionExecuted"); - assert.isTrue(txExecutedEvent.args.success, "Relayed tx should succeed"); - } else { - await makerV2Manager[method](...params, { gasLimit: 2000000, from: owner }); - } - - const afterCollateral = (collateral.address === ETH_TOKEN) - ? await utils.getBalance(walletAddress) - : await collateral.balanceOf(walletAddress); - - const x = add ? -1 : 1; - const expectedCollateralChange = collateralAmount.mul(new BN(x)); - // wallet collateral should have changed by ${expectedCollateralChange} (relayed: ${relayed}) - expect(afterCollateral.sub(beforeCollateral)).to.eq.BN(expectedCollateralChange); - } - - describe("Add/Remove Collateral", () => { - let daiAmount; - let collateralAmount; - - before(async () => { - const testAmounts = await getTestAmounts(ETH_TOKEN); - daiAmount = testAmounts.daiAmount; - collateralAmount = testAmounts.collateralAmount; - }); - - it("should add collateral (blockchain tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await testChangeCollateral({ - loanId, collateralAmount: new BN(web3.utils.toWei("0.010")), add: true, relayed: false, - }); - }); - - it("should add collateral (relayed tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - await testChangeCollateral({ - loanId, collateralAmount: new BN(web3.utils.toWei("0.010")), add: true, relayed: true, - }); - }); - - it("should not add collateral for the wrong loan owner", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - const proxy = await Proxy.new(walletImplementation.address); - const wallet2 = await BaseWallet.at(proxy.address); - - await wallet2.init(owner2, [versionManager.address]); - await truffleAssert.reverts( - makerV2.addCollateral(wallet2.address, loanId, ETH_TOKEN, web3.utils.toWei("0.010"), { from: owner2 }), - "MV2: unauthorized loanId", - ); - }); - - it("should remove collateral (blockchain tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await testChangeCollateral({ - loanId, collateralAmount: new BN(web3.utils.toWei("0.010")), add: false, relayed: false, - }); - }); - - it("should remove collateral (relayed tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - await testChangeCollateral({ - loanId, collateralAmount: new BN(web3.utils.toWei("0.010")), add: false, relayed: true, - }); - }); - - it("should not remove collateral with invalid collateral amount", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await truffleAssert.reverts( - makerV2.removeCollateral(walletAddress, loanId, ETH_TOKEN, new BN(2).pow(new BN(255)), { from: owner }), - "MV2: int overflow", - ); - }); - - it("should not remove collateral for the wrong loan owner", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - const proxy = await Proxy.new(walletImplementation.address); - const wallet2 = await BaseWallet.at(proxy.address); - - await wallet2.init(owner2, [versionManager.address]); - await truffleAssert.reverts( - makerV2.removeCollateral(wallet2.address, loanId, ETH_TOKEN, web3.utils.toWei("0.010"), { from: owner2 }), - "MV2: unauthorized loanId", - ); - }); - }); - - async function testChangeDebt({ - loanId, daiAmount, add, relayed, - }) { - const beforeDAI = await dai.balanceOf(wallet.address); - const beforeETH = await utils.getBalance(wallet.address); - const method = add ? "addDebt" : "removeDebt"; - const params = [wallet.address, loanId, dai.address, daiAmount.toString()]; - if (relayed) { - const txR = await manager.relay(makerV2, method, params, { address: walletAddress }, [owner]); - const txExecutedEvent = await utils.getEvent(txR, relayerManager, "TransactionExecuted"); - assert.isTrue(txExecutedEvent.args.success, "Relayed tx should succeed"); - } else { - await makerV2[method](...params, { gasLimit: 2000000, from: owner }); - } - const afterDAI = await dai.balanceOf(wallet.address); - const afterETH = await utils.getBalance(wallet.address); - if (add) { - // wallet DAI should have increased by ${daiAmount.toString()} (relayed: ${relayed}) - expect(afterDAI.sub(beforeDAI)).to.eq.BN(daiAmount); - } else { - assert.isTrue( - afterDAI.lt(beforeDAI) || afterETH.lt(beforeETH), - `wallet DAI or ETH should have decreased (relayed: ${relayed})`, - ); - } - } - - describe("Increase Debt", () => { - let daiAmount; - let collateralAmount; - - before(async () => { - const testAmounts = await getTestAmounts(ETH_TOKEN); - daiAmount = testAmounts.daiAmount; - collateralAmount = testAmounts.collateralAmount; - }); - - it("should increase debt (blockchain tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await testChangeDebt({ - loanId, daiAmount: web3.utils.toWei("0.5"), add: true, relayed: false, - }); - }); - - it("should increase debt (relayed tx)", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - await testChangeDebt({ - loanId, daiAmount: web3.utils.toWei("0.5"), add: true, relayed: true, - }); - }); - - it("should not increase debt for the wrong loan owner", async () => { - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - const proxy = await Proxy.new(walletImplementation.address); - const wallet2 = await BaseWallet.at(proxy.address); - - await wallet2.init(owner2, [versionManager.address]); - await truffleAssert.reverts( - makerV2.addDebt(wallet2.address, loanId, ETH_TOKEN, web3.utils.toWei("0.010"), { from: owner2 }), - "MV2: unauthorized loanId", - ); - }); - }); - - async function testRepayDebt({ relayed }) { - const { collateralAmount, daiAmount: daiAmount_ } = await getTestAmounts(ETH_TOKEN); - const daiAmount = daiAmount_.add(new BN(web3.utils.toWei("0.3"))); - - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed }); - await utils.increaseTime(3); // wait 3 seconds - const beforeDAI = await dai.balanceOf(wallet.address); - const beforeETH = await utils.getBalance(wallet.address); - await testChangeDebt({ - loanId, daiAmount: web3.utils.toWei("0.2"), add: false, relayed, - }); - - const afterDAI = await dai.balanceOf(wallet.address); - const afterETH = await utils.getBalance(wallet.address); - - assert.isTrue(afterDAI.lt(beforeDAI) && afterETH.eq(beforeETH), "should have less DAI"); - } - - describe("Repay Debt", () => { - it("should repay debt (blockchain tx)", async () => { - await testRepayDebt({ relayed: false }); - }); - - it("should repay debt (relayed tx)", async () => { - await testRepayDebt({ relayed: true }); - }); - - it("should not repay debt when only dust left", async () => { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await truffleAssert.reverts( - makerV2.removeDebt(walletAddress, loanId, dai.address, daiAmount.subn(1), { from: owner }), - "MV2: repay less or full", - ); - }); - - it("should not repay debt for the wrong loan owner", async () => { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - const proxy = await Proxy.new(walletImplementation.address); - const wallet2 = await BaseWallet.at(proxy.address); - - await wallet2.init(owner2, [versionManager.address]); - await truffleAssert.reverts( - makerV2.removeDebt(wallet2.address, loanId, ETH_TOKEN, web3.utils.toWei("0.010"), { from: owner2 }), - "MV2: unauthorized loanId", - ); - }); - }); - - async function testCloseLoan({ relayed }) { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed }); - // give some ETH to the wallet to be used for repayment - await wallet.send(collateralAmount.muln(2), { from: owner }); - await utils.increaseTime(3); // wait 3 seconds - const beforeDAI = await dai.balanceOf(wallet.address); - const method = "closeLoan"; - const params = [wallet.address, loanId]; - if (relayed) { - const txR = await manager.relay(makerV2, method, params, { address: walletAddress }, [owner]); - const txExecutedEvent = await utils.getEvent(txR, relayerManager, "TransactionExecuted"); - assert.isTrue(txExecutedEvent.args.success, "Relayed tx should succeed"); - } else { - await makerV2[method](...params, { gasLimit: 3000000, from: owner }); - } - const afterDAI = await dai.balanceOf(wallet.address); - // should have spent some DAI - expect(afterDAI).to.be.lt.BN(beforeDAI); - } - - describe("Close Vaults", () => { - it("should close a vault (blockchain tx)", async () => { - await testCloseLoan({ relayed: false }); - }); - - it("should close a vault (relayed tx)", async () => { - await testCloseLoan({ relayed: true }); - }); - - it("should not close a vault for the wrong loan owner", async () => { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - const loanId = await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - const proxy = await Proxy.new(walletImplementation.address); - const wallet2 = await BaseWallet.at(proxy.address); - - await wallet2.init(owner2, [versionManager.address]); - await truffleAssert.reverts( - makerV2.closeLoan(wallet2.address, loanId, { from: owner2 }), - "MV2: unauthorized loanId", - ); - }); - }); - - describe("MakerRegistry", () => { - it("should add a new collateral token", async () => { - const numCollateralBefore = (await makerRegistry.getCollateralTokens()).length; - await makerRegistry.addCollateral(batJoin.address); - const numCollateralAfter = (await makerRegistry.getCollateralTokens()).length; - assert.equal(numCollateralAfter, numCollateralBefore + 1, "A new collateral should have been added"); - await makerRegistry.removeCollateral(bat.address); // cleanup - }); - - it("should open a loan with a newly added collateral token", async () => { - await makerRegistry.addCollateral(batJoin.address); - const { daiAmount, collateralAmount } = await getTestAmounts(bat.address); - await bat.mint(walletAddress, collateralAmount); - await testOpenLoan({ - collateralAmount, daiAmount, collateral: bat, relayed: false, - }); - await makerRegistry.removeCollateral(bat.address); // cleanup - }); - - it("should not add a collateral when Join is not in the Vat", async () => { - const badJoin = await GemJoin.new(vat.address, formatBytes32String("BAD"), bat.address); - await truffleAssert.reverts(makerRegistry.addCollateral(badJoin.address), "MR: _joinAdapter not authorised in vat"); - }); - - it("should not add a duplicate collateral", async () => { - await makerRegistry.addCollateral(batJoin.address); - await truffleAssert.reverts(makerRegistry.addCollateral(batJoin.address), "MR: collateral already added"); - await makerRegistry.removeCollateral(bat.address); // cleanup - }); - - it("should remove a collateral", async () => { - const numCollateralBefore = (await makerRegistry.getCollateralTokens()).length; - await makerRegistry.addCollateral(batJoin.address); - await makerRegistry.removeCollateral(bat.address); - const numCollateralAfter = (await makerRegistry.getCollateralTokens()).length; - assert.equal(numCollateralAfter, numCollateralBefore, "The added collateral should have been removed"); - }); - - it("should not remove a non-existing collateral", async () => { - await truffleAssert.reverts(makerRegistry.removeCollateral(bat.address), "MR: collateral does not exist"); - }); - }); - - describe("Acquiring a wallet's vault", () => { - async function testAcquireVault({ relayed }) { - // Create the vault with `owner` as owner - const { ilk } = await makerRegistry.collaterals(weth.address); - const tx = await cdpManager.open(ilk, owner, { from: owner }); - const txNewCdpEvent = await utils.getEvent(tx.receipt, cdpManager, "NewCdp"); - const vaultId = txNewCdpEvent.args.cdp; - // Transfer the vault to the wallet - await cdpManager.give(vaultId, walletAddress, { from: owner }); - // Transfer the vault to the feature - const loanId = utils.numberToBytes32(vaultId); - - const method = "acquireLoan"; - const params = [walletAddress, loanId]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(makerV2, method, params, { address: walletAddress }, [owner]); - const { success } = await utils.parseRelayReceipt(txReceipt); - assert.isTrue(success, "Relayed tx should succeed"); - } else { - const tx1 = await makerV2[method](...params, { gasLimit: 1000000, from: owner }); - txReceipt = tx1.receipt; - } - await utils.hasEvent(txReceipt, makerV2, "LoanAcquired"); - - // The loanId held by the MakerV2Manager will be different from the transferred vault id, in case the latter was merged into an existing vault - const featureLoanId = await makerV2.loanIds(walletAddress, ilk); - // Add some collateral and debt - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - - await testChangeCollateral({ - loanId: featureLoanId, collateralAmount, add: true, relayed, makerV2, - }); - await testChangeDebt({ - loanId: featureLoanId, daiAmount, add: true, relayed, - }); - } - - it("should transfer a vault from a wallet to the feature (blockchain tx)", async () => { - await testAcquireVault({ relayed: false }); - }); - - it("should transfer a vault from a wallet to the feature (relayed tx)", async () => { - await testAcquireVault({ relayed: true }); - }); - - it("should not transfer a vault that is not owned by the wallet", async () => { - // Create the vault with `owner` as owner - const { ilk } = await makerRegistry.collaterals(weth.address); - const tx = await cdpManager.open(ilk, owner, { from: owner }); - const txNewCdpEvent = await utils.getEvent(tx.receipt, cdpManager, "NewCdp"); - const vaultId = txNewCdpEvent.args.cdp; - const loanId = utils.numberToBytes32(vaultId); - // We are NOT transferring the vault from the owner to the wallet - await truffleAssert.reverts( - makerV2.acquireLoan(walletAddress, loanId, { from: owner }), "MV2: wrong vault owner", - ); - }); - - it("should not transfer a vault that is not given to the feature", async () => { - // Deploy a fake wallet - const fakeWallet = await FakeWallet.new(false, AddressZero, 0, "0x00"); - await fakeWallet.init(owner, [versionManager.address]); - const lastVersion = await versionManager.lastVersion(); - await versionManager.upgradeWallet(fakeWallet.address, lastVersion.toString(), { from: owner }); - // Create the vault with `owner` as owner - const { ilk } = await makerRegistry.collaterals(weth.address); - const tx = await cdpManager.open(ilk, owner, { from: owner }); - const txNewCdpEvent = await utils.getEvent(tx.receipt, cdpManager, "NewCdp"); - const vaultId = txNewCdpEvent.args.cdp; - const loanId = utils.numberToBytes32(vaultId); - - // Transfer the vault to the fake wallet - await cdpManager.give(vaultId, fakeWallet.address, { from: owner }); - - await truffleAssert.reverts( - makerV2.acquireLoan(fakeWallet.address, loanId, { from: owner }), "MV2: failed give", - ); - }); - - it("should transfer (merge) a vault when already holding a vault in the feature (blockchain tx)", async () => { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - await testOpenLoan({ collateralAmount, daiAmount, relayed: false }); - await testAcquireVault({ relayed: false }); - }); - - it("should transfer (merge) a vault when already holding a vault in the feature (relayed tx)", async () => { - const { collateralAmount, daiAmount } = await getTestAmounts(ETH_TOKEN); - await testOpenLoan({ collateralAmount, daiAmount, relayed: true }); - await testAcquireVault({ relayed: true }); - }); - - it("should not allow reentrancy in acquireLoan", async () => { - // Deploy a fake wallet capable of reentrancy - const acquireLoanCallData = makerV2.contract.methods.acquireLoan(AddressZero, utils.numberToBytes32(0)).encodeABI(); - const fakeWallet = await FakeWallet.new(true, makerV2.address, 0, acquireLoanCallData); - await fakeWallet.init(owner, [versionManager.address]); - const lastVersion = await versionManager.lastVersion(); - await versionManager.upgradeWallet(fakeWallet.address, lastVersion.toString(), { from: owner }); - // Create the vault with `owner` as owner - const { ilk } = await makerRegistry.collaterals(weth.address); - const tx = await cdpManager.open(ilk, owner, { from: owner }); - const txNewCdpEvent = await utils.getEvent(tx.receipt, cdpManager, "NewCdp"); - const vaultId = txNewCdpEvent.args.cdp; - const loanId = utils.numberToBytes32(vaultId); - // Transfer the vault to the fake wallet - await cdpManager.give(vaultId, fakeWallet.address, { from: owner }); - await truffleAssert.reverts( - makerV2.acquireLoan(fakeWallet.address, loanId, { from: owner }), "MV2: reentrant call", - ); - }); - }); - - describe("Upgrade of MakerV2Manager", () => { - let upgradedMakerV2; - let daiAmount; - let collateralAmount; - - beforeEach(async () => { - // Generate test amounts - const testAmounts = await getTestAmounts(ETH_TOKEN); - daiAmount = testAmounts.daiAmount; - collateralAmount = testAmounts.collateralAmount; - - // Deploy and register the upgraded MakerV2 feature - upgradedMakerV2 = await UpgradedMakerV2Manager.new( - lockStorage.address, - migration.address, - pot.address, - jug.address, - makerRegistry.address, - uniswapFactory.address, - makerV2.address, - versionManager.address - ); - - // Adding BAT to the registry of supported collateral tokens - if (!(await makerRegistry.collaterals(bat.address)).exists) { - await makerRegistry.addCollateral(batJoin.address); - } - }); - - async function testUpgradeModule({ relayed, withBatVault = false }) { - // Open a WETH vault with the old MakerV2 feature - const loanId1 = await testOpenLoan({ collateralAmount, daiAmount, relayed }); - let loanId2; - if (withBatVault) { - // Open a BAT vault with the old MakerV2 feature - const batTestAmounts = await getTestAmounts(bat.address); - await bat.mint(walletAddress, batTestAmounts.collateralAmount.add(new BN(web3.utils.toWei("0.01")))); - loanId2 = await testOpenLoan({ - collateralAmount: batTestAmounts.collateralAmount, - daiAmount: batTestAmounts.daiAmount, - collateral: bat, - relayed, - }); - } - - // Add the upgraded feature - await versionManager.addVersion([ - upgradedMakerV2.address, - transferManager.address, - relayerManager.address, - ], [upgradedMakerV2.address]); - - const lastVersion = await versionManager.lastVersion(); - const params = [walletAddress, lastVersion.toNumber()]; - if (relayed) { - const txR = await manager.relay(versionManager, "upgradeWallet", params, wallet, [owner]); - const { success } = utils.parseRelayReceipt(txR); - assert.isTrue(success, "Relayed tx should succeed"); - } else { - await versionManager.upgradeWallet(...params, { gasLimit: 2000000, from: owner }); - } - // Make sure that the vaults can be manipulated from the upgraded feature - await testChangeCollateral({ - loanId: loanId1, - collateralAmount: new BN(web3.utils.toWei("0.010")), - add: true, - relayed, - makerV2Manager: upgradedMakerV2, - }); - await upgradedMakerV2.closeLoan(walletAddress, loanId1, { gasLimit: 4500000, from: owner }); - - if (withBatVault) { - await testChangeCollateral({ - loanId: loanId2, - collateralAmount: new BN(web3.utils.toWei("0.010")), - add: true, - relayed, - collateral: bat, - makerV2Manager: upgradedMakerV2, - }); - await upgradedMakerV2.closeLoan(walletAddress, loanId2, { gasLimit: 4500000, from: owner }); - } - - // reset the last version to the default bundle - await versionManager.addVersion([ - makerV2.address, - transferManager.address, - relayerManager.address, - ], []); - } - - it("should move a vault after a feature upgrade (blockchain tx)", async () => { - await testUpgradeModule({ relayed: false }); - }); - - it("should move a vault after a feature upgrade (relayed tx)", async () => { - await testUpgradeModule({ relayed: true }); - }); - - it("should move 2 vaults after a feature upgrade (blockchain tx)", async () => { - await testUpgradeModule({ withBatVault: true, relayed: false }); - }); - - it("should move 2 vaults after a feature upgrade (relayed tx)", async () => { - await testUpgradeModule({ withBatVault: true, relayed: true }); - }); - - it("should not allow non-feature to give vault", async () => { - await truffleAssert.reverts(makerV2.giveVault(walletAddress, formatBytes32String(""), { from: owner }), "BF: must be a wallet feature"); - }); - - it("should not allow (fake) feature to give unowned vault", async () => { - // Deploy a (fake) bad feature - const badFeature = await BadFeature.new(lockStorage.address, versionManager.address, 0); - - // Add the bad feature to the wallet - await versionManager.addVersion([ - badFeature.address, - transferManager.address, - relayerManager.address, - ], []); - const lastVersion = await versionManager.lastVersion(); - await versionManager.upgradeWallet(walletAddress, lastVersion, { gasLimit: 2000000, from: owner }); - // Use the bad module to attempt a bad giveVault call - const callData = makerV2.contract.methods.giveVault(walletAddress, utils.numberToBytes32(666)).encodeABI(); - await truffleAssert.reverts(badFeature.callContract(makerV2.address, 0, callData, { from: owner }), "MV2: unauthorized loanId"); - }); - }); -}); diff --git a/test/multiCallHelper.js b/test/multiCallHelper.js new file mode 100644 index 000000000..67e1ad5ea --- /dev/null +++ b/test/multiCallHelper.js @@ -0,0 +1,174 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +const MultiCallHelper = artifacts.require("MultiCallHelper"); + +const AaveV2Filter = artifacts.require("AaveV2Filter"); +const AaveV2LendingPool = artifacts.require("AaveV2LendingPoolMock"); +const ERC20 = artifacts.require("TestERC20"); + +const { encodeTransaction, addTrustedContact, createWallet } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const MAX_UINT = (new BN(2)).pow(new BN(256)).sub(new BN(1)); + +contract("MultiCallHelper", (accounts) => { + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const trustedContact = accounts[4]; + const authorisedDapp = accounts[5]; + const unknownAddress = accounts[6]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let dappRegistry; + let filter; + let helper; + let tokenA; + let aave; + + before(async () => { + // Deploy test tokens + tokenA = await ERC20.new([infrastructure], web3.utils.toWei("1000"), 18); + + // Deploy AaveV2 + aave = await AaveV2LendingPool.new([tokenA.address]); + + // Deploy and fund UniswapV2 + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + const weth = await WETH.new(); + const uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + filter = await AaveV2Filter.new(); + await dappRegistry.addDapp(0, aave.address, filter.address); + + await dappRegistry.addDapp(0, authorisedDapp, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + helper = await MultiCallHelper.new(transferStorage.address, dappRegistry.address); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + await wallet.send(new BN("1000000000000000000")); + + await addTrustedContact(wallet, trustedContact, module, SECURITY_PERIOD); + }); + + describe("multicall helper", () => { + it("should return true when the multicall is authorised", async () => { + const transactions = []; + transactions.push(encodeTransaction(trustedContact, 10, ZERO_BYTES)); + transactions.push(encodeTransaction(authorisedDapp, 10, ZERO_BYTES)); + + const authorised = await helper.isMultiCallAuthorised(wallet.address, transactions); + assert.isTrue(authorised); + }); + + it("should return true when the multicall is authorised for 0 address wallet", async () => { + const transactions = []; + transactions.push(encodeTransaction(authorisedDapp, 10, ZERO_BYTES)); + + const authorised = await helper.isMultiCallAuthorised(ZERO_ADDRESS, transactions); + assert.isTrue(authorised); + }); + + it("should return false when the multicall is not authorised", async () => { + const transactions = []; + transactions.push(encodeTransaction(trustedContact, 10, ZERO_BYTES)); + transactions.push(encodeTransaction(unknownAddress, 10, ZERO_BYTES)); + + const authorised = await helper.isMultiCallAuthorised(wallet.address, transactions); + assert.isFalse(authorised); + }); + + it("should return the correct registry ID", async () => { + const transactions = []; + transactions.push(encodeTransaction(trustedContact, 10, ZERO_BYTES)); + transactions.push(encodeTransaction(authorisedDapp, 10, ZERO_BYTES)); + transactions.push(encodeTransaction(unknownAddress, 10, ZERO_BYTES)); + + const registryId = await helper.multiCallAuthorisation(wallet.address, transactions); + assert.equal(registryId[0], 256, "should be 256 for trusted contacts"); + assert.equal(registryId[1], 0, "should be the correct registry"); + assert.isTrue(MAX_UINT.eq(registryId[2]), "should be MAX_UINT when not trusted"); + }); + + it("should return true when the multicall is authorised for a registry", async () => { + await tokenA.mint(wallet.address, web3.utils.toWei("1000")); + const transactions = []; + transactions.push(encodeTransaction(tokenA.address, 0, tokenA.contract.methods.approve(aave.address, 100).encodeABI())); + transactions.push(encodeTransaction(aave.address, 0, aave.contract.methods.deposit(tokenA.address, 100, wallet.address, "").encodeABI())); + + const authorised = await helper.isAuthorisedInRegistry(wallet.address, transactions, 0); + assert.isTrue(authorised); + }); + + it("should return false when the multicall is not authorised for a registry", async () => { + await tokenA.mint(wallet.address, web3.utils.toWei("1000")); + const transactions = []; + transactions.push(encodeTransaction(tokenA.address, 0, tokenA.contract.methods.approve(aave.address, 100).encodeABI())); + transactions.push(encodeTransaction(aave.address, 0, aave.contract.methods.deposit(tokenA.address, 100, wallet.address, "").encodeABI())); + transactions.push(encodeTransaction(tokenA.address, 0, tokenA.contract.methods.approve(unknownAddress, 100).encodeABI())); + + const authorised = await helper.isAuthorisedInRegistry(wallet.address, transactions, 0); + assert.isFalse(authorised); + }); + }); +}); diff --git a/test/multisig.js b/test/multisig.js index e66413320..92647ae35 100644 --- a/test/multisig.js +++ b/test/multisig.js @@ -243,7 +243,7 @@ contract("MultiSigWallet", (accounts) => { it("should fail with the wrong nonce", async () => { const nonceOffset = 1; - await executeSendFailure([owner1, owner2], nonceOffset, true, false, "MSW: Badly ordered signatures"); + await executeSendFailure([owner1, owner2], nonceOffset, true, false, "MSW"); }); it("should fail with the wrong signature", async () => { diff --git a/test/nftTransfer.js b/test/nftTransfer.js deleted file mode 100644 index d7e887ef3..000000000 --- a/test/nftTransfer.js +++ /dev/null @@ -1,223 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); -const utils = require("../utils/utilities.js"); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const Registry = artifacts.require("ModuleRegistry"); -const VersionManager = artifacts.require("VersionManager"); -const RelayerManager = artifacts.require("RelayerManager"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const NftTransfer = artifacts.require("NftTransfer"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const ERC721 = artifacts.require("TestERC721"); -const CK = artifacts.require("CryptoKittyTest"); -const ERC20 = artifacts.require("TestERC20"); -const ERC20Approver = artifacts.require("ERC20Approver"); - -const ZERO_BYTES32 = ethers.constants.HashZero; - -const RelayManager = require("../utils/relay-manager"); - -contract("NftTransfer", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner1 = accounts[1]; - const owner2 = accounts[2]; - const eoaRecipient = accounts[3]; - const tokenId = 1; - - let nftFeature; - let walletImplementation; - let relayerManager; - let wallet1; - let wallet2; - let erc721; - let ck; - let ckId; - let erc20; - let erc20Approver; - let tokenPriceRegistry; - let lockStorage; - let versionManager; - - before(async () => { - const registry = await Registry.new(); - walletImplementation = await BaseWallet.new(); - - const guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - ck = await CK.new(); - tokenPriceRegistry = await TokenPriceRegistry.new(); - await tokenPriceRegistry.addManager(infrastructure); - nftFeature = await NftTransfer.new( - lockStorage.address, - tokenPriceRegistry.address, - versionManager.address, - ck.address); - erc20Approver = await ERC20Approver.new(versionManager.address); - - await versionManager.addVersion([erc20Approver.address, nftFeature.address, relayerManager.address], []); - }); - - beforeEach(async () => { - const proxy1 = await Proxy.new(walletImplementation.address); - wallet1 = await BaseWallet.at(proxy1.address); - const proxy2 = await Proxy.new(walletImplementation.address); - wallet2 = await BaseWallet.at(proxy2.address); - - await wallet1.init(owner1, [versionManager.address]); - await wallet2.init(owner2, [versionManager.address]); - await versionManager.upgradeWallet(wallet1.address, await versionManager.lastVersion(), { from: owner1 }); - await versionManager.upgradeWallet(wallet2.address, await versionManager.lastVersion(), { from: owner2 }); - - erc721 = await ERC721.new(); - await erc721.mint(wallet1.address, tokenId); - }); - - describe("NFT transfers", () => { - async function testNftTransfer({ safe = true, relayed, recipientAddress, nftContract = erc721, nftId = tokenId }) { - const beforeWallet1 = await nftContract.balanceOf(wallet1.address); - const beforeRecipient = await nftContract.balanceOf(recipientAddress); - if (relayed) { - const txReceipt = await manager.relay(nftFeature, "transferNFT", - [wallet1.address, nftContract.address, recipientAddress, nftId, safe, ZERO_BYTES32], wallet1, [owner1]); - const { success } = utils.parseRelayReceipt(txReceipt); - assert.isTrue(success); - } else { - await nftFeature.transferNFT(wallet1.address, nftContract.address, recipientAddress, nftId, safe, ZERO_BYTES32, - { from: owner1 }); - } - - const afterWallet1 = await nftContract.balanceOf(wallet1.address); - const afterRecipient = await nftContract.balanceOf(recipientAddress); - assert.equal(beforeWallet1.sub(afterWallet1).toNumber(), 1, `wallet1 should have one less NFT (safe: ${safe}, relayed: ${relayed})`); - assert.equal(afterRecipient.sub(beforeRecipient).toNumber(), 1, `recipient should have one more NFT (safe: ${safe}, relayed: ${relayed})`); - } - - describe("transfer to EOA account", () => { - it("should allow unsafe NFT transfer from wallet1 to an EOA account", async () => { - await testNftTransfer({ safe: false, relayed: false, recipientAddress: eoaRecipient }); - }); - - it("should allow safe NFT transfer from wallet1 to an EOA account", async () => { - await testNftTransfer({ safe: true, relayed: false, recipientAddress: eoaRecipient }); - }); - - it("should allow unsafe NFT transfer from wallet1 to an EOA account (relayed)", async () => { - await testNftTransfer({ safe: false, relayed: true, recipientAddress: eoaRecipient }); - }); - - it("should allow safe NFT transfer from wallet1 to an EOA account (relayed)", async () => { - await testNftTransfer({ safe: true, relayed: true, recipientAddress: eoaRecipient }); - }); - }); - - describe("transfer to other wallet", () => { - it("should allow unsafe NFT transfer from wallet1 to wallet2", async () => { - await testNftTransfer({ safe: false, relayed: false, recipientAddress: wallet2.address }); - }); - - it("should allow safe NFT transfer from wallet1 to wallet2", async () => { - await testNftTransfer({ safe: true, relayed: false, recipientAddress: wallet2.address }); - }); - - it("should allow unsafe NFT transfer from wallet1 to wallet2 (relayed)", async () => { - await testNftTransfer({ safe: false, relayed: true, recipientAddress: wallet2.address }); - }); - - it("should allow safe NFT transfer from wallet1 to wallet2 (relayed)", async () => { - await testNftTransfer({ safe: true, relayed: true, recipientAddress: wallet2.address }); - }); - }); - - describe("CK transfer", () => { - beforeEach(async () => { - await ck.createDumbKitty(wallet1.address); - ckId = (ckId === undefined) ? 0 : ckId + 1; // update the id of the CryptoKitty that was just created - }); - - it("should allow CK transfer from wallet1 to wallet2", async () => { - await testNftTransfer({ - relayed: false, nftId: ckId, nftContract: ck, recipientAddress: wallet2.address, - }); - }); - - it("should allow CK transfer from wallet1 to wallet2 (relayed)", async () => { - await testNftTransfer({ - relayed: true, nftId: ckId, nftContract: ck, recipientAddress: wallet2.address, - }); - }); - - it("should allow CK transfer from wallet1 to EOA account", async () => { - await testNftTransfer({ - relayed: false, nftId: ckId, nftContract: ck, recipientAddress: eoaRecipient, - }); - }); - - it("should allow CK transfer from wallet1 to EOA account (relayed)", async () => { - await testNftTransfer({ - relayed: true, nftId: ckId, nftContract: ck, recipientAddress: eoaRecipient, - }); - }); - }); - - describe("Protecting from transferFrom hijacking", () => { - beforeEach(async () => { - erc20 = await ERC20.new([wallet1.address], 1000, 18); - tokenPriceRegistry.setPriceForTokenList([erc20.address], [1]); - await erc20Approver.approveERC20( - wallet1.address, - erc20.address, - wallet1.address, // spender - 100, - { from: owner1 } - ); // amount - }); - - it("should NOT allow ERC20 transfer from wallet1 to wallet2", async () => { - await truffleAssert.reverts(nftFeature.transferNFT(wallet1.address, erc20.address, wallet2.address, 100, false, ZERO_BYTES32, - { from: owner1, gasLimit: 300000 }), - "NT: Forbidden ERC20 contract"); - }); - - it("should NOT allow ERC20 transfer from wallet1 to wallet2 (relayed)", async () => { - const txReceipt = await manager.relay(nftFeature, "transferNFT", - [wallet1.address, erc20.address, wallet2.address, 100, false, ZERO_BYTES32], wallet1, [owner1]); - - const { success, error } = utils.parseRelayReceipt(txReceipt); - assert.isFalse(success); - assert.equal(error, "NT: Forbidden ERC20 contract"); - }); - }); - - describe("Static calls", () => { - it("should delegate onERC721Received static calls to the NftTransfer feature", async () => { - const ERC721_RECEIVED = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("onERC721Received(address,address,uint256,bytes)")).slice(0, 10); - const erc721ReceivedDelegate = await wallet1.enabled(ERC721_RECEIVED); - assert.equal(erc721ReceivedDelegate, versionManager.address); - - const walletAsTransferManager = await NftTransfer.at(wallet1.address); - const result = await walletAsTransferManager.onERC721Received.call(infrastructure, infrastructure, 0, "0x"); - assert.equal(result, ERC721_RECEIVED); - }); - }); - }); -}); diff --git a/test/paraswapV4.js b/test/paraswapV4.js new file mode 100644 index 000000000..bb5e00240 --- /dev/null +++ b/test/paraswapV4.js @@ -0,0 +1,697 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { expect, assert } = chai; +chai.use(bnChai(BN)); + +// Paraswap +const IAugustusSwapper = artifacts.require("IAugustusSwapper"); +const AugustusSwapper = artifacts.require("AugustusSwapperMock"); +const Whitelisted = artifacts.require("Whitelisted"); +const PartnerRegistry = artifacts.require("PartnerRegistry"); +const PartnerDeployer = artifacts.require("PartnerDeployer"); +const Uniswap = artifacts.require("Uniswap"); +const UniswapV2 = artifacts.require("UniswapV2Mock"); +const UniswapProxy = artifacts.require("UniswapProxyTest"); +const UniswapV3Router = artifacts.require("UniswapV3Router"); +const ZeroxV2TargetExchange = artifacts.require("ZeroxV2TargetExchangeMock"); +const ZeroxV4TargetExchange = artifacts.require("ZeroxV4TargetExchangeMock"); +const CurvePool = artifacts.require("CurvePoolMock"); +const Curve = artifacts.require("Curve"); +const WethAdapter = artifacts.require("WethExchangeMock"); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const Proxy = artifacts.require("Proxy"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ParaswapFilter = artifacts.require("ParaswapFilter"); +const ParaswapUniV2RouterFilter = artifacts.require("ParaswapUniV2RouterFilter"); +const ZeroExV2Filter = artifacts.require("WhitelistedZeroExV2Filter"); +const ZeroExV4Filter = artifacts.require("WhitelistedZeroExV4Filter"); +const CurveFilter = artifacts.require("CurveFilter"); +const OnlyApproveFilter = artifacts.require("OnlyApproveFilter"); +const ERC20 = artifacts.require("TestERC20"); +const TokenRegistry = artifacts.require("TokenRegistry"); + +// Utils +const RelayManager = require("../utils/relay-manager"); +const { deployUniswap } = require("../utils/defi-deployer"); +const { getRouteParams, getParaswappoolData, getSimpleSwapParams, getRoutesForExchange } = require("../utils/paraswap/sell-helper"); +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, initNonce, encodeCalls, asciiToBytes32 } = require("../utils/utilities.js"); + +// Constants +const DECIMALS = 18; // number of decimal for TOKEN_A, TOKEN_B contracts +const PARASWAP_ETH_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; +const TOKEN_A_LIQ = web3.utils.toWei("300"); +const TOKEN_B_LIQ = web3.utils.toWei("300"); +const TOKEN_C_LIQ = web3.utils.toWei("300"); +const WETH_LIQ = web3.utils.toWei("1"); + +contract("Paraswap Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const other = accounts[2]; + const marketMaker = accounts[3]; + const relayer = accounts[4]; + const zeroExV2 = accounts[5]; + const zeroExV4 = accounts[6]; + const proxyContract = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let walletImplementation; + let dappRegistry; + + let uniswapV1Factory; + let uniswapV1Exchanges; + let uniswapV2Factory; + let zeroExV2Proxy; + let zeroExV2TargetExchange; + let zeroExV4TargetExchange; + let curvePool; + let uniswapV1Adapter; + let uniswapV2Adapter; + let sushiswapAdapter; + let linkswapAdapter; + let defiswapAdapter; + let zeroExV2Adapter; + let zeroExV4Adapter; + let curveAdapter; + let wethAdapter; + let unauthorisedAdapter; + let initCode; + let weth; + let tokenA; + let tokenB; + let tokenC; + let paraswap; + let paraswapProxy; + let paraswapFilter; + let tokenRegistry; + let uniswapProxy; + let uniV3Router; + + before(async () => { + // Deploy test tokens + tokenA = await ERC20.new([infrastructure], new BN(TOKEN_A_LIQ).muln(3), DECIMALS); + tokenB = await ERC20.new([infrastructure], new BN(TOKEN_B_LIQ).muln(3), DECIMALS); + tokenC = await ERC20.new([infrastructure], new BN(TOKEN_C_LIQ).muln(3), DECIMALS); + + // Fake Adapters/TargetExchanges/Proxies + zeroExV2Adapter = { address: zeroExV2 }; + zeroExV4Adapter = { address: zeroExV4 }; + zeroExV2TargetExchange = await ZeroxV2TargetExchange.new(); + zeroExV4TargetExchange = await ZeroxV4TargetExchange.new(); + curvePool = await CurvePool.new(); + zeroExV2Proxy = { address: proxyContract }; + + // Deploy Uniswap + const ethPerToken = new BN(10).pow(new BN(16)); + const { uniswapFactory, uniswapExchanges } = (await deployUniswap( + infrastructure, [tokenA, tokenB, tokenC], [ethPerToken, ethPerToken, ethPerToken] + )); + uniswapV1Factory = uniswapFactory; + uniswapV1Exchanges = uniswapExchanges; + + // Deploy UniswapV2 + uniswapV2Factory = await UniswapV2Factory.new(ZERO_ADDRESS); + weth = await WETH.new(); + const uniswapRouter = await UniswapV2Router01.new(uniswapV2Factory.address, weth.address); + initCode = await uniswapV2Factory.getKeccakOfPairCreationCode(); + await weth.deposit({ value: new BN(WETH_LIQ).muln(3) }); + await weth.approve(uniswapRouter.address, new BN(WETH_LIQ).muln(3)); + await tokenA.approve(uniswapRouter.address, new BN(TOKEN_A_LIQ).muln(2)); + await tokenB.approve(uniswapRouter.address, new BN(TOKEN_B_LIQ).muln(2)); + await tokenC.approve(uniswapRouter.address, new BN(TOKEN_C_LIQ)); + const timestamp = await utils.getTimestamp(); + await uniswapRouter.addLiquidity(tokenA.address, weth.address, TOKEN_A_LIQ, WETH_LIQ, 1, 1, infrastructure, timestamp + 300); + await uniswapRouter.addLiquidity(tokenB.address, weth.address, TOKEN_B_LIQ, WETH_LIQ, 1, 1, infrastructure, timestamp + 300); + await uniswapRouter.addLiquidity(tokenA.address, tokenB.address, TOKEN_A_LIQ, TOKEN_B_LIQ, 1, 1, infrastructure, timestamp + 300); + await uniswapRouter.addLiquidity(tokenC.address, weth.address, TOKEN_C_LIQ, WETH_LIQ, 1, 1, infrastructure, timestamp + 300); + + // Deploy Paraswap + uniswapProxy = await UniswapProxy.new(weth.address, uniswapV2Factory.address, initCode); + const paraswapWhitelist = await Whitelisted.new(); + const partnerDeployer = await PartnerDeployer.new(); + const partnerRegistry = await PartnerRegistry.new(partnerDeployer.address); + paraswap = await IAugustusSwapper.at((await AugustusSwapper.new()).address); + await paraswap.initialize( + paraswapWhitelist.address, + ZERO_ADDRESS, + partnerRegistry.address, + infrastructure, + uniswapProxy.address); + uniswapV1Adapter = await Uniswap.new(); + uniswapV2Adapter = await UniswapV2.new(weth.address, asciiToBytes32("Uniswap")); + sushiswapAdapter = await UniswapV2.new(weth.address, asciiToBytes32("Sushiswap")); + linkswapAdapter = await UniswapV2.new(weth.address, asciiToBytes32("Linkswap")); + defiswapAdapter = await UniswapV2.new(weth.address, asciiToBytes32("Defiswap")); + curveAdapter = await Curve.new(); + wethAdapter = await WethAdapter.new(weth.address); + unauthorisedAdapter = await Uniswap.new(); + const wlr = await paraswapWhitelist.WHITELISTED_ROLE(); + await paraswapWhitelist.grantRole(wlr, wethAdapter.address); + await paraswapWhitelist.grantRole(wlr, uniswapV1Adapter.address); + await paraswap.initializeAdapter(uniswapV1Adapter.address, web3.eth.abi.encodeParameter( + { ParentStruct: { factory: "address" } }, + { factory: uniswapV1Factory.address })); + for (const adapter of [uniswapV2Adapter, sushiswapAdapter, linkswapAdapter, defiswapAdapter]) { + await paraswapWhitelist.grantRole(wlr, adapter.address); + await paraswap.initializeAdapter(adapter.address, web3.eth.abi.encodeParameter( + { ParentStruct: { uinswapV2Router: "address", factory: "address", initCode: "bytes32", } }, + { uinswapV2Router: uniswapRouter.address, factory: uniswapV2Factory.address, initCode })); + } + paraswapProxy = await paraswap.getTokenTransferProxy(); + uniV3Router = await UniswapV3Router.new(uniswapV2Factory.address, weth.address, initCode); + + // deploy Argent + registry = await Registry.new(); + tokenRegistry = await TokenRegistry.new(); + const pairs = [ + await uniswapV2Factory.allPairs(0), // tokenA-weth uniV2 + await uniswapV2Factory.allPairs(1), // tokenB-weth uniV2 + await uniswapV2Factory.allPairs(2), // tokenA-tokenB uniV2 + uniswapExchanges[tokenA.address].address, // tokenA-eth uniV1 + uniswapExchanges[tokenB.address].address // tokenB-eth uniV1 + ]; + await tokenRegistry.setTradableForTokenList([tokenA.address, tokenB.address, ...pairs], Array(2 + pairs.length).fill(true)); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + paraswapFilter = await ParaswapFilter.new( + tokenRegistry.address, + dappRegistry.address, + paraswap.address, + uniswapProxy.address, + [uniswapV2Factory.address, uniswapV2Factory.address, uniswapV2Factory.address], + [initCode, initCode, initCode], + [ + uniswapV1Adapter.address, + uniswapV2Adapter.address, + sushiswapAdapter.address, + linkswapAdapter.address, + defiswapAdapter.address, + zeroExV2Adapter.address, + zeroExV4Adapter.address, + curveAdapter.address, + wethAdapter.address + ], + [uniswapV1Factory.address, zeroExV2TargetExchange.address, zeroExV4TargetExchange.address, curvePool.address], + [marketMaker]); + const proxyFilter = await OnlyApproveFilter.new(); + const paraswapUniV2RouterFilter = await ParaswapUniV2RouterFilter.new(tokenRegistry.address, uniswapV2Factory.address, initCode, weth.address); + const zeroExV2Filter = await ZeroExV2Filter.new([marketMaker]); + const zeroExV4Filter = await ZeroExV4Filter.new([marketMaker]); + const curveFilter = await CurveFilter.new(); + await dappRegistry.addDapp(0, paraswap.address, paraswapFilter.address); + await dappRegistry.addDapp(0, paraswapProxy, proxyFilter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + await dappRegistry.addDapp(0, uniswapV1Exchanges[tokenA.address].address, ZERO_ADDRESS); + await dappRegistry.addDapp(0, uniswapV1Exchanges[tokenB.address].address, ZERO_ADDRESS); + await dappRegistry.addDapp(0, uniswapV1Exchanges[tokenC.address].address, ZERO_ADDRESS); + await dappRegistry.addDapp(0, uniV3Router.address, paraswapUniV2RouterFilter.address); + await dappRegistry.addDapp(0, zeroExV2Proxy.address, proxyFilter.address); + await dappRegistry.addDapp(0, zeroExV2TargetExchange.address, zeroExV2Filter.address); + await dappRegistry.addDapp(0, zeroExV4TargetExchange.address, zeroExV4Filter.address); + await dappRegistry.addDapp(0, curvePool.address, curveFilter.address); + walletImplementation = await BaseWallet.new(); + + manager = new RelayManager(guardianStorage.address, tokenRegistry.address); + }); + + beforeEach(async () => { + // create wallet + const proxy = await Proxy.new(walletImplementation.address); + wallet = await BaseWallet.at(proxy.address); + await wallet.init(owner, [module.address]); + + // fund wallet + await wallet.send(web3.utils.toWei("1")); + await tokenA.mint(wallet.address, web3.utils.toWei("1000")); + await tokenB.mint(wallet.address, web3.utils.toWei("1000")); + await tokenC.mint(wallet.address, web3.utils.toWei("1000")); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + function getTokenContract(tokenAddress) { + let tokenContract; + if (tokenAddress === tokenA.address) { + tokenContract = tokenA; + } else if (tokenAddress === tokenB.address) { + tokenContract = tokenB; + } else if (tokenAddress === tokenC.address) { + tokenContract = tokenC; + } else if (tokenAddress === weth.address) { + tokenContract = weth; + } else { + tokenContract = { address: PARASWAP_ETH_TOKEN }; + } + return tokenContract; + } + + async function getBalance(tokenAddress, _wallet) { + let balance; + if (tokenAddress === PARASWAP_ETH_TOKEN) { + balance = await utils.getBalance(_wallet.address); + } else { + balance = await getTokenContract(tokenAddress).balanceOf(_wallet.address); + } + return balance; + } + + const multiCall = async (transactions, { errorReason = null }) => { + const txReceipt = await manager.relay( + module, "multiCall", [wallet.address, transactions], wallet, [owner], 1, ETH_TOKEN, relayer + ); + const { success, error } = utils.parseRelayReceipt(txReceipt); + if (errorReason) { + assert.isFalse(success, "multiCall should have failed"); + assert.equal(error, errorReason); + } else { + assert.isTrue(success, `multiCall failed: "${error}"`); + } + }; + + function getPath({ fromToken, toToken, routes, useUnauthorisedAdapter = false, useUnauthorisedTargetExchange = false }) { + const exchanges = { + uniswap: useUnauthorisedAdapter ? unauthorisedAdapter.address : uniswapV1Adapter.address, + uniswapv2: useUnauthorisedAdapter ? unauthorisedAdapter.address : uniswapV2Adapter.address, + sushiswap: useUnauthorisedAdapter ? unauthorisedAdapter.address : sushiswapAdapter.address, + linkswap: useUnauthorisedAdapter ? unauthorisedAdapter.address : linkswapAdapter.address, + defiswap: useUnauthorisedAdapter ? unauthorisedAdapter.address : defiswapAdapter.address, + paraswappoolv2: useUnauthorisedAdapter ? unauthorisedAdapter.address : zeroExV2Adapter.address, + paraswappoolv4: useUnauthorisedAdapter ? unauthorisedAdapter.address : zeroExV4Adapter.address, + curve: useUnauthorisedAdapter ? unauthorisedAdapter.address : curveAdapter.address, + weth: useUnauthorisedAdapter ? unauthorisedAdapter.address : wethAdapter.address, + }; + const targetExchanges = { + uniswap: useUnauthorisedTargetExchange ? other : uniswapV1Factory.address, + uniswapv2: ZERO_ADDRESS, + sushiswap: ZERO_ADDRESS, + linkswap: ZERO_ADDRESS, + defiswap: ZERO_ADDRESS, + paraswappoolv2: useUnauthorisedTargetExchange ? other : zeroExV2TargetExchange.address, + paraswappoolv4: useUnauthorisedTargetExchange ? other : zeroExV4TargetExchange.address, + curve: useUnauthorisedTargetExchange ? other : curvePool.address, + weth: ZERO_ADDRESS + }; + return [{ + to: toToken, + totalNetworkFee: 0, + routes: routes.map((route) => getRouteParams(fromToken, toToken, route, exchanges, targetExchanges)), + }]; + } + + function getMultiSwapData({ + fromToken, + toToken, + fromAmount, + toAmount, + beneficiary, + useUnauthorisedAdapter = false, + useUnauthorisedTargetExchange = false, + routes + }) { + const path = getPath({ fromToken, toToken, routes, useUnauthorisedAdapter, useUnauthorisedTargetExchange }); + return paraswap.contract.methods.multiSwap({ + fromToken, fromAmount, toAmount, expectedAmount: 0, beneficiary, referrer: "abc", useReduxToken: false, path + }).encodeABI(); + } + + function getMegaSwapData({ + fromToken, + toToken, + fromAmount, + toAmount, + beneficiary, + useUnauthorisedAdapter, + useUnauthorisedTargetExchange, + routes + }) { + const path = getPath({ fromToken, toToken, routes, useUnauthorisedAdapter, useUnauthorisedTargetExchange }); + return paraswap.contract.methods.megaSwap({ + fromToken, + fromAmount, + toAmount, + expectedAmount: 0, + beneficiary, + referrer: "abc", + useReduxToken: false, + path: [{ fromAmountPercent: 10000, path }] + }).encodeABI(); + } + + function getSimpleSwapExchangeCallParams({ exchange, fromToken, toToken, fromAmount, toAmount, maker }) { + let targetExchange; + let swapMethod; + let swapParams; + let proxy = null; + + if (exchange === "uniswapv2") { + targetExchange = uniV3Router; + swapMethod = "swap"; + swapParams = [fromAmount, toAmount, [fromToken, toToken]]; + } else if (exchange === "uniswap") { + if (fromToken === PARASWAP_ETH_TOKEN) { + targetExchange = uniswapV1Exchanges[toToken]; + swapMethod = "ethToTokenSwapInput"; + swapParams = [1, 99999999999]; + } else { + targetExchange = uniswapV1Exchanges[fromToken]; + if (toToken === PARASWAP_ETH_TOKEN) { + swapMethod = "tokenToEthSwapInput"; + swapParams = [fromAmount, 1, 99999999999]; + } else { + swapMethod = "tokenToTokenSwapInput"; + swapParams = [fromAmount, 1, 1, 99999999999, toToken]; + } + } + } else if (exchange === "zeroexv2") { + proxy = zeroExV2Proxy; + targetExchange = zeroExV2TargetExchange; + swapMethod = "marketSellOrdersNoThrow"; + const { orders, signatures } = getParaswappoolData({ maker, version: 2 }); + swapParams = [orders, 0, signatures]; + } else if (exchange === "zeroexv4") { + targetExchange = zeroExV4TargetExchange; + swapMethod = "fillRfqOrder"; + const { order, signature } = getParaswappoolData({ fromToken, toToken, maker, version: 4 }); + swapParams = [order, signature, 0]; + } else if (exchange === "curve") { + targetExchange = curvePool; + swapMethod = "exchange"; + swapParams = [0, 1, fromAmount, toAmount]; + } + + return { targetExchange, swapMethod, swapParams, proxy }; + } + + function getSimpleSwapData({ fromToken, toToken, fromAmount, toAmount, exchange, beneficiary, maker = marketMaker }) { + const simpleSwapParams = getSimpleSwapParams({ + ...getSimpleSwapExchangeCallParams({ exchange, fromToken, toToken, fromAmount, toAmount, maker }), + fromTokenContract: getTokenContract(fromToken), + toTokenContract: getTokenContract(toToken), + fromAmount, + toAmount, + beneficiary + }); + return paraswap.contract.methods.simpleSwap(...simpleSwapParams).encodeABI(); + } + + function getSwapOnUniswapData({ fromToken, toToken, fromAmount, toAmount }) { + return paraswap.contract.methods.swapOnUniswap(fromAmount, toAmount, [fromToken, toToken], 0).encodeABI(); + } + + function getSwapOnUniswapForkData({ fromToken, toToken, fromAmount, toAmount }) { + return paraswap.contract.methods.swapOnUniswapFork(uniswapV2Factory.address, initCode, fromAmount, toAmount, [fromToken, toToken], 0).encodeABI(); + } + + async function testTrade({ + method, + fromToken, + toToken, + beneficiary = ZERO_ADDRESS, + fromAmount = web3.utils.toWei("0.01"), + toAmount = 1, + useUnauthorisedAdapter = false, + useUnauthorisedTargetExchange = false, + errorReason = null, + exchange = "uniswap" + }) { + const beforeFrom = await getBalance(fromToken, wallet); + const beforeTo = await getBalance(toToken, wallet); + expect(beforeFrom).to.be.gte.BN(fromAmount); // wallet should have enough of fromToken + const transactions = []; + + // token approval if necessary + if (fromToken !== PARASWAP_ETH_TOKEN) { + const token = getTokenContract(fromToken); + const approveData = token.contract.methods.approve(paraswapProxy, fromAmount).encodeABI(); + transactions.push(encodeTransaction(fromToken, 0, approveData)); + } + + // token swap + let swapData; + const routes = getRoutesForExchange({ fromToken, toToken, maker: marketMaker, exchange }); + if (method === "multiSwap") { + swapData = getMultiSwapData({ + fromToken, toToken, fromAmount, toAmount, beneficiary, routes, useUnauthorisedAdapter, useUnauthorisedTargetExchange + }); + } else if (method === "megaSwap") { + swapData = getMegaSwapData({ + fromToken, toToken, fromAmount, toAmount, beneficiary, routes, useUnauthorisedAdapter, useUnauthorisedTargetExchange + }); + } else if (method === "simpleSwap") { + swapData = getSimpleSwapData({ fromToken, toToken, fromAmount, toAmount, beneficiary, exchange }); + } else if (method === "swapOnUniswap") { + swapData = getSwapOnUniswapData({ fromToken, toToken, fromAmount, toAmount }); + } else if (method === "swapOnUniswapFork") { + swapData = getSwapOnUniswapForkData({ fromToken, toToken, fromAmount, toAmount }); + } else { + throw new Error("Invalid method"); + } + const value = fromToken === PARASWAP_ETH_TOKEN ? fromAmount : 0; + transactions.push(encodeTransaction(paraswap.address, value, swapData)); + + await multiCall(transactions, { errorReason }); + if (!errorReason) { + const afterFrom = await getBalance(fromToken, wallet); + const afterTo = await getBalance(toToken, wallet); + expect(beforeFrom).to.be.gt.BN(afterFrom); + expect(afterTo).to.be.gt.BN(beforeTo); + } + } + + function testsForMethod(method) { + describe(`${method} trades`, () => { + it("should sell ETH for token A", async () => { + await testTrade({ method, fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address }); + }); + it("should sell token B for ETH", async () => { + await testTrade({ method, fromToken: tokenB.address, toToken: PARASWAP_ETH_TOKEN }); + }); + it("should sell token B for token A", async () => { + await testTrade({ method, fromToken: tokenB.address, toToken: tokenA.address }); + }); + it("should not sell ETH for non-tradable token C", async () => { + await testTrade({ method, fromToken: PARASWAP_ETH_TOKEN, toToken: tokenC.address, errorReason: "TM: call not authorised" }); + }); + }); + } + + ["multiSwap", "swapOnUniswap", "swapOnUniswapFork", "megaSwap"].forEach(testsForMethod); + + it("performs a multiswap weth->eth trade", async () => { + const fromAmount = web3.utils.toWei("0.01"); + await weth.deposit({ value: fromAmount }); + await weth.transfer(wallet.address, fromAmount); + await testTrade({ method: "multiSwap", fromAmount, fromToken: weth.address, toToken: PARASWAP_ETH_TOKEN, exchange: "weth" }); + }); + + it("performs a multiswap eth->weth trade", async () => { + const fromAmount = web3.utils.toWei("0.01"); + await testTrade({ method: "multiSwap", fromAmount, fromToken: PARASWAP_ETH_TOKEN, toToken: weth.address, exchange: "weth" }); + }); + + function testSimpleSwapTradesViaExchange(exchange) { + const method = "simpleSwap"; + describe(`simpleSwap trades via ${exchange}`, () => { + it("should sell ETH for token A", async () => { + await testTrade({ method, fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address, exchange }); + }); + it("should sell token B for ETH", async () => { + await testTrade({ method, fromToken: tokenB.address, toToken: PARASWAP_ETH_TOKEN, exchange }); + }); + it("should sell token B for token A", async () => { + await testTrade({ method, fromToken: tokenB.address, toToken: tokenA.address, exchange }); + }); + it("should not sell ETH for non-tradable token C", async () => { + await testTrade({ method, fromToken: PARASWAP_ETH_TOKEN, toToken: tokenC.address, errorReason: "TM: call not authorised", exchange + }); + }); + }); + } + + ["uniswap", "uniswapv2"].forEach(testSimpleSwapTradesViaExchange); + + describe("unauthorised access", () => { + it("should not allow sending ETH without calling an authorised method", async () => { + await multiCall([encodeTransaction(paraswap.address, web3.utils.toWei("0.01"), "0x")], + { errorReason: "TM: call not authorised" }); + }); + + it("should not allow unsupported method call", async () => { + await multiCall(encodeCalls([[paraswap, "getFeeWallet"]]), + { errorReason: "TM: call not authorised" }); + }); + + it("should not allow swapOnUniswap[Fork] via unauthorised uniswap proxy", async () => { + await paraswap.changeUniswapProxy(other); + await paraswapFilter.updateIsValidUniswapProxy(); + await testTrade({ + method: "swapOnUniswap", fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address, errorReason: "TM: call not authorised" + }); + await testTrade({ + method: "swapOnUniswapFork", fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address, errorReason: "TM: call not authorised" + }); + await paraswap.changeUniswapProxy(uniswapProxy.address); + await paraswapFilter.updateIsValidUniswapProxy(); + }); + + it("should not allow simpleSwap via unauthorised callee", async () => { + await dappRegistry.removeDapp(0, uniswapV1Exchanges[tokenA.address].address); + await testTrade({ + method: "simpleSwap", fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address, errorReason: "TM: call not authorised" + }); + await dappRegistry.addDapp(0, uniswapV1Exchanges[tokenA.address].address, ZERO_ADDRESS); + }); + + async function testUnauthorisedAdapter(method) { + await testTrade({ + method, + fromToken: PARASWAP_ETH_TOKEN, + toToken: tokenA.address, + useUnauthorisedAdapter: true, + errorReason: "TM: call not authorised" + }); + } + + it("should not allow multiSwap via unauthorised adapter", async () => { + await testUnauthorisedAdapter("multiSwap"); + }); + + it("should not allow megaSwap via unauthorised adapter", async () => { + await testUnauthorisedAdapter("megaSwap"); + }); + + async function testUnauthorisedTargetExchange(method) { + await testTrade({ + method, + fromToken: PARASWAP_ETH_TOKEN, + toToken: tokenA.address, + useUnauthorisedTargetExchange: true, + errorReason: "TM: call not authorised" + }); + } + + it("should not allow multiSwap via unauthorised target exchange", async () => { + await testUnauthorisedTargetExchange("multiSwap"); + }); + + it("should not allow megaSwap via unauthorised target exchange", async () => { + await testUnauthorisedTargetExchange("megaSwap"); + }); + }); + + describe("authorisations", () => { + describe("multiswap", () => { + async function testMultiSwapAuthorisation({ + fromToken, + toToken, + routes, + expectValid = true, + beneficiary = ZERO_ADDRESS, + fromAmount = web3.utils.toWei("0.01"), + toAmount = 1, + }) { + const swapData = getMultiSwapData({ fromToken, toToken, fromAmount, toAmount, beneficiary, routes }); + const isValid = await paraswapFilter.isValid(wallet.address, paraswap.address, paraswap.address, swapData); + assert.equal(isValid, expectValid, `authorisation should ${expectValid ? "" : "not"} have been granted for multiSwap`); + } + + it("authorises a multiswap paraswappool trade", async () => { + const fromToken = PARASWAP_ETH_TOKEN; + const toToken = tokenA.address; + const routes = getRoutesForExchange({ fromToken, toToken, exchange: "paraswappoolv4", maker: marketMaker }); + await testMultiSwapAuthorisation({ fromToken, toToken, routes, expectValid: true }); + }); + + it("denies a multiswap paraswappool trade with non-whitelisted maker", async () => { + const fromToken = PARASWAP_ETH_TOKEN; + const toToken = tokenA.address; + const routes = getRoutesForExchange({ fromToken, toToken, exchange: "paraswappoolv4", maker: other }); + await testMultiSwapAuthorisation({ fromToken, toToken, routes, expectValid: false }); + }); + + it("authorises a multiswap curve trade", async () => { + const fromToken = PARASWAP_ETH_TOKEN; + const toToken = tokenA.address; + const routes = getRoutesForExchange({ fromToken, toToken, exchange: "curve" }); + await testMultiSwapAuthorisation({ fromToken, toToken, routes, expectValid: true }); + }); + }); + + describe("simpleswap", () => { + function testSimpleSwapAuthorisationViaExchange(exchange) { + describe(`simpleSwap authorisation via ${exchange}`, () => { + async function testSimpleSwapAuthorisation({ + fromToken, toToken, expectValid = true, maker = marketMaker, fromAmount = web3.utils.toWei("0.01"), toAmount = 1 + }) { + const swapData = getSimpleSwapData({ fromToken, toToken, fromAmount, toAmount, exchange, maker }); + const isValid = await paraswapFilter.isValid(wallet.address, paraswap.address, paraswap.address, swapData); + assert.equal(isValid, expectValid, `authorisation should ${expectValid ? "" : "not"} have been granted for simpleswap`); + } + it("should allow selling ETH for token A", async () => { + await testSimpleSwapAuthorisation({ fromToken: PARASWAP_ETH_TOKEN, toToken: tokenA.address }); + }); + it("should allow selling token B for ETH", async () => { + await testSimpleSwapAuthorisation({ fromToken: tokenB.address, toToken: PARASWAP_ETH_TOKEN }); + }); + it("should allow selling token B for token A", async () => { + await testSimpleSwapAuthorisation({ fromToken: tokenB.address, toToken: tokenA.address }); + }); + if (["zeroexv2", "zeroexv4"].includes(exchange)) { + it("should not allow selling token B for token A via invalid market maker", async () => { + await testSimpleSwapAuthorisation({ fromToken: tokenB.address, toToken: tokenA.address, expectValid: false, maker: other }); + }); + } + it("should not allow ETH transfers", async () => { + const { targetExchange } = getSimpleSwapExchangeCallParams({ exchange }); + const simpleSwapParams = getSimpleSwapParams({ targetExchange, swapMethod: null, swapData: null }); + const swapData = paraswap.contract.methods.simpleSwap(...simpleSwapParams).encodeABI(); + const isValid = await paraswapFilter.isValid(wallet.address, paraswap.address, paraswap.address, swapData); + assert.equal(isValid, false, `authorisation should not have been granted for ETH transfer to ${exchange}`); + }); + }); + } + + ["uniswapv2", "zeroexv2", "zeroexv4", "curve"].forEach(testSimpleSwapAuthorisationViaExchange); + }); + }); +}); diff --git a/test/proxy.js b/test/proxy.js deleted file mode 100644 index 958337965..000000000 --- a/test/proxy.js +++ /dev/null @@ -1,70 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); - -const { getBalance } = require("../utils/utilities.js"); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const VersionManager = artifacts.require("VersionManager"); -const Registry = artifacts.require("ModuleRegistry"); - -contract("Proxy", (accounts) => { - const owner = accounts[1]; - - let walletImplementation; - let wallet; - let proxy; - let module1; - let module2; - let module3; - let registry; - - async function deployTestModule() { - const module = await VersionManager.new( - registry.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - await module.addVersion([], []); - return module; - } - - before(async () => { - registry = await Registry.new(); - walletImplementation = await BaseWallet.new(); - module1 = await deployTestModule(); - module2 = await deployTestModule(); - module3 = await deployTestModule(); - }); - - beforeEach(async () => { - proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - }); - - it("should init the wallet with the correct owner", async () => { - let walletOwner = await wallet.owner(); - assert.equal(walletOwner, ethers.constants.AddressZero, "owner should be null before init"); - await wallet.init(owner, [module1.address]); - walletOwner = await wallet.owner(); - assert.equal(walletOwner, owner, "owner should be the owner after init"); - }); - - it("should init a wallet with the correct modules", async () => { - await wallet.init(owner, [module1.address, module2.address]); - const module1IsAuthorised = await wallet.authorised(module1.address); - const module2IsAuthorised = await wallet.authorised(module2.address); - const module3IsAuthorised = await wallet.authorised(module3.address); - assert.equal(module1IsAuthorised, true, "module1 should be authorised"); - assert.equal(module2IsAuthorised, true, "module2 should be authorised"); - assert.equal(module3IsAuthorised, false, "module3 should not be authorised"); - }); - - it("should accept ETH", async () => { - const before = await getBalance(wallet.address); - await wallet.send(50000000); - const after = await getBalance(wallet.address); - assert.equal(after.sub(before).toNumber(), 50000000, "should have received ETH"); - }); -}); diff --git a/test/recoveryManager.js b/test/recovery.js similarity index 63% rename from test/recoveryManager.js rename to test/recovery.js index 091f584b2..90f186831 100644 --- a/test/recoveryManager.js +++ b/test/recovery.js @@ -1,112 +1,106 @@ /* global artifacts */ const truffleAssert = require("truffle-assertions"); const ethers = require("ethers"); +const chai = require("chai"); const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); -const GuardianManager = artifacts.require("GuardianManager"); -const LockManager = artifacts.require("LockManager"); -const RecoveryManager = artifacts.require("RecoveryManager"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); const Proxy = artifacts.require("Proxy"); const BaseWallet = artifacts.require("BaseWallet"); const Registry = artifacts.require("ModuleRegistry"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const WalletFactory = artifacts.require("WalletFactory"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); -const RelayManager = require("../utils/relay-manager"); const utils = require("../utils/utilities.js"); +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 24; +const SECURITY_WINDOW = 12; +const LOCK_PERIOD = 50; +const RECOVERY_PERIOD = 36; + +const RelayManager = require("../utils/relay-manager"); + const WRONG_SIGNATURE_NUMBER_REVERT_MSG = "RM: Wrong number of signatures"; const INVALID_SIGNATURES_REVERT_MSG = "RM: Invalid signatures"; contract("RecoveryManager", (accounts) => { - const manager = new RelayManager(); + let manager; + const infrastructure = accounts[0]; const owner = accounts[1]; const guardian1 = accounts[2]; const guardian2 = accounts[3]; const guardian3 = accounts[4]; const newowner = accounts[5]; const nonowner = accounts[6]; - const nonowner2 = accounts[9]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; - let guardianManager; - let lockStorage; + let registry; let guardianStorage; - let lockManager; - let recoveryManager; - let recoveryPeriod; + let transferStorage; + let module; let wallet; let walletImplementation; - let relayerManager; - let versionManager; + let dappRegistry; + let factory; before(async () => { - walletImplementation = await BaseWallet.new(); - }); - - beforeEach(async () => { - const registry = await Registry.new(); + registry = await Registry.new(); guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( registry.address, - lockStorage.address, guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, 12); - lockManager = await LockManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24 * 5); - recoveryManager = await RecoveryManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 36, 24 * 5); - recoveryPeriod = await recoveryManager.recoveryPeriod(); - relayerManager = await RelayerManager.new( - lockStorage.address, + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await versionManager.addVersion([ - guardianManager.address, - lockManager.address, - recoveryManager.address, - relayerManager.address, - ], []); - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + await wallet.send(new BN("1000000000000000000")); }); async function addGuardians(guardians) { // guardians can be BaseWallet or ContractWrapper objects for (const guardian of guardians) { - await guardianManager.addGuardian(wallet.address, guardian, { from: owner }); + await module.addGuardian(wallet.address, guardian, { from: owner }); } - await utils.increaseTime(30); - for (let i = 1; i < guardians.length; i += 1) { - await guardianManager.confirmGuardianAddition(wallet.address, guardians[i]); + for (let i = 0; i < guardians.length; i += 1) { + await module.confirmGuardianAddition(wallet.address, guardians[i]); } - const count = (await guardianManager.guardianCount(wallet.address)).toNumber(); - assert.equal(count, guardians.length, `${guardians.length} guardians should be added`); } async function createSmartContractGuardians(guardians) { @@ -115,8 +109,7 @@ contract("RecoveryManager", (accounts) => { for (guardian of guardians) { const proxy = await Proxy.new(walletImplementation.address); const guardianWallet = await BaseWallet.at(proxy.address); - await guardianWallet.init(guardian, [versionManager.address]); - await versionManager.upgradeWallet(guardianWallet.address, await versionManager.lastVersion(), { from: guardian }); + await guardianWallet.init(guardian, [module.address]); wallets.push(guardianWallet.address); } return wallets; @@ -125,12 +118,13 @@ contract("RecoveryManager", (accounts) => { function testExecuteRecovery(guardians) { it("should let a majority of guardians execute the recovery procedure", async () => { const majority = guardians.slice(0, Math.ceil((guardians.length) / 2)); - await manager.relay(recoveryManager, "executeRecovery", [wallet.address, newowner], wallet, utils.sortWalletByAddress(majority)); + await manager.relay(module, "executeRecovery", [wallet.address, newowner], wallet, utils.sortWalletByAddress(majority)); const timestamp = await utils.getTimestamp(); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isTrue(isLocked, "should be locked by recovery"); - const recoveryConfig = await recoveryManager.getRecovery(wallet.address); + const recoveryConfig = await module.getRecovery(wallet.address); + const recoveryPeriod = new BN(RECOVERY_PERIOD); assert.equal(recoveryConfig._address, newowner); assert.closeTo(recoveryConfig._executeAfter.toNumber(), recoveryPeriod.add(new BN(timestamp)).toNumber(), 1); assert.equal(recoveryConfig._guardianCount, guardians.length); @@ -140,7 +134,7 @@ contract("RecoveryManager", (accounts) => { const expectedRevertMsg = guardians.length >= 3 ? WRONG_SIGNATURE_NUMBER_REVERT_MSG : INVALID_SIGNATURES_REVERT_MSG; await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -153,7 +147,7 @@ contract("RecoveryManager", (accounts) => { const majority = guardians.slice(0, Math.ceil((guardians.length) / 2) - 1); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -161,7 +155,7 @@ contract("RecoveryManager", (accounts) => { ), INVALID_SIGNATURES_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isFalse(isLocked, "should not be locked"); }); @@ -169,7 +163,7 @@ contract("RecoveryManager", (accounts) => { const minority = guardians.slice(0, Math.ceil((guardians.length) / 2) - 1); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -177,65 +171,39 @@ contract("RecoveryManager", (accounts) => { ), WRONG_SIGNATURE_NUMBER_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isFalse(isLocked, "should not be locked"); }); } - function testFinalizeRecovery() { - it("should let anyone finalize the recovery procedure after the recovery period", async () => { - await utils.increaseTime(40); // moving time to after the end of the recovery period - await manager.relay(recoveryManager, "finalizeRecovery", [wallet.address], wallet, []); - const isLocked = await lockManager.isLocked(wallet.address); - assert.isFalse(isLocked, "should no longer be locked after finalization of recovery"); - const walletOwner = await wallet.owner(); - assert.equal(walletOwner, newowner, "wallet owner should have been changed"); - - const recoveryConfig = await recoveryManager.getRecovery(wallet.address); - assert.equal(recoveryConfig._address, ethers.constants.AddressZero); - assert.equal(recoveryConfig._executeAfter.toNumber(), 0); - assert.equal(recoveryConfig._guardianCount, 0); - }); - - it("should not let anyone finalize the recovery procedure before the end of the recovery period", async () => { - const txReceipt = await manager.relay(recoveryManager, "finalizeRecovery", [wallet.address], wallet, []); - const { success, error } = utils.parseRelayReceipt(txReceipt); - assert.isFalse(success); - assert.equal(error, "RM: the recovery period is not over yet"); - - const isLocked = await lockManager.isLocked(wallet.address); - assert.isTrue(isLocked, "should still be locked"); - }); - } - function testCancelRecovery() { it("should let 2 guardians cancel the recovery procedure", async () => { - await manager.relay(recoveryManager, "cancelRecovery", [wallet.address], wallet, utils.sortWalletByAddress([guardian1, guardian2])); - const isLocked = await lockManager.isLocked(wallet.address); + await manager.relay(module, "cancelRecovery", [wallet.address], wallet, utils.sortWalletByAddress([guardian1, guardian2])); + const isLocked = await module.isLocked(wallet.address); assert.isFalse(isLocked, "should no longer be locked by recovery"); await utils.increaseTime(40); // moving time to after the end of the recovery period - const txReceipt = await manager.relay(recoveryManager, "finalizeRecovery", [wallet.address], wallet, []); + const txReceipt = await manager.relay(module, "finalizeRecovery", [wallet.address], wallet, []); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success); - assert.equal(error, "RM: there must be an ongoing recovery"); + assert.equal(error, "SM: no ongoing recovery"); const walletOwner = await wallet.owner(); assert.equal(walletOwner, owner, "wallet owner should not have been changed"); - const recoveryConfig = await recoveryManager.getRecovery(wallet.address); + const recoveryConfig = await module.getRecovery(wallet.address); assert.equal(recoveryConfig._address, ethers.constants.AddressZero); assert.equal(recoveryConfig._executeAfter.toNumber(), 0); assert.equal(recoveryConfig._guardianCount, 0); }); it("should let 1 guardian + owner cancel the recovery procedure", async () => { - await manager.relay(recoveryManager, "cancelRecovery", [wallet.address], wallet, [owner, guardian1]); - const isLocked = await lockManager.isLocked(wallet.address); + await manager.relay(module, "cancelRecovery", [wallet.address], wallet, [owner, guardian1]); + const isLocked = await module.isLocked(wallet.address); assert.isFalse(isLocked, "should no longer be locked by recovery"); await utils.increaseTime(40); // moving time to after the end of the recovery period - const txReceipt = await manager.relay(recoveryManager, "finalizeRecovery", [wallet.address], wallet, []); + const txReceipt = await manager.relay(module, "finalizeRecovery", [wallet.address], wallet, []); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "finalization should have failed"); - assert.equal(error, "RM: there must be an ongoing recovery"); + assert.equal(error, "SM: no ongoing recovery"); const walletOwner = await wallet.owner(); assert.equal(walletOwner, owner, "wallet owner should not have been changed"); }); @@ -243,7 +211,7 @@ contract("RecoveryManager", (accounts) => { it("should not let 1 guardian cancel the recovery procedure", async () => { await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "cancelRecovery", [wallet.address], wallet, @@ -251,14 +219,14 @@ contract("RecoveryManager", (accounts) => { ), WRONG_SIGNATURE_NUMBER_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isTrue(isLocked, "should still be locked"); }); it("should not let the owner cancel the recovery procedure", async () => { await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "cancelRecovery", [wallet.address], wallet, @@ -266,14 +234,14 @@ contract("RecoveryManager", (accounts) => { ), WRONG_SIGNATURE_NUMBER_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isTrue(isLocked, "should still be locked"); }); it("should not allow duplicate guardian signatures", async () => { await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "cancelRecovery", [wallet.address], wallet, @@ -281,14 +249,14 @@ contract("RecoveryManager", (accounts) => { ), INVALID_SIGNATURES_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isTrue(isLocked, "should still be locked"); }); it("should not allow non guardians signatures", async () => { await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "cancelRecovery", [wallet.address], wallet, @@ -296,7 +264,7 @@ contract("RecoveryManager", (accounts) => { ), INVALID_SIGNATURES_REVERT_MSG, ); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isTrue(isLocked, "should still be locked"); }); } @@ -305,7 +273,7 @@ contract("RecoveryManager", (accounts) => { it("should let owner + the majority of guardians execute an ownership transfer", async () => { const majority = guardians.slice(0, Math.ceil((guardians.length) / 2)); - await manager.relay(recoveryManager, "transferOwnership", + await manager.relay(module, "transferOwnership", [wallet.address, newowner], wallet, [owner, ...utils.sortWalletByAddress(majority)]); const walletOwner = await wallet.owner(); assert.equal(walletOwner, newowner, "owner should have been changed"); @@ -315,7 +283,7 @@ contract("RecoveryManager", (accounts) => { const minority = guardians.slice(0, Math.ceil((guardians.length) / 2) - 1); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -331,7 +299,7 @@ contract("RecoveryManager", (accounts) => { const majority = guardians.slice(0, Math.ceil((guardians.length) / 2)); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -344,29 +312,24 @@ contract("RecoveryManager", (accounts) => { }); } - describe("RecoveryManager high level logic", () => { - it("should not be able to instantiate the RecoveryManager with lock period shorter than the recovery period", async () => { - await truffleAssert.reverts(RecoveryManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 36, 35), - "RM: insecure security periods"); - }); - }); - describe("Execute Recovery", () => { it("should not allow recovery to be executed with no guardians", async () => { const noGuardians = []; + + // Revoke the only guardian added at wallet creation + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardian1); + await truffleAssert.reverts(manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, noGuardians, - ), "RM: no guardians set on wallet"); + ), "AM: no guardians set on wallet"); - const isLocked = await lockManager.isLocked(wallet.address); + const isLocked = await module.isLocked(wallet.address); assert.isFalse(isLocked, "should not be locked by recovery"); const walletOwner = await wallet.owner(); @@ -375,7 +338,7 @@ contract("RecoveryManager", (accounts) => { describe("EOA Guardians: G = 2", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2]); + await addGuardians([guardian2]); }); testExecuteRecovery([guardian1, guardian2]); @@ -383,7 +346,7 @@ contract("RecoveryManager", (accounts) => { describe("EOA Guardians: G = 3", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2, guardian3]); + await addGuardians([guardian2, guardian3]); }); testExecuteRecovery([guardian1, guardian2, guardian3]); @@ -392,7 +355,7 @@ contract("RecoveryManager", (accounts) => { const badMajority = [guardian1, guardian1]; await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -405,7 +368,7 @@ contract("RecoveryManager", (accounts) => { describe("Smart Contract Guardians: G = 2", () => { let guardians; beforeEach(async () => { - guardians = await createSmartContractGuardians([guardian1, guardian2]); + guardians = await createSmartContractGuardians([guardian2]); await addGuardians(guardians); }); @@ -415,7 +378,7 @@ contract("RecoveryManager", (accounts) => { describe("Smart Contract Guardians: G = 3", () => { let guardians; beforeEach(async () => { - guardians = await createSmartContractGuardians([guardian1, guardian2, guardian3]); + guardians = await createSmartContractGuardians([guardian2, guardian3]); await addGuardians(guardians); }); @@ -423,97 +386,116 @@ contract("RecoveryManager", (accounts) => { }); describe("Safety checks", () => { - beforeEach(async () => { - await addGuardians([guardian1]); - }); - - it("should not be able to call ExecuteRecovery with an empty recovery address", async () => { - const txReceipt = await manager.relay(recoveryManager, "executeRecovery", + it("should not be able to call executeRecovery with an empty recovery address", async () => { + const txReceipt = await manager.relay(module, "executeRecovery", [wallet.address, ethers.constants.AddressZero], wallet, [guardian1]); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "executeRecovery should fail"); - assert.equal(error, "RM: new owner address cannot be null"); + assert.equal(error, "SM: new owner cannot be null"); }); - it("should not be able to call ExecuteRecovery with a guardian address", async () => { - const txReceipt = await manager.relay(recoveryManager, "executeRecovery", + it("should not be able to call executeRecovery with a guardian address", async () => { + const txReceipt = await manager.relay(module, "executeRecovery", [wallet.address, guardian1], wallet, [guardian1]); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "executeRecovery should fail"); - assert.equal(error, "RM: new owner address cannot be a guardian"); + assert.equal(error, "SM: new owner cannot be guardian"); }); - it("should not be able to call ExecuteRecovery if already in the process of Recovery", async () => { - await manager.relay(recoveryManager, "executeRecovery", + it("should not be able to call executeRecovery if already in the process of Recovery", async () => { + await manager.relay(module, "executeRecovery", [wallet.address, newowner], wallet, utils.sortWalletByAddress([guardian1])); - const txReceipt = await manager.relay(recoveryManager, "executeRecovery", + const txReceipt = await manager.relay(module, "executeRecovery", [wallet.address, ethers.constants.AddressZero], wallet, [guardian1]); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "executeRecovery should fail"); - assert.equal(error, "RM: there cannot be an ongoing recovery"); - }); - - it("should revert if an unknown method is executed", async () => { - const nonce = await utils.getNonceForRelay(); - const chainId = await utils.getChainId(); - let methodData = recoveryManager.contract.methods.executeRecovery(wallet.address, ethers.constants.AddressZero).encodeABI(); - // Replace the `executeRecovery` method signature: b0ba4da0 with a non-existent one: e0b6fcfc - methodData = methodData.replace("b0ba4da0", "e0b6fcfc"); - - const signatures = await utils.signOffchain( - [guardian1], - relayerManager.address, - recoveryManager.address, - 0, - methodData, - chainId, - nonce, - 0, - 700000, - utils.ETH_TOKEN, - ethers.constants.AddressZero, - ); - await truffleAssert.reverts( - relayerManager.execute( - wallet.address, - recoveryManager.address, - methodData, - nonce, - signatures, - 0, - 700000, - utils.ETH_TOKEN, - ethers.constants.AddressZero, - { gasLimit: 800000, from: nonowner2 }, - ), - "RM: unknown method", - ); + assert.equal(error, "SM: ongoing recovery"); }); }); }); describe("Finalize Recovery", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2, guardian3]); + await addGuardians([guardian2, guardian3]); + }); + + it("should let anyone finalize the recovery procedure after the recovery period", async () => { + await manager.relay( + module, + "executeRecovery", + [wallet.address, newowner], + wallet, + utils.sortWalletByAddress([guardian1, guardian2]), + ); + await utils.increaseTime(40); // moving time to after the end of the recovery period + await manager.relay(module, "finalizeRecovery", [wallet.address], wallet, []); + const isLocked = await module.isLocked(wallet.address); + assert.isFalse(isLocked, "should no longer be locked after finalization of recovery"); + const walletOwner = await wallet.owner(); + assert.equal(walletOwner, newowner, "wallet owner should have been changed"); + + const recoveryConfig = await module.getRecovery(wallet.address); + assert.equal(recoveryConfig._address, ethers.constants.AddressZero); + assert.equal(recoveryConfig._executeAfter.toNumber(), 0); + assert.equal(recoveryConfig._guardianCount, 0); + }); + + it("should not let anyone finalize the recovery procedure before the end of the recovery period", async () => { await manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, utils.sortWalletByAddress([guardian1, guardian2]), ); + const txReceipt = await manager.relay(module, "finalizeRecovery", [wallet.address], wallet, []); + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "SM: ongoing recovery period"); + + const isLocked = await module.isLocked(wallet.address); + assert.isTrue(isLocked, "should still be locked"); }); - testFinalizeRecovery(); + it("session should be cleared", async () => { + // start sesesion + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], accounts[6], 1000], + wallet, + [owner, ...utils.sortWalletByAddress([guardian1, guardian2])], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + let session = await module.getSession(wallet.address); + assert.equal(session.key, accounts[6]); + + // start recovery + await manager.relay( + module, + "executeRecovery", + [wallet.address, newowner], + wallet, + utils.sortWalletByAddress([guardian1, guardian2]), + ); + await utils.increaseTime(40); // moving time to after the end of the recovery period + + // finalize recovery and check the session has been cleared + await manager.relay(module, "finalizeRecovery", [wallet.address], wallet, []); + session = await module.getSession(wallet.address); + assert.equal(session.key, ZERO_ADDRESS); + }); }); describe("Cancel Recovery with 3 guardians", () => { describe("EOA Guardians", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2, guardian3]); + await addGuardians([guardian2, guardian3]); await manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -525,10 +507,10 @@ contract("RecoveryManager", (accounts) => { }); describe("Smart Contract Guardians", () => { beforeEach(async () => { - const scGuardians = await createSmartContractGuardians([guardian1, guardian2, guardian3]); + const scGuardians = await createSmartContractGuardians([guardian2, guardian3]); await addGuardians(scGuardians); await manager.relay( - recoveryManager, + module, "executeRecovery", [wallet.address, newowner], wallet, @@ -542,32 +524,35 @@ contract("RecoveryManager", (accounts) => { describe("Ownership Transfer", () => { it("should not allow transfer to an empty address", async () => { - await addGuardians([guardian1]); const txReceipt = await manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, ethers.constants.AddressZero], wallet, [owner, guardian1], ); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "transferOwnership should fail"); - assert.equal(error, "RM: new owner address cannot be null"); + assert.equal(error, "SM: new owner cannot be null"); }); it("should not allow transfer to a guardian address", async () => { - await addGuardians([guardian1]); const txReceipt = await manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, guardian1], wallet, [owner, guardian1], ); const { success, error } = utils.parseRelayReceipt(txReceipt); assert.isFalse(success, "transferOwnership should fail"); - assert.equal(error, "RM: new owner address cannot be a guardian"); + assert.equal(error, "SM: new owner cannot be guardian"); }); it("when no guardians, owner should be able to transfer alone", async () => { + // Revoke the only guardian added at wallet creation + await module.revokeGuardian(wallet.address, guardian1, { from: owner }); + await utils.increaseTime(30); + await module.confirmGuardianRevokation(wallet.address, guardian1); + const txReceipt = await manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -578,10 +563,9 @@ contract("RecoveryManager", (accounts) => { }); it("should not allow owner not signing", async () => { - await addGuardians([guardian1]); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -591,10 +575,9 @@ contract("RecoveryManager", (accounts) => { }); it("should not allow duplicate owner signatures", async () => { - await addGuardians([guardian1]); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -604,10 +587,10 @@ contract("RecoveryManager", (accounts) => { }); it("should not allow duplicate guardian signatures", async () => { - await addGuardians([guardian1, guardian2, guardian3]); + await addGuardians([guardian2, guardian3]); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -617,10 +600,9 @@ contract("RecoveryManager", (accounts) => { }); it("should not allow non guardian signatures", async () => { - await addGuardians([guardian1]); await truffleAssert.reverts( manager.relay( - recoveryManager, + module, "transferOwnership", [wallet.address, newowner], wallet, @@ -630,16 +612,12 @@ contract("RecoveryManager", (accounts) => { }); describe("Guardians: G = 1", () => { - beforeEach(async () => { - await addGuardians([guardian1]); - }); - testOwnershipTransfer([guardian1]); }); describe("Guardians: G = 2", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2]); + await addGuardians([guardian2]); }); testOwnershipTransfer([guardian1, guardian2]); @@ -647,10 +625,31 @@ contract("RecoveryManager", (accounts) => { describe("Guardians: G = 3", () => { beforeEach(async () => { - await addGuardians([guardian1, guardian2, guardian3]); + await addGuardians([guardian2, guardian3]); }); testOwnershipTransfer([guardian1, guardian2, guardian3]); }); }); + + describe("benchmark", () => { + it("should recover wallet with 1 guardian", async () => { + const txReceipt = await manager.relay( + module, + "executeRecovery", + [wallet.address, newowner], + wallet, + [guardian1]); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "execute recovery failed"); + console.log("Gas to execute recovery: ", txReceipt.gasUsed); + + await utils.increaseTime(40); + + const tx = await module.finalizeRecovery(wallet.address, { from: infrastructure }); + const walletOwner = await wallet.owner(); + assert.equal(walletOwner, newowner, "wallet owner should have been changed"); + console.log("Gas to finalize recovery: ", tx.receipt.gasUsed); + }); + }); }); diff --git a/test/relayer.js b/test/relayer.js index e362fc68c..e14d59519 100644 --- a/test/relayer.js +++ b/test/relayer.js @@ -4,175 +4,137 @@ const ethers = require("ethers"); const chai = require("chai"); const BN = require("bn.js"); const bnChai = require("bn-chai"); -const { formatBytes32String } = require("ethers").utils; -const utils = require("../utils/utilities.js"); -const { ETH_TOKEN } = require("../utils/utilities.js"); -const { expect } = chai; +const { assert, expect } = chai; chai.use(bnChai(BN)); -const Proxy = artifacts.require("Proxy"); +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +const WalletFactory = artifacts.require("WalletFactory"); const BaseWallet = artifacts.require("BaseWallet"); -const BadFeature = artifacts.require("BadFeature"); -const RelayerManager = artifacts.require("RelayerManager"); -const TestFeature = artifacts.require("TestFeature"); -const TestLimitFeature = artifacts.require("TestLimitFeature"); const Registry = artifacts.require("ModuleRegistry"); -const GuardianManager = artifacts.require("GuardianManager"); +const TransferStorage = artifacts.require("TransferStorage"); const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const ApprovedTransfer = artifacts.require("ApprovedTransfer"); -const RecoveryManager = artifacts.require("RecoveryManager"); // non-owner only feature -const VersionManager = artifacts.require("VersionManager"); +const ArgentModule = artifacts.require("ArgentModuleTest"); +const DappRegistry = artifacts.require("DappRegistry"); +const PriceOracle = artifacts.require("TestSimpleOracle"); const ERC20 = artifacts.require("TestERC20"); +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, addTrustedContact, initNonce } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + const RelayManager = require("../utils/relay-manager"); -contract("RelayerManager", (accounts) => { - const manager = new RelayManager(); +contract("ArgentModule - Relayer", (accounts) => { + let manager; const infrastructure = accounts[0]; const owner = accounts[1]; - const recipient = accounts[3]; - const guardian = accounts[4]; + const guardian1 = accounts[2]; + const recipient = accounts[4]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; let registry; + let transferStorage; let guardianStorage; - let lockStorage; - let limitStorage; - let guardianManager; - let recoveryManager; + let module; let wallet; - let approvedTransfer; - let testFeature; - let testFeatureNew; - let relayerManager; - let tokenPriceRegistry; - let limitFeature; - let badFeature; - let versionManager; - let versionManagerV2; + let factory; + let token; + let dappRegistry; + let priceOracle; before(async () => { + // deploy Uniswap V2 + const weth = await WETH.new(); + token = await ERC20.new([infrastructure], web3.utils.toWei("1000"), 18); + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + const uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + await token.approve(uniswapRouter.address, web3.utils.toWei("600")); + const timestamp = await utils.getTimestamp(); + await uniswapRouter.addLiquidityETH( + token.address, + web3.utils.toWei("600"), + 1, + 1, + infrastructure, + timestamp + 300, + { value: web3.utils.toWei("300") } + ); + + // deploy Argent registry = await Registry.new(); guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - limitStorage = await LimitStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - limitStorage.address); - versionManagerV2 = await VersionManager.new( + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + + module = await ArgentModule.new( registry.address, - lockStorage.address, guardianStorage.address, - ethers.constants.AddressZero, - limitStorage.address); + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); - tokenPriceRegistry = await TokenPriceRegistry.new(); - await tokenPriceRegistry.addManager(infrastructure); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address); - await manager.setRelayerManager(relayerManager); - }); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); - beforeEach(async () => { - approvedTransfer = await ApprovedTransfer.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - versionManager.address, - ethers.constants.AddressZero); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, - 12); - recoveryManager = await RecoveryManager.new( - lockStorage.address, + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, guardianStorage.address, - versionManager.address, - 36, 24 * 5); + refundAddress); + await factory.addManager(infrastructure); - testFeature = await TestFeature.new(lockStorage.address, versionManager.address, 0); - testFeatureNew = await TestFeature.new(lockStorage.address, versionManager.address, 0); - - limitFeature = await TestLimitFeature.new( - lockStorage.address, limitStorage.address, versionManager.address); - badFeature = await BadFeature.new(lockStorage.address, versionManager.address); + priceOracle = await PriceOracle.new(uniswapRouter.address); + manager = new RelayManager(guardianStorage.address, priceOracle.address); + }); - const walletImplementation = await BaseWallet.new(); - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - for (const vm of [versionManager, versionManagerV2]) { - await vm.addVersion([ - relayerManager.address, - approvedTransfer.address, - guardianManager.address, - recoveryManager.address, - testFeature.address, - limitFeature.address, - badFeature.address, - ], []); - } - - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + await wallet.send(web3.utils.toWei("1")); + await token.transfer(wallet.address, web3.utils.toWei("1")); }); - describe("relaying feature transactions", () => { + describe("relay transactions", () => { it("should fail when _data is less than 36 bytes", async () => { - const params = []; // the first argument is not the wallet address, which should make the relaying revert - await truffleAssert.reverts( - manager.relay(testFeature, "clearInt", params, wallet, [owner]), "RM: Invalid dataWallet", - ); - }); - - it("should fail when feature is not authorised", async () => { - const params = [wallet.address, 2]; + const nonce = await utils.getNonceForRelay(); + const gasLimit = 2000000; await truffleAssert.reverts( - manager.relay(testFeatureNew, "setIntOwnerOnly", params, wallet, [owner]), "RM: feature not authorised", - ); - }); - - it("should fail when the RelayerManager is not authorised", async () => { - await versionManager.addVersion([testFeature.address], []); - const wrongWallet = await BaseWallet.new(); - await wrongWallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wrongWallet.address, await versionManager.lastVersion(), { from: owner }); - const params = [wrongWallet.address, 2]; - const txReceipt = await manager.relay(testFeature, "setIntOwnerOnly", params, wrongWallet, [owner]); - - const { success, error } = utils.parseRelayReceipt(txReceipt); - assert.isFalse(success); - assert.equal(error, "BF: must be owner or feature"); - - // reset last version to default bundle - await versionManager.addVersion([ - relayerManager.address, - approvedTransfer.address, - guardianManager.address, - recoveryManager.address, - testFeature.address, - limitFeature.address, - badFeature.address, - ], []); + module.execute( + wallet.address, + "0xf435f5a7", + nonce, + "0xdeadbeef", + 0, + gasLimit, + ETH_TOKEN, + ethers.constants.AddressZero, + { gas: 2 * gasLimit, gasPrice: 0, from: relayer } + ), "RM: Invalid dataWallet"); }); it("should fail when the first param is not the wallet", async () => { - const params = [owner, 4]; + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); await truffleAssert.reverts( - manager.relay(testFeature, "setIntOwnerOnly", params, wallet, [owner]), "RM: Target of _data != _wallet", + manager.relay(module, "multiCall", [infrastructure, [transaction]], wallet, [owner]), + "RM: Target of _data != _wallet" ); }); @@ -181,9 +143,8 @@ contract("RelayerManager", (accounts) => { const gasLimit = 2000000; await truffleAssert.reverts( - relayerManager.execute( + module.execute( wallet.address, - testFeature.address, "0xdeadbeef", nonce, "0xdeadbeef", @@ -191,231 +152,206 @@ contract("RelayerManager", (accounts) => { gasLimit, ETH_TOKEN, ethers.constants.AddressZero, - { gas: gasLimit * 0.9, gasPrice: 0, from: accounts[9] } + { gas: gasLimit * 0.9, gasPrice: 0, from: relayer } ), "RM: not enough gas provided"); }); it("should fail when a wrong number of signatures is provided", async () => { - const params = [wallet.address, 2]; + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); await truffleAssert.reverts( - manager.relay(testFeature, "setIntOwnerOnly", params, wallet, [owner, recipient]), + manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner, recipient]), "RM: Wrong number of signatures" ); }); it("should fail a duplicate transaction", async () => { - const methodData = testFeature.contract.methods.setIntOwnerOnly(wallet.address, 2).encodeABI(); const nonce = await utils.getNonceForRelay(); const chainId = await utils.getChainId(); - + const gasLimit = 100000; + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + const methodData = module.contract.methods.multiCall(wallet.address, [transaction]).encodeABI(); const signatures = await utils.signOffchain( [owner], - relayerManager.address, - testFeature.address, + module.address, 0, methodData, chainId, nonce, 0, - 0, + gasLimit, ETH_TOKEN, ethers.constants.AddressZero, ); - await relayerManager.execute( + await module.execute( wallet.address, - testFeature.address, methodData, nonce, signatures, 0, - 0, + gasLimit, ETH_TOKEN, - ethers.constants.AddressZero, - { from: accounts[9] }); + ethers.constants.AddressZero); await truffleAssert.reverts( - relayerManager.execute( + module.execute( wallet.address, - testFeature.address, methodData, nonce, signatures, 0, - 0, + gasLimit, ETH_TOKEN, - ethers.constants.AddressZero, - { from: accounts[9] } - ), "RM: Duplicate request"); - }); - - it("should fail when relaying to itself", async () => { - const methodData = testFeature.contract.methods.setIntOwnerOnly(wallet.address, 2).encodeABI(); - const params = [ - wallet.address, - testFeature.address, - methodData, - 0, - ethers.constants.HashZero, - 0, - 200000, - ETH_TOKEN, - ethers.constants.AddressZero, - ]; - await truffleAssert.reverts( - manager.relay(relayerManager, "execute", params, wallet, [owner]), "BF: disabled method", - ); + ethers.constants.AddressZero), + "RM: Duplicate request"); }); it("should update the nonce after the transaction", async () => { - const nonceBefore = await relayerManager.getNonce(wallet.address); - await manager.relay(testFeature, "setIntOwnerOnly", [wallet.address, 2], wallet, [owner]); + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); - const nonceAfter = await relayerManager.getNonce(wallet.address); + const nonceBefore = await module.getNonce(wallet.address); + await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const nonceAfter = await module.getNonce(wallet.address); expect(nonceAfter).to.be.gt.BN(nonceBefore); }); }); - describe("refund relayed transactions", () => { - let erc20; + describe("refund", () => { beforeEach(async () => { - const decimals = 12; // number of decimal for TOKN contract - erc20 = await ERC20.new([infrastructure], 10000000, decimals); // TOKN contract with 10M tokens (10M TOKN for account[0]) - - // Prices stored in registry = price per token * 10^(18-token decimals) - const tokenRate = new BN(10).pow(new BN(19)).mul(new BN(51)); // 1 TOKN = 0.00051 ETH = 0.00051*10^18 ETH wei => *10^(18-decimals) = 0.00051*10^18 * 10^6 = 0.00051*10^24 = 51*10^19 - await tokenPriceRegistry.setPriceForTokenList([erc20.address], [tokenRate]); - - await limitFeature.setLimitAndDailySpent(wallet.address, 10000000000, 0); + await initNonce(wallet, module, manager, SECURITY_PERIOD); + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); }); - async function callAndRefund({ refundToken }) { - const relayParams = [ - testFeature, - "setIntOwnerOnly", - [wallet.address, 2], + it("should refund in ETH", async () => { + // eth balance + const balanceStart = await utils.getBalance(wallet.address); + // send erc20 + const data = token.contract.methods.transfer(recipient, 100).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], wallet, [owner], - 10000, - refundToken, - recipient]; - const txReceipt = await manager.relay(...relayParams); - return txReceipt; - } + 1, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + const balanceEnd = await utils.getBalance(wallet.address); + expect(balanceEnd.sub(balanceStart)).to.be.lt.BN(0); - it("should refund in ETH", async () => { - await wallet.send("100000000000000"); - const wBalanceStart = await utils.getBalance(wallet.address); - const rBalanceStart = await utils.getBalance(recipient); - await callAndRefund({ refundToken: ETH_TOKEN }); - const wBalanceEnd = await utils.getBalance(wallet.address); - const rBalanceEnd = await utils.getBalance(recipient); - const refund = wBalanceStart.sub(wBalanceEnd); - // should have refunded ETH - expect(refund).to.be.gt.BN(0); - // should have refunded the recipient - expect(refund).to.eq.BN(rBalanceEnd.sub(rBalanceStart)); + console.log("Gas for relaying an ERC20 transfer with refund in ETH:", txReceipt.gasUsed); }); it("should refund in ERC20", async () => { - await erc20.transfer(wallet.address, "10000000000"); - const wBalanceStart = await erc20.balanceOf(wallet.address); - const rBalanceStart = await erc20.balanceOf(recipient); - await callAndRefund({ refundToken: erc20.address }); - const wBalanceEnd = await erc20.balanceOf(wallet.address); - const rBalanceEnd = await erc20.balanceOf(recipient); - const refund = wBalanceStart.sub(wBalanceEnd); - // should have refunded ERC20 - expect(refund).to.be.gt.BN(0); - // should have refunded the recipient - expect(refund).to.eq.BN(rBalanceEnd.sub(rBalanceStart)); + // token balance + const balanceStart = await token.balanceOf(relayer); + // send ETH + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + token.address, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + const balanceEnd = await token.balanceOf(relayer); + expect(balanceEnd.sub(balanceStart)).to.be.gt.BN(0); + + console.log("Gas for relaying an ETH transfer with refund in ERC20:", txReceipt.gasUsed); }); it("should emit the Refund event", async () => { - await wallet.send("100000000000"); - const txReceipt = await callAndRefund({ refundToken: ETH_TOKEN }); - await utils.hasEvent(txReceipt, relayerManager, "Refund"); - }); - - it("should fail the transaction when there is not enough ETH for the refund", async () => { - await wallet.send(10); - await truffleAssert.reverts(callAndRefund({ refundToken: ETH_TOKEN }), "VM: wallet invoke reverted"); + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + await utils.hasEvent(txReceipt, module, "Refund"); }); - it("should fail the transaction when there is not enough ERC20 for the refund", async () => { - await erc20.transfer(wallet.address, 10); - await truffleAssert.reverts(callAndRefund({ refundToken: erc20.address }), "ERC20: transfer amount exceeds balance"); - }); + it("should fail when there is not enough ETH to refund", async () => { + const balance = await utils.getBalance(wallet.address); + const transaction = encodeTransaction(recipient, balance.toString(), ZERO_BYTES); - it("should include the refund in the daily limit", async () => { - await wallet.send("100000000000"); - await limitFeature.setLimitAndDailySpent(wallet.address, "100000000000000000", 10); - let dailySpent = await limitFeature.getDailySpent(wallet.address); - expect(dailySpent).to.eq.BN(10); - await callAndRefund({ refundToken: ETH_TOKEN }); - dailySpent = await limitFeature.getDailySpent(wallet.address); - expect(dailySpent).to.be.gt.BN(10); + await truffleAssert.reverts( + manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer) + ); }); - it("should refund and reset the daily limit when approved by guardians", async () => { - // set funds and limit/daily spent - await wallet.send("100000000000"); - await limitFeature.setLimitAndDailySpent(wallet.address, 1000000000, 10); - let dailySpent = await limitFeature.getDailySpent(wallet.address); - // initial daily spent should be 10 - expect(dailySpent).to.eq.BN(10); - const rBalanceStart = await utils.getBalance(recipient); - // add a guardian - await guardianManager.addGuardian(wallet.address, guardian, { from: owner }); - // call approvedTransfer - const params = [wallet.address, ETH_TOKEN, recipient, 1000, ethers.constants.HashZero]; - await manager.relay(approvedTransfer, "transferToken", params, wallet, [owner, guardian]); - - dailySpent = await limitFeature.getDailySpent(wallet.address); - // daily spent should be reset - expect(dailySpent).to.be.zero; - const rBalanceEnd = await utils.getBalance(recipient); - expect(rBalanceEnd).to.be.gt.BN(rBalanceStart); - }); + it("should fail when there is not enough ERC20 to refund", async () => { + const balance = await token.balanceOf(wallet.address); + const data = token.contract.methods.transfer(recipient, balance.toString()).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); - it("should fail if required signatures is 0 and OwnerRequirement is not Anyone", async () => { await truffleAssert.reverts( - manager.relay(badFeature, "setIntOwnerOnly", [wallet.address, 2], wallet, [owner]), "RM: Wrong signature requirement", + manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + token.address, + relayer) ); }); - it("should fail the transaction when the refund is over the daily limit", async () => { - await wallet.send("100000000000"); - await limitFeature.setLimitAndDailySpent(wallet.address, 1000000000, 999999990); - const dailySpent = await limitFeature.getDailySpent(wallet.address); - expect(dailySpent).to.eq.BN(999999990); - await truffleAssert.reverts(callAndRefund({ refundToken: ETH_TOKEN }), "RM: refund is above daily limit"); - }); - }); - - describe("addModule transactions", () => { - it("should succeed when relayed on VersionManager", async () => { - await registry.registerModule(versionManagerV2.address, formatBytes32String("versionManagerV2")); - const params = [wallet.address, versionManagerV2.address]; - await manager.relay(versionManager, "addModule", params, wallet, [owner]); - - const isModuleAuthorised = await wallet.authorised(versionManagerV2.address); - assert.isTrue(isModuleAuthorised); - await registry.deregisterModule(versionManagerV2.address); + it("should fail when wallet is locked", async () => { + await module.lock(wallet.address, { from: guardian1 }); + await truffleAssert.reverts( + manager.relay( + module, + "multiCall", + [wallet.address, []], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer), + "RM: Locked wallet refund" + ); }); - it("should succeed when called directly on VersionManager", async () => { - await registry.registerModule(versionManagerV2.address, formatBytes32String("versionManagerV2")); - await versionManager.addModule(wallet.address, versionManagerV2.address, { from: owner }); + it("should succeed when wallet is locked and refund is 0", async () => { + await module.lock(wallet.address, { from: guardian1 }); - const isModuleAuthorised = await wallet.authorised(versionManagerV2.address); - assert.isTrue(isModuleAuthorised); - await registry.deregisterModule(versionManagerV2.address); - }); - - it("should fail to add module which is not registered", async () => { - await truffleAssert.reverts(versionManager.addModule(wallet.address, versionManagerV2.address, { from: owner }), - "VM: module is not registered"); + const txReceipt = await manager.relay( + module, + "revokeGuardian", + [wallet.address, guardian1], + wallet, + [owner]); + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); }); }); }); diff --git a/test/session.js b/test/session.js new file mode 100644 index 000000000..08ffdc61f --- /dev/null +++ b/test/session.js @@ -0,0 +1,391 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert, expect } = chai; +chai.use(bnChai(BN)); + +const truffleAssert = require("truffle-assertions"); + +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ERC20 = artifacts.require("TestERC20"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); +const WalletFactory = artifacts.require("WalletFactory"); + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, initNonce } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("ArgentModule sessions", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const recipient = accounts[4]; + const sessionUser = accounts[6]; + const sessionUser2 = accounts[7]; + const refundAddress = accounts[8]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let dappRegistry; + let token; + let factory; + + before(async () => { + registry = await Registry.new(); + + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + token = await ERC20.new([infrastructure], web3.utils.toWei("10000"), 19); + }); + + beforeEach(async () => { + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + await wallet.send(new BN("1000000000000000000")); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + describe("session lifecycle", () => { + it("owner plus majority guardians should be able to start a session", async () => { + const txReceipt = await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 1000], + wallet, + [owner, guardian1], + 1, + ETH_TOKEN, + recipient); + + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + + const session = await module.getSession(wallet.address); + assert.equal(session.key, sessionUser); + + const timestamp = await utils.getTimestamp(txReceipt.blockNumber); + expect(session.expires).to.eq.BN(timestamp + 1000); + + console.log(`Gas for starting a session: ${txReceipt.gasUsed}`); + }); + + it("should be able to overwrite an existing session", async () => { + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 1000], + wallet, + [owner, guardian1], + 1, + ETH_TOKEN, + recipient); + + // Start another session on the same wallet for sessionUser2 with duration 2000s + const txReceipt = await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser2, 2000], + wallet, + [owner, guardian1], + 1, + ETH_TOKEN, + recipient); + + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + + const session = await module.getSession(wallet.address); + assert.equal(session.key, sessionUser2); + + const timestamp = await utils.getTimestamp(txReceipt.blockNumber); + expect(session.expires).to.eq.BN(timestamp + 2000); + }); + + it("should not be able to start a session for empty user address", async () => { + const txReceipt = await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], ZERO_ADDRESS, 1000], + wallet, + [owner, guardian1], + 1, + ETH_TOKEN, + recipient); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "TM: Invalid session user"); + }); + + it("should not be able to start a session for zero duration", async () => { + const txReceipt = await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 0], + wallet, + [owner, guardian1], + 1, + ETH_TOKEN, + recipient); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "TM: Invalid session duration"); + }); + + it("owner should be able to clear a session", async () => { + // Start a session for sessionUser with duration 1000s + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 1000], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + // owner clears the session + const txReceipt = await manager.relay( + module, + "clearSession", + [wallet.address], + wallet, + [owner], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + const { success } = utils.parseRelayReceipt(txReceipt); + assert.isTrue(success); + + const session = await module.getSession(wallet.address); + assert.equal(session.key, ZERO_ADDRESS); + + expect(session.expires).to.eq.BN(0); + }); + + it("non-owner should not be able to clear a session", async () => { + // Start a session for sessionUser with duration 1000s + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 1000], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + // owner clears the session + await truffleAssert.reverts(manager.relay( + module, + "clearSession", + [wallet.address], + wallet, + [accounts[8]], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS), "RM: Invalid signatures"); + }); + + it("should not be able to clear a session when wallet is locked", async () => { + // Start a session for sessionUser with duration 1000s + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 1000], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + // Lock wallet + await module.lock(wallet.address, { from: guardian1 }); + + // owner clears the session + const txReceipt = await manager.relay( + module, + "clearSession", + [wallet.address], + wallet, + [owner], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "BM: wallet locked"); + }); + }); + + describe("approved transfer (without using a session)", () => { + it("should be able to send ETH with guardians", async () => { + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + + const balanceBefore = await utils.getBalance(recipient); + const txReceipt = await manager.relay( + module, + "multiCallWithGuardians", + [wallet.address, [transaction]], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success); + + const balanceAfter = await utils.getBalance(recipient); + expect(balanceAfter.sub(balanceBefore)).to.eq.BN(10); + console.log(`Gas for send ETH with guardians: ${txReceipt.gasUsed}`); + }); + + it("should be able to transfer ERC20 with guardians", async () => { + await token.transfer(wallet.address, 10); + const data = await token.contract.methods.transfer(recipient, 10).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); + + const balanceBefore = await token.balanceOf(recipient); + const txReceipt = await manager.relay( + module, + "multiCallWithGuardians", + [wallet.address, [transaction]], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success); + + const balanceAfter = await token.balanceOf(recipient); + expect(balanceAfter.sub(balanceBefore)).to.eq.BN(10); + }); + }); + + describe("transfer using session", () => { + beforeEach(async () => { + // Create a session for sessionUser with duration 1000s to use in tests + await manager.relay( + module, + "multiCallWithGuardiansAndStartSession", + [wallet.address, [], sessionUser, 10000], + wallet, + [owner, guardian1], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + }); + + it("should be able to send ETH", async () => { + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + + const balanceBefore = await utils.getBalance(recipient); + const txReceipt = await manager.relay( + module, + "multiCallWithSession", + [wallet.address, [transaction]], + wallet, + [sessionUser], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success); + + const balanceAfter = await utils.getBalance(recipient); + expect(balanceAfter.sub(balanceBefore)).to.eq.BN(10); + }); + + it("should be able to transfer ERC20", async () => { + await token.transfer(wallet.address, 10); + const data = await token.contract.methods.transfer(recipient, 10).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); + + const balanceBefore = await token.balanceOf(recipient); + const txReceipt = await manager.relay( + module, + "multiCallWithSession", + [wallet.address, [transaction]], + wallet, + [sessionUser], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success); + + const balanceAfter = await token.balanceOf(recipient); + expect(balanceAfter.sub(balanceBefore)).to.eq.BN(10); + }); + + it("should not be able to send ETH with invalid session", async () => { + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + + await truffleAssert.reverts(manager.relay( + module, + "multiCallWithSession", + [wallet.address, [transaction]], + wallet, + [sessionUser2], + 0, + ZERO_ADDRESS, + ZERO_ADDRESS), "RM: Invalid session"); + }); + }); +}); diff --git a/test/simpleUpgrader.js b/test/simpleUpgrader.js deleted file mode 100644 index 27ff35b6d..000000000 --- a/test/simpleUpgrader.js +++ /dev/null @@ -1,332 +0,0 @@ -/* global artifacts */ - -const truffleAssert = require("truffle-assertions"); -const ethers = require("ethers"); -const { formatBytes32String, parseBytes32String } = require("ethers").utils; -const utils = require("../utils/utilities.js"); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const SimpleUpgrader = artifacts.require("SimpleUpgrader"); -const GuardianManager = artifacts.require("GuardianManager"); -const LockManager = artifacts.require("LockManager"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const Registry = artifacts.require("ModuleRegistry"); -const RecoveryManager = artifacts.require("RecoveryManager"); -const VersionManager = artifacts.require("VersionManager"); -const RelayerManager = artifacts.require("RelayerManager"); - -const RelayManager = require("../utils/relay-manager"); - -contract("SimpleUpgrader", (accounts) => { - const manager = new RelayManager(); - - const owner = accounts[1]; - let registry; - let guardianStorage; - let lockStorage; - let walletImplementation; - let wallet; - - before(async () => { - walletImplementation = await BaseWallet.new(); - }); - - beforeEach(async () => { - registry = await Registry.new(); - guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - }); - - async function deployTestModule() { - const module = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - const relayer = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - module.address); - await module.addVersion([relayer.address], []); - return { module, relayer }; - } - - describe("Registering modules", () => { - it("should register modules in the registry", async () => { - const name = "test_1.1"; - const { module: initialModule } = await deployTestModule(); - await registry.registerModule(initialModule.address, formatBytes32String(name)); - const isRegistered = await registry.contract.methods["isRegisteredModule(address[])"]([initialModule.address]).call(); - assert.equal(isRegistered, true, "module1 should be registered"); - const info = await registry.moduleInfo(initialModule.address); - assert.equal(parseBytes32String(info), name, "module1 should be registered with the correct name"); - }); - - it("should add registered modules to a wallet", async () => { - // create modules - const { module: initialModule } = await deployTestModule(); - const { module: moduleToAdd } = await deployTestModule(); - // register module - await registry.registerModule(initialModule.address, formatBytes32String("initial")); - await registry.registerModule(moduleToAdd.address, formatBytes32String("added")); - - await wallet.init(owner, [initialModule.address]); - await initialModule.upgradeWallet(wallet.address, await initialModule.lastVersion(), { from: owner }); - let isAuthorised = await wallet.authorised(initialModule.address); - assert.equal(isAuthorised, true, "initial module should be authorised"); - // add module to wallet - await initialModule.addModule(wallet.address, moduleToAdd.address, { from: owner }); - - isAuthorised = await wallet.authorised(moduleToAdd.address); - assert.equal(isAuthorised, true, "added module should be authorised"); - }); - - it("should block addition of unregistered modules to a wallet", async () => { - // create modules - const { module: initialModule } = await deployTestModule(); - const { module: moduleToAdd } = await deployTestModule(); - // register initial module only - await registry.registerModule(initialModule.address, formatBytes32String("initial")); - - await wallet.init(owner, [initialModule.address]); - await initialModule.upgradeWallet(wallet.address, await initialModule.lastVersion(), { from: owner }); - let isAuthorised = await wallet.authorised(initialModule.address); - assert.equal(isAuthorised, true, "initial module should be authorised"); - // try (and fail) to add moduleToAdd to wallet - await truffleAssert.reverts(initialModule.addModule(wallet.address, moduleToAdd.address, { from: owner }), "VM: module is not registered"); - isAuthorised = await wallet.authorised(moduleToAdd.address); - assert.equal(isAuthorised, false, "unregistered module should not be authorised"); - }); - - it("should not be able to upgrade to unregistered module", async () => { - // create module V1 - const { module: moduleV1 } = await deployTestModule(); - // register module V1 - await registry.registerModule(moduleV1.address, formatBytes32String("V1")); - - await wallet.init(owner, [moduleV1.address]); - await moduleV1.upgradeWallet(wallet.address, await moduleV1.lastVersion(), { from: owner }); - // create module V2 - const { module: moduleV2 } = await deployTestModule(); - // create upgrader - const upgrader = await SimpleUpgrader.new( - registry.address, lockStorage.address, [moduleV1.address], [moduleV2.address]); - await registry.registerModule(upgrader.address, formatBytes32String("V1toV2")); - - // check we can't upgrade from V1 to V2 - await truffleAssert.reverts(moduleV1.addModule(wallet.address, upgrader.address, { from: owner }), "SU: Not all modules are registered"); - // register module V2 - await registry.registerModule(moduleV2.address, formatBytes32String("V2")); - // now we can upgrade - await moduleV1.addModule(wallet.address, upgrader.address, { from: owner }); - - // test if the upgrade worked - const isV1Authorised = await wallet.authorised(moduleV1.address); - const isV2Authorised = await wallet.authorised(moduleV2.address); - const isUpgraderAuthorised = await wallet.authorised(upgrader.address); - const numModules = await wallet.modules(); - assert.isFalse(isV1Authorised, "moduleV1 should be unauthorised"); - assert.isTrue(isV2Authorised, "moduleV2 should be authorised"); - assert.equal(isUpgraderAuthorised, false, "upgrader should not be authorised"); - assert.equal(numModules, 1, "only one module (moduleV2) should be authorised"); - }); - }); - - describe("Upgrading modules", () => { - async function testUpgradeModule({ relayed, modulesToAdd = (moduleV2) => [moduleV2] }) { - // create module V1 - const { module: moduleV1, relayer: relayerV1 } = await deployTestModule(); - await manager.setRelayerManager(relayerV1); - // register module V1 - await registry.registerModule(moduleV1.address, formatBytes32String("V1")); - // create wallet with module V1 and relayer feature - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [moduleV1.address]); - await moduleV1.upgradeWallet(wallet.address, await moduleV1.lastVersion(), { from: owner }); - - // create module V2 - const { module: moduleV2 } = await deployTestModule(); - // register module V2 - await registry.registerModule(moduleV2.address, formatBytes32String("V2")); - // create upgraders - const toAdd = modulesToAdd(moduleV2.address); - const upgrader1 = await SimpleUpgrader.new( - registry.address, lockStorage.address, [moduleV1.address], toAdd); - const upgrader2 = await SimpleUpgrader.new( - registry.address, lockStorage.address, [moduleV1.address], toAdd); - await registry.registerModule(upgrader1.address, formatBytes32String("V1toV2_1")); - await registry.registerModule(upgrader2.address, formatBytes32String("V1toV2_2")); - - // upgrade from V1 to V2 - let txReceipt; - const params1 = [wallet.address, upgrader1.address]; - const params2 = [wallet.address, upgrader2.address]; - // if no module is added and all modules are removed, the upgrade should fail - if (toAdd.length === 0) { - if (relayed) { - txReceipt = await manager.relay(moduleV1, "addModule", params2, wallet, [owner]); - const event = await utils.getEvent(txReceipt, relayerV1, "TransactionExecuted"); - assert.isTrue(!event.args.success, "Relayed upgrade to 0 module should have failed."); - } else { - truffleAssert.reverts(moduleV1.addModule(...params2, { from: owner }), "BW: wallet must have at least one module"); - } - return; - } - if (relayed) { - txReceipt = await manager.relay(moduleV1, "addModule", params1, wallet, [owner]); - const event = await utils.getEvent(txReceipt, relayerV1, "TransactionExecuted"); - assert.isTrue(event.args.success, "Relayed tx should only have succeeded if an OnlyOwnerModule was used"); - } else { - const tx = await moduleV1.addModule(...params1, { from: owner }); - txReceipt = tx.receipt; - } - - // test event ordering - const event = await utils.getEvent(txReceipt, wallet, "AuthorisedModule"); - assert.equal(event.args.module, upgrader1.address); - assert.isTrue(event.args.value); - - // test if the upgrade worked - const isV1Authorised = await wallet.authorised(moduleV1.address); - const isV2Authorised = await wallet.authorised(moduleV2.address); - const isUpgraderAuthorised = await wallet.authorised(upgrader1.address); - const numModules = await wallet.modules(); - assert.equal(isV1Authorised, false, "moduleV1 should be unauthorised"); - assert.equal(isV2Authorised, true, "moduleV2 should be authorised"); - assert.equal(isUpgraderAuthorised, false, "upgrader should not be authorised"); - assert.equal(numModules.toNumber(), 1, "only 1 module (moduleV2) should be authorised"); - } - - it("should upgrade modules (relayed tx)", async () => { - await testUpgradeModule({ relayed: true }); - }); - - it("should not upgrade to 0 module (blockchain tx)", async () => { - // we intentionally try to add 0 module, this should fail - await testUpgradeModule({ relayed: false, modulesToAdd: (v2) => [] }); // eslint-disable-line no-unused-vars - }); - - it("should not upgrade to 0 module (relayed tx)", async () => { - // we intentionally try to add 0 module, this should fail - await testUpgradeModule({ relayed: true, modulesToAdd: (v2) => [] }); // eslint-disable-line no-unused-vars - }); - }); - - describe("Upgrading when wallet is locked", () => { - let versionManager; - let relayerManager; - let guardianManager; - let lockManager; - let recoveryManager; - let moduleV2; - const guardian = accounts[2]; - const newowner = accounts[3]; - - beforeEach(async () => { - // Setup the module for wallet - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, 12); - lockManager = await LockManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24 * 5); - recoveryManager = await RecoveryManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 36, 24 * 5); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - // Setup the wallet with the initial set of modules - await versionManager.addVersion([ - guardianManager.address, - lockManager.address, - recoveryManager.address, - relayerManager.address, - ], []); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - await guardianManager.addGuardian(wallet.address, guardian, { from: owner }); - - // Setup module v2 for the upgrade - const { module } = await deployTestModule(); - moduleV2 = module; - await registry.registerModule(moduleV2.address, formatBytes32String("V2")); - }); - - it("should not be able to upgrade if wallet is locked by guardian", async () => { - const upgrader = await SimpleUpgrader.new( - lockStorage.address, registry.address, [versionManager.address], [moduleV2.address]); - await registry.registerModule(upgrader.address, formatBytes32String("V1toV2")); - - // Guardian locks the wallet - await lockManager.lock(wallet.address, { from: guardian }); - - // Try to upgrade while wallet is locked - await truffleAssert.reverts(versionManager.addModule(wallet.address, upgrader.address, { from: owner }), "BF: wallet locked"); - - // Check wallet is still locked - const locked = await lockManager.isLocked(wallet.address); - assert.isTrue(locked); - // Check upgrade failed - const isV1Authorised = await wallet.authorised(versionManager.address); - const isV2Authorised = await wallet.authorised(moduleV2.address); - assert.isTrue(isV1Authorised); - assert.isFalse(isV2Authorised); - }); - - it("should not be able to upgrade if wallet is under recovery", async () => { - const upgrader = await SimpleUpgrader.new( - lockStorage.address, - registry.address, - [versionManager.address], - [moduleV2.address], - ); - await registry.registerModule(upgrader.address, formatBytes32String("V1toV2")); - - // Put the wallet under recovery - await manager.relay(recoveryManager, "executeRecovery", [wallet.address, newowner], wallet, [guardian]); - // check that the wallet is locked - let locked = await lockManager.isLocked(wallet.address); - assert.isTrue(locked, "wallet should be locked"); - - // Try to upgrade while wallet is under recovery - await truffleAssert.reverts(versionManager.addModule(wallet.address, upgrader.address, { from: owner }), "BF: wallet locked"); - - // Check wallet is still locked - locked = await lockManager.isLocked(wallet.address); - assert.isTrue(locked, "wallet should still be locked"); - - // Check upgrade failed - const isV1Authorised = await wallet.authorised(versionManager.address); - const isV2Authorised = await wallet.authorised(moduleV2.address); - assert.isTrue(isV1Authorised); - assert.isFalse(isV2Authorised); - }); - }); -}); diff --git a/test/static-calls.js b/test/static-calls.js new file mode 100644 index 000000000..4a72499f4 --- /dev/null +++ b/test/static-calls.js @@ -0,0 +1,183 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const truffleAssert = require("truffle-assertions"); +const TruffleContract = require("@truffle/contract"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const Proxy = artifacts.require("Proxy"); +const BaseWallet = artifacts.require("BaseWallet"); +const BaseWalletV24Contract = require("../build-legacy/v2.4.0/BaseWallet"); + +const BaseWalletV24 = TruffleContract(BaseWalletV24Contract); + +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ERC165Tester = artifacts.require("TestContract"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const utils = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("Static Calls", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + const msg = "0x1234"; + const messageHash = web3.eth.accounts.hashMessage(msg); + let signature; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let factory; + let wallet; + let oldWallet; + let oldWalletImplementation; + + let dappRegistry; + + before(async () => { + BaseWalletV24.defaults({ from: accounts[0] }); + BaseWalletV24.setProvider(web3.currentProvider); + + registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + oldWalletImplementation = await BaseWalletV24.new(); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + signature = await utils.signMessage(msg, owner); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + const proxy2 = await Proxy.new(oldWalletImplementation.address); + oldWallet = await BaseWalletV24.at(proxy2.address); + await oldWallet.init(owner, [module.address]); + }); + + async function checkStaticCalls({ _wallet, _supportERC1155 }) { + const staticCalls = [ + { method: "isValidSignature(bytes32,bytes)", params: [messageHash, signature] }, + { method: "onERC721Received(address,address,uint256,bytes)", params: [infrastructure, infrastructure, 0, "0x"] }, + ]; + if (_supportERC1155) { + staticCalls.push( + { method: "onERC1155Received(address,address,uint256,uint256,bytes)", params: [infrastructure, infrastructure, 0, 0, "0x"] }, + { method: "onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)", params: [infrastructure, infrastructure, [0], [0], "0x"] }, + { method: "supportsInterface(bytes4)", params: ["0x01ffc9a7"], result: "0x0000000000000000000000000000000000000000000000000000000000000001" }, + { method: "supportsInterface(bytes4)", params: ["0x4e2312e0"], result: "0x0000000000000000000000000000000000000000000000000000000000000001" }, + { method: "supportsInterface(bytes4)", params: ["0xffffffff"], result: "0x0000000000000000000000000000000000000000000000000000000000000000" }, + ); + } + + for (const { method, params, result } of staticCalls) { + const methodSig = utils.sha3(method).slice(0, 10); + const delegate = await _wallet.enabled(methodSig); + assert.equal(delegate, module.address, "wallet.enabled() is not module"); + const output = await web3.eth.call({ + to: _wallet.address, + data: utils.encodeFunctionCall(method, params), + }); + const expectedOutput = (result === undefined) ? web3.utils.padRight(methodSig, 64) : result; + assert.equal(output, expectedOutput, `unexpected static call return value for ${method}`); + } + } + + describe("default static calls", () => { + it("should have ERC721 and ERC1271 static calls enabled by default (old wallet)", async () => { + await checkStaticCalls({ _wallet: oldWallet, _supportERC1155: false }); + }); + it("should have all static calls enabled by default (new wallet)", async () => { + await checkStaticCalls({ _wallet: wallet, _supportERC1155: true }); + }); + }); + + describe("isValidSignature", () => { + it("should revert isValidSignature static call for invalid signature", async () => { + const walletAsModule = await ArgentModule.at(wallet.address); + await truffleAssert.reverts( + walletAsModule.isValidSignature(messageHash, `${signature}a1`), "TM: invalid signature length", + ); + }); + + it("should revert isValidSignature static call for invalid signer", async () => { + const walletAsModule = await ArgentModule.at(wallet.address); + const badSig = await utils.signMessage(messageHash, infrastructure); + await truffleAssert.reverts( + walletAsModule.isValidSignature(messageHash, badSig), "TM: Invalid signer", + ); + }); + }); + + describe("ERC1155 activation", () => { + it("lets the owner enable ERC1155TokenReceiver (old wallet)", async () => { + const txReceipt = await manager.relay( + module, + "enableERC1155TokenReceiver", + [oldWallet.address], + oldWallet, + [owner]); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "enableERC1155TokenReceiver failed"); + await checkStaticCalls({ _wallet: oldWallet, _supportERC1155: true }); + }); + + it("requires less than 10000 gas to call supportsInterface()", async () => { + // this call will fail if supportsInterface() consumes more than 10000 gas + const txReceipt = await (await ERC165Tester.new()).testERC165Gas(wallet.address, "0x4e2312e0"); + console.log(`supportsInterface() costs less than ${txReceipt.logs[0].args._gas.toString()} gas`); + }); + }); +}); diff --git a/test/test_utils.js b/test/test_utils.js deleted file mode 100644 index dee6e3061..000000000 --- a/test/test_utils.js +++ /dev/null @@ -1,43 +0,0 @@ -const utils = require("../utils/utilities.js"); - -const modules = [ - { - address: "0xFFB9239F43673068E3c8D7664382Dd6Fdd6e40cb", - name: "ApprovedTransfer", - }, - { - address: "0x25BD64224b7534f7B9e3E16dd10b6dED1A412b90", - name: "GuardianManager", - }, - { - address: "0xe6d5631C6272C8e6190352EC35305e5c03C25Fe1", - name: "LockManager", - }, - { - address: "0xa7939338f2921230aD801b73bfD7758cB09Bccc5", - name: "RecoveryManager", - }, - { - address: "0xE0f4a78BbF24E9624989B9ef10A3B035cc46CE5B", - name: "TokenExchanger", - }, - { - address: "0x624EbBd0f4169E2e11861618045491b6A4e29E77", - name: "NftTransfer", - }, - { - address: "0x6a87B61a54E35f46869c6AC9D636823F70733AeD", - name: "LoanManager", - }, - { - address: "0xD9c6b78bc89432FFe283Ab38fA95e1Edfc5FD46e", - name: "InvestManager", - }, -]; - -describe("Utils", () => { - describe("It should produce the version", () => { - const version = utils.versionFingerprint(modules); - console.log(version); - }); -}); diff --git a/test/tokenExchanger.js b/test/tokenExchanger.js deleted file mode 100644 index 5bc5f60ca..000000000 --- a/test/tokenExchanger.js +++ /dev/null @@ -1,462 +0,0 @@ -/* global artifacts */ - -const truffleAssert = require("truffle-assertions"); -const ethers = require("ethers"); -const { AddressZero } = require("ethers").constants; - -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const TruffleContract = require("@truffle/contract"); - -const BaseWalletContract = require("../build-legacy/v1.3.0/BaseWallet.json"); - -const OldWallet = TruffleContract(BaseWalletContract); - -// Paraswap -const AugustusSwapper = artifacts.require("AugustusSwapper"); -const Whitelisted = artifacts.require("Whitelisted"); -const PartnerRegistry = artifacts.require("PartnerRegistry"); -const PartnerDeployer = artifacts.require("PartnerDeployer"); -const Kyber = artifacts.require("Kyber"); -const UniswapV2 = artifacts.require("UniswapV2"); - -// Kyber -const KyberNetwork = artifacts.require("KyberNetworkTest"); - -// UniswapV2 -const UniswapV2Factory = artifacts.require("UniswapV2Factory"); -const UniswapV2Router01 = artifacts.require("UniswapV2Router01"); -const WETH = artifacts.require("WETH9"); - -// Argent -const ModuleRegistry = artifacts.require("ModuleRegistry"); -const DexRegistry = artifacts.require("DexRegistry"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LockStorage = artifacts.require("LockStorage"); -const TokenExchanger = artifacts.require("TokenExchanger"); -const RelayerManager = artifacts.require("RelayerManager"); -const ERC20 = artifacts.require("TestERC20"); -const TransferStorage = artifacts.require("TransferStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const TransferManager = artifacts.require("TransferManager"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const VersionManager = artifacts.require("VersionManager"); - -// Utils -const { makePathes } = require("../utils/paraswap/sell-helper"); -const { makeRoutes } = require("../utils/paraswap/buy-helper"); -const { ETH_TOKEN } = require("../utils/utilities.js"); -const utils = require("../utils/utilities.js"); -const RelayManager = require("../utils/relay-manager"); - -// Constants -const DECIMALS = 18; // number of decimal for TOKEN_A, TOKEN_B contracts -const TOKEN_A_RATE = web3.utils.toWei("0.06"); -const TOKEN_B_RATE = web3.utils.toWei("0.03"); - -contract("TokenExchanger", (accounts) => { - const manager = new RelayManager(); - const infrastructure = accounts[0]; - const owner = accounts[1]; - - let lockStorage; - let guardianStorage; - let wallet; - let walletImplementation; - let exchanger; - let dexRegistry; - let transferManager; - let relayerManager; - let kyberNetwork; - let kyberAdapter; - let uniswapRouter; - let uniswapV2Adapter; - let tokenA; - let tokenB; - let paraswap; - let tokenPriceRegistry; - let versionManager; - - before(async () => { - OldWallet.defaults({ from: accounts[0] }); - OldWallet.setProvider(web3.currentProvider); - - const registry = await ModuleRegistry.new(); - dexRegistry = await DexRegistry.new(); - guardianStorage = await GuardianStorage.new(); - lockStorage = await LockStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address, - ); - await manager.setRelayerManager(relayerManager); - - // Deploy test tokens - tokenA = await ERC20.new([infrastructure], web3.utils.toWei("1000"), DECIMALS); - tokenB = await ERC20.new([infrastructure], web3.utils.toWei("1000"), DECIMALS); - - // Deploy and fund Kyber - kyberNetwork = await KyberNetwork.new(); - await tokenA.mint(kyberNetwork.address, web3.utils.toWei("1000")); - await tokenB.mint(kyberNetwork.address, web3.utils.toWei("1000")); - await kyberNetwork.addToken(tokenA.address, TOKEN_A_RATE, DECIMALS); - await kyberNetwork.addToken(tokenB.address, TOKEN_B_RATE, DECIMALS); - await kyberNetwork.send(web3.utils.toWei("10").toString()); - - // Deploy and fund UniswapV2 - const uniswapFactory = await UniswapV2Factory.new(AddressZero); - const weth = await WETH.new(); - uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); - await tokenA.approve(uniswapRouter.address, web3.utils.toWei("300")); - await tokenB.approve(uniswapRouter.address, web3.utils.toWei("600")); - const timestamp = await utils.getTimestamp(); - await uniswapRouter.addLiquidity( - tokenA.address, - tokenB.address, - web3.utils.toWei("300"), - web3.utils.toWei("600"), - 1, - 1, - infrastructure, - timestamp + 300, - ); - - // Deploy Paraswap - const whitelist = await Whitelisted.new(); - const partnerDeployer = await PartnerDeployer.new(); - const partnerRegistry = await PartnerRegistry.new(partnerDeployer.address); - paraswap = await AugustusSwapper.new( - whitelist.address, - infrastructure, - partnerRegistry.address, - infrastructure, - infrastructure, - ); - kyberAdapter = await Kyber.new(infrastructure); - uniswapV2Adapter = await UniswapV2.new(weth.address); - await whitelist.addWhitelisted(kyberAdapter.address); - await whitelist.addWhitelisted(uniswapV2Adapter.address); - - // Deploy exchanger module - tokenPriceRegistry = await TokenPriceRegistry.new(); - await tokenPriceRegistry.setTradableForTokenList([tokenA.address, tokenB.address], [true, true]); - await dexRegistry.setAuthorised([kyberAdapter.address, uniswapV2Adapter.address], [true, true]); - exchanger = await TokenExchanger.new( - lockStorage.address, - tokenPriceRegistry.address, - versionManager.address, - dexRegistry.address, - paraswap.address, - "argent", - ); - - // Deploy TransferManager module - const transferStorage = await TransferStorage.new(); - const limitStorage = await LimitStorage.new(); - transferManager = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address, - 3600, - 3600, - 10000, - AddressZero, - AddressZero, - ); - - // Deploy wallet implementation - walletImplementation = await BaseWallet.new(); - - await versionManager.addVersion([ - exchanger.address, - transferManager.address, - relayerManager.address, - ], []); - }); - - beforeEach(async () => { - // create wallet - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - - // fund wallet - await wallet.send(web3.utils.toWei("0.1")); - await tokenA.mint(wallet.address, web3.utils.toWei("1000")); - await tokenB.mint(wallet.address, web3.utils.toWei("1000")); - }); - - async function getBalance(tokenAddress, _wallet) { - let balance; - if (tokenAddress === ETH_TOKEN) { - balance = await utils.getBalance(_wallet.address); - } else if (tokenAddress === tokenA.address) { - balance = await tokenA.balanceOf(_wallet.address); - } else { - balance = await tokenB.balanceOf(_wallet.address); - } - return balance; - } - - function getRoutes({ - fromToken, toToken, srcAmount, destAmount, minConversionRateForBuy = "1", - }) { - const exchange = [toToken, fromToken].includes(ETH_TOKEN) ? "kyber" : "uniswapV2"; - const payload = exchange === "kyber" ? { minConversionRateForBuy } : { - path: [ - fromToken, - toToken, - ], - }; - const routes = [ - { - exchange, - percent: "100", - srcAmount: srcAmount.toString(), - destAmount: destAmount.toString(), - data: { - tokenFrom: fromToken, - tokenTo: toToken, - ...payload, - }, - }, - ]; - return routes; - } - - function buildPathes({ - fromToken, toToken, srcAmount, destAmount, - }) { - const routes = getRoutes({ - fromToken, toToken, srcAmount, destAmount, - }); - const exchanges = { kyber: kyberAdapter.address, uniswapv2: uniswapV2Adapter.address }; - const targetExchanges = { kyber: kyberNetwork.address, uniswapv2: uniswapRouter.address }; - return makePathes(fromToken, toToken, routes, exchanges, targetExchanges, false); - } - - function buildRoutes({ - fromToken, toToken, srcAmount, destAmount, - }) { - const routes = getRoutes({ - fromToken, toToken, srcAmount, destAmount, - }); - const exchanges = { kyber: kyberAdapter.address, uniswapv2: uniswapV2Adapter.address }; - const targetExchanges = { kyber: kyberNetwork.address, uniswapv2: uniswapRouter.address }; - return makeRoutes(fromToken, toToken, routes, exchanges, targetExchanges); - } - - function getParams({ - method, fromToken, toToken, fixedAmount, variableAmount, expectedDestAmount = "123", _wallet = wallet, - }) { - let routes; - let srcAmount; - let destAmount; - if (method === "sell") { - srcAmount = fixedAmount; - destAmount = variableAmount; - routes = buildPathes({ - fromToken, toToken, srcAmount, destAmount, - }); - } else if (method === "buy") { - srcAmount = variableAmount; - destAmount = fixedAmount; - routes = buildRoutes({ - fromToken, toToken, srcAmount, destAmount, - }); - } else { - throw new Error("Unsupported method:", method); - } - const params = [_wallet.address, fromToken, toToken, srcAmount.toString(), destAmount.toString(), expectedDestAmount, routes, 0]; - return params; - } - - async function testTrade({ - method, fromToken, toToken, relayed = true, _wallet = wallet, - }) { - const beforeFrom = await getBalance(fromToken, _wallet); - const beforeTo = await getBalance(toToken, _wallet); - const fixedAmount = web3.utils.toWei("0.01"); - const variableAmount = method === "sell" ? 1 : beforeFrom; - - // wallet should have enough of fromToken - if (method === "sell") { expect(beforeFrom).to.be.gte.BN(fixedAmount); } - - const params = getParams({ - method, - fromToken, - toToken, - fixedAmount, // srcAmount for sell; destAmount for buy - variableAmount, // destAmount for sell; srcAmount for buy - _wallet - }); - - let txR; - if (relayed) { - txR = await manager.relay(exchanger, method, params, _wallet, [owner]); - const event = await utils.getEvent(txR, relayerManager, "TransactionExecuted"); - assert.isTrue(event.args.success, "Relayed tx should succeed"); - } else { - const calldata = await exchanger.contract.methods[method](...params).encodeABI(); - const tx = await exchanger.sendTransaction({ data: calldata, gasLimit: 2000000, from: owner }); - txR = tx.receipt; - } - - const event = await utils.getEvent(txR, exchanger, "TokenExchanged"); - const { destAmount } = event.args; - - const afterFrom = await getBalance(fromToken, _wallet); - const afterTo = await getBalance(toToken, _wallet); - - if (method === "sell") { - // should send the exact amount of fromToken - expect(beforeFrom.sub(afterFrom)).to.eq.BN(fixedAmount); - // should receive some toToken - expect(afterTo).to.be.gt.BN(beforeTo); - // should receive more toToken than minimum specified - expect(destAmount).to.be.gte.BN(variableAmount); - } - if (method === "buy") { - // should send some fromToken - expect(beforeFrom).to.be.gt.BN(afterFrom); - // should receive the exact amount of toToken - expect(afterTo.sub(beforeTo)).to.eq.BN(fixedAmount); - // destAmount should be the requested amount of toToken - expect(destAmount).to.eq.BN(fixedAmount); - } - } - - function testsForMethod(method) { - it("trades ETH to ERC20 (blockchain tx)", async () => { - await testTrade({ - method, fromToken: ETH_TOKEN, toToken: tokenA.address, relayed: false, - }); - }); - it("trades ETH to ERC20 (relayed tx)", async () => { - await testTrade({ - method, fromToken: ETH_TOKEN, toToken: tokenA.address, relayed: true, - }); - }); - it("trades ERC20 to ETH (blockchain tx)", async () => { - await testTrade({ - method, fromToken: tokenA.address, toToken: ETH_TOKEN, relayed: false, - }); - }); - it("trades ERC20 to ETH (relayed tx)", async () => { - await testTrade({ - method, fromToken: tokenA.address, toToken: ETH_TOKEN, relayed: true, - }); - }); - it("trades ERC20 to ERC20 (blockchain tx)", async () => { - await testTrade({ - method, fromToken: tokenA.address, toToken: tokenB.address, relayed: false, - }); - }); - it("trades ERC20 to ERC20 (relayed tx)", async () => { - await testTrade({ - method, fromToken: tokenA.address, toToken: tokenB.address, relayed: true, - }); - }); - - it("can exclude non tradable tokens", async () => { - const fromToken = tokenA.address; - const toToken = tokenB.address; - const fixedAmount = web3.utils.toWei("0.01"); - const variableAmount = method === "sell" ? "1" : await getBalance(fromToken, wallet); - const params = getParams({ - method, - fromToken, - toToken, - fixedAmount, - variableAmount, - }); - await tokenPriceRegistry.setTradableForTokenList([toToken], [false]); - await truffleAssert.reverts(exchanger[method](...params, { gasLimit: 2000000, from: owner }), "TE: Token not tradable"); - await tokenPriceRegistry.setTradableForTokenList([toToken], [true]); - }); - - it("can exclude exchanges", async () => { - const fromToken = tokenA.address; - const toToken = tokenB.address; - // whitelist no exchange - await dexRegistry.setAuthorised([kyberAdapter.address, uniswapV2Adapter.address], [false, false]); - const fixedAmount = web3.utils.toWei("0.01"); - const variableAmount = method === "sell" ? "1" : await getBalance(fromToken, wallet); - const params = getParams({ - method, - fromToken, - toToken, - fixedAmount, - variableAmount, - }); - await truffleAssert.reverts(exchanger[method](...params, { gasLimit: 2000000, from: owner }), "DR: Unauthorised DEX"); - // reset whitelist - await dexRegistry.setAuthorised([kyberAdapter.address, uniswapV2Adapter.address], [true, true]); - }); - - it(`lets old wallets call ${method} successfully`, async () => { - // create wallet - const oldWalletImplementation = await OldWallet.new(); - const proxy = await Proxy.new(oldWalletImplementation.address); - const oldWallet = await OldWallet.at(proxy.address); - await oldWallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(oldWallet.address, await versionManager.lastVersion(), { from: owner }); - // fund wallet - await oldWallet.send(web3.utils.toWei("0.1")); - // call sell/buy - await testTrade({ - method, - fromToken: ETH_TOKEN, - toToken: tokenA.address, - _wallet: oldWallet, - relayed: false, - }); - }); - - const testTradeWithPreExistingAllowance = async (allowance) => { - const spender = await paraswap.getTokenTransferProxy(); - await transferManager.approveToken(wallet.address, tokenA.address, spender, allowance, { from: owner }); - // call sell - await testTrade({ - method, fromToken: tokenA.address, toToken: ETH_TOKEN, relayed: false, - }); - // check that the pre-existing allowance is restored - const newAllowance = await tokenA.allowance(wallet.address, spender); - expect(newAllowance).to.eq.BN(allowance); - }; - - it(`calls ${method} successfully with a pre-existing allowance`, async () => { - // Make the wallet grant some non-zero allowance to the Paraswap proxy - await testTradeWithPreExistingAllowance(3); - }); - - it(`calls ${method} successfully with a pre-existing infinite allowance`, async () => { - // Make the wallet grant an infinite allowance to the Paraswap proxy - const infiniteAllowance = new BN(2).pow(new BN(256)).subn(1); - await testTradeWithPreExistingAllowance(infiniteAllowance); - }); - } - - describe("Sell", () => testsForMethod("sell")); - describe("Buy", () => testsForMethod("buy")); -}); diff --git a/test/tokenPriceRegistry.js b/test/tokenPriceRegistry.js deleted file mode 100644 index f3e381163..000000000 --- a/test/tokenPriceRegistry.js +++ /dev/null @@ -1,96 +0,0 @@ -/* global artifacts */ -const truffleAssert = require("truffle-assertions"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const ERC20 = artifacts.require("TestERC20"); - -const utils = require("../utils/utilities.js"); - -contract("TokenPriceRegistry", (accounts) => { - const owner = accounts[0]; - const manager = accounts[1]; - - let tokenAddress; - let tokenPriceRegistry; - - before(async () => { - const token = await ERC20.new([], 1, 18); - tokenAddress = token.address; - }); - - beforeEach(async () => { - tokenPriceRegistry = await TokenPriceRegistry.new(); - await tokenPriceRegistry.addManager(manager); - await tokenPriceRegistry.setMinPriceUpdatePeriod(3600); - }); - - describe("Price changes", () => { - it("lets managers change price after security period", async () => { - await tokenPriceRegistry.setPriceForTokenList([tokenAddress], [111111], { from: manager }); - const beforePrice = await tokenPriceRegistry.getTokenPrice(tokenAddress); - expect(beforePrice).to.eq.BN(111111); - await utils.increaseTime(3601); - await tokenPriceRegistry.setPriceForTokenList([tokenAddress], [222222], { from: manager }); - const afterPrice = await tokenPriceRegistry.getTokenPrice(tokenAddress); - expect(afterPrice).to.eq.BN(222222); - }); - - it("does not let managers change price with invalid array lengths", async () => { - await truffleAssert.reverts( - tokenPriceRegistry.setPriceForTokenList([tokenAddress], [222222, 333333], { from: manager }), - "TPS: Array length mismatch"); - }); - - it("does not let managers change price before security period", async () => { - await tokenPriceRegistry.setPriceForTokenList([tokenAddress], [111111], { from: manager }); - await utils.increaseTime(3500); - await truffleAssert.reverts( - tokenPriceRegistry.setPriceForTokenList([tokenAddress], [222222], { from: manager }), - "TPS: Price updated too early"); - }); - - it("lets the owner change security period", async () => { - await tokenPriceRegistry.setPriceForTokenList([tokenAddress], [111111], { from: manager }); - await utils.increaseTime(1600); - await truffleAssert.reverts( - tokenPriceRegistry.setPriceForTokenList([tokenAddress], [222222], { from: manager }), - "TPS: Price updated too early"); - await tokenPriceRegistry.setMinPriceUpdatePeriod(0, { from: owner }); - await tokenPriceRegistry.setPriceForTokenList([tokenAddress], [222222], { from: manager }); - const afterPrice = await tokenPriceRegistry.getTokenPrice(tokenAddress); - expect(afterPrice).to.eq.BN(222222); - }); - }); - - describe("Tradable status changes", () => { - it("lets the owner change tradable status", async () => { - await tokenPriceRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); - let tradable = await tokenPriceRegistry.isTokenTradable(tokenAddress); - assert.isTrue(tradable); - await tokenPriceRegistry.setTradableForTokenList([tokenAddress], [false], { from: owner }); - tradable = await tokenPriceRegistry.isTokenTradable(tokenAddress); - assert.isFalse(tradable); - await tokenPriceRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); - tradable = await tokenPriceRegistry.isTokenTradable(tokenAddress); - assert.isTrue(tradable); - }); - it("lets managers set tradable to false only", async () => { - await tokenPriceRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); - await tokenPriceRegistry.setTradableForTokenList([tokenAddress], [false], { from: manager }); - const tradable = await tokenPriceRegistry.isTokenTradable(tokenAddress); - assert.isFalse(tradable); - await truffleAssert.reverts(tokenPriceRegistry.setTradableForTokenList([tokenAddress], [true], { from: manager }), "TPS: Unauthorised"); - }); - it("does not let managers change tradable with invalid array lengths", async () => { - await truffleAssert.reverts( - tokenPriceRegistry.setTradableForTokenList([tokenAddress], [false, false], { from: manager }), - "TPS: Array length mismatch"); - }); - }); -}); diff --git a/test/tokenRegistry.js b/test/tokenRegistry.js new file mode 100644 index 000000000..ad5a3ada4 --- /dev/null +++ b/test/tokenRegistry.js @@ -0,0 +1,71 @@ +/* global artifacts */ +const truffleAssert = require("truffle-assertions"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +chai.use(bnChai(BN)); + +const TokenRegistry = artifacts.require("TokenRegistry"); +const ERC20 = artifacts.require("TestERC20"); + +contract("TokenRegistry", (accounts) => { + const owner = accounts[0]; + const manager = accounts[1]; + + let tokenAddress; + let tokenAddress2; + let tokenRegistry; + + before(async () => { + const token = await ERC20.new([], 1, 18); + tokenAddress = token.address; + const token2 = await ERC20.new([], 1, 18); + tokenAddress2 = token2.address; + }); + + beforeEach(async () => { + tokenRegistry = await TokenRegistry.new(); + await tokenRegistry.addManager(manager); + }); + + describe("Tradable status changes", () => { + it("lets the owner change tradable status", async () => { + await tokenRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); + let tradable = await tokenRegistry.isTokenTradable(tokenAddress); + assert.isTrue(tradable); + await tokenRegistry.setTradableForTokenList([tokenAddress], [false], { from: owner }); + tradable = await tokenRegistry.isTokenTradable(tokenAddress); + assert.isFalse(tradable); + await tokenRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); + tradable = await tokenRegistry.isTokenTradable(tokenAddress); + assert.isTrue(tradable); + }); + + it("lets managers set tradable to false only", async () => { + await tokenRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); + await tokenRegistry.setTradableForTokenList([tokenAddress], [false], { from: manager }); + const tradable = await tokenRegistry.isTokenTradable(tokenAddress); + assert.isFalse(tradable); + await truffleAssert.reverts(tokenRegistry.setTradableForTokenList([tokenAddress], [true], { from: manager }), "TR: Unauthorised operation"); + }); + + it("does not let managers change tradable with invalid array lengths", async () => { + await truffleAssert.reverts( + tokenRegistry.setTradableForTokenList([tokenAddress], [false, false], { from: manager }), + "TR: Array length mismatch"); + }); + }); + + describe("Reading tradable status", () => { + it("lets managers read tradable information on multiple tokens", async () => { + await tokenRegistry.setTradableForTokenList([tokenAddress], [true], { from: owner }); + let tradable = await tokenRegistry.areTokensTradable([tokenAddress, tokenAddress2]); + assert.isFalse(tradable); + + await tokenRegistry.setTradableForTokenList([tokenAddress2], [true], { from: owner }); + tradable = await tokenRegistry.areTokensTradable([tokenAddress, tokenAddress2]); + assert.isTrue(tradable); + }); + }); +}); diff --git a/test/transferManager.js b/test/transferManager.js deleted file mode 100644 index fd18c162c..000000000 --- a/test/transferManager.js +++ /dev/null @@ -1,935 +0,0 @@ -/* global artifacts */ - -const truffleAssert = require("truffle-assertions"); -const ethers = require("ethers"); -const chai = require("chai"); -const BN = require("bn.js"); -const bnChai = require("bn-chai"); - -const { expect } = chai; -chai.use(bnChai(BN)); - -const TruffleContract = require("@truffle/contract"); - -const LegacyTransferManagerContract = require("../build-legacy/v1.6.0/TransferManager"); -const LegacyTokenPriceProviderContract = require("../build-legacy/v1.6.0/TokenPriceProvider"); - -const LegacyTransferManager = TruffleContract(LegacyTransferManagerContract); -const LegacyTokenPriceProvider = TruffleContract(LegacyTokenPriceProviderContract); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const Registry = artifacts.require("ModuleRegistry"); -const VersionManager = artifacts.require("VersionManager"); -const TransferStorage = artifacts.require("TransferStorage"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const RelayerManager = artifacts.require("RelayerManager"); -const TransferManager = artifacts.require("TransferManager"); - -const ERC20 = artifacts.require("TestERC20"); -const WETH = artifacts.require("WETH9"); -const TestContract = artifacts.require("TestContract"); - -const utils = require("../utils/utilities.js"); -const { ETH_TOKEN } = require("../utils/utilities.js"); - -const ETH_LIMIT = 1000000; -const SECURITY_PERIOD = 2; -const SECURITY_WINDOW = 2; -const ZERO_BYTES32 = ethers.constants.HashZero; - -const ACTION_TRANSFER = 0; - -const RelayManager = require("../utils/relay-manager"); - -contract("TransferManager", (accounts) => { - const manager = new RelayManager(); - - const infrastructure = accounts[0]; - const owner = accounts[1]; - const nonowner = accounts[2]; - const recipient = accounts[3]; - const spender = accounts[4]; - - let registry; - let priceProvider; - let transferStorage; - let lockStorage; - let guardianStorage; - let limitStorage; - let tokenPriceRegistry; - let transferManager; - let previousTransferManager; - let wallet; - let walletImplementation; - let erc20; - let weth; - let relayerManager; - let versionManager; - - before(async () => { - LegacyTransferManager.defaults({ from: accounts[0] }); - LegacyTransferManager.setProvider(web3.currentProvider); - LegacyTokenPriceProvider.defaults({ from: accounts[0] }); - LegacyTokenPriceProvider.setProvider(web3.currentProvider); - - weth = await WETH.new(); - registry = await Registry.new(); - - priceProvider = await LegacyTokenPriceProvider.new(ethers.constants.AddressZero); - await priceProvider.addManager(infrastructure); - - transferStorage = await TransferStorage.new(); - lockStorage = await LockStorage.new(); - guardianStorage = await GuardianStorage.new(); - limitStorage = await LimitStorage.new(); - tokenPriceRegistry = await TokenPriceRegistry.new(); - await tokenPriceRegistry.addManager(infrastructure); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - transferStorage.address, - limitStorage.address); - - previousTransferManager = await LegacyTransferManager.new( - registry.address, - transferStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - SECURITY_PERIOD, - SECURITY_WINDOW, - ETH_LIMIT, - ethers.constants.AddressZero, - ); - - transferManager = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address, - SECURITY_PERIOD, - SECURITY_WINDOW, - ETH_LIMIT, - weth.address, - previousTransferManager.address); - - await registry.registerModule(versionManager.address, ethers.utils.formatBytes32String("VersionManager")); - - walletImplementation = await BaseWallet.new(); - - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address); - await manager.setRelayerManager(relayerManager); - - await versionManager.addVersion([transferManager.address, relayerManager.address], [transferManager.address]); - }); - - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - - const decimals = 12; // number of decimal for TOKN contract - const tokenRate = new BN(10).pow(new BN(19)).muln(51); // 1 TOKN = 0.00051 ETH = 0.00051*10^18 ETH wei => *10^(18-decimals) = 0.00051*10^18 * 10^6 = 0.00051*10^24 = 51*10^19 - - erc20 = await ERC20.new([infrastructure, wallet.address], 10000000, decimals); // TOKN contract with 10M tokens (5M TOKN for wallet and 5M TOKN for account[0]) - await tokenPriceRegistry.setPriceForTokenList([erc20.address], [tokenRate.toString()]); - await wallet.send(new BN("1000000000000000000")); - }); - - async function getEtherValue(amount, token) { - if (token === ETH_TOKEN) { - return amount; - } - const price = await tokenPriceRegistry.getTokenPrice(token); - const ethPrice = new BN(price.toString()).mul(new BN(amount)).div(new BN(10).pow(new BN(18))); - return ethPrice; - } - - describe("Initialising the module", () => { - it("when no previous transfer manager is passed, should initialise with default limit", async () => { - const transferManager1 = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager.address, - SECURITY_PERIOD, - SECURITY_WINDOW, - 10, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - await versionManager.addVersion([transferManager1.address], [transferManager1.address]); - const proxy = await Proxy.new(walletImplementation.address); - const existingWallet = await BaseWallet.at(proxy.address); - await existingWallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(existingWallet.address, await versionManager.lastVersion(), { from: owner }); - - const defaultLimit = await transferManager1.defaultLimit(); - const limit = await transferManager1.getCurrentLimit(existingWallet.address); - expect(limit).to.eq.BN(defaultLimit); - - // reset the last version to the default bundle - await versionManager.addVersion([transferManager.address, relayerManager.address], [transferManager.address]); - }); - }); - - describe("Managing the whitelist", () => { - it("should add/remove an account to/from the whitelist", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - let isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.isFalse(isTrusted, "should not be trusted during the security period"); - await utils.increaseTime(3); - isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.isTrue(isTrusted, "should be trusted after the security period"); - await transferManager.removeFromWhitelist(wallet.address, recipient, { from: owner }); - isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.isFalse(isTrusted, "should no removed from whitelist immediately"); - }); - - it("should not be able to whitelist a token twice", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - await utils.increaseTime(3); - await truffleAssert.reverts( - transferManager.addToWhitelist(wallet.address, recipient, { from: owner }), "TT: target already whitelisted", - ); - }); - - it("should be able to remove a whitelisted token from the whitelist during the security period", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - await transferManager.removeFromWhitelist(wallet.address, recipient, { from: owner }); - - await utils.increaseTime(3); - const isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.isFalse(isTrusted); - }); - }); - - describe("Reading and writing token prices", () => { - let erc20First; - let erc20Second; - let erc20ZeroDecimals; - - beforeEach(async () => { - erc20First = await ERC20.new([infrastructure], 10000000, 18); - erc20Second = await ERC20.new([infrastructure], 10000000, 18); - erc20ZeroDecimals = await ERC20.new([infrastructure], 10000000, 0); - }); - - it("should get a token price correctly", async () => { - const tokenPrice = new BN(10).pow(new BN(18)).muln(1800); - await tokenPriceRegistry.setPriceForTokenList([erc20First.address], [tokenPrice]); - const tokenPriceSet = await tokenPriceRegistry.getTokenPrice(erc20First.address); - expect(tokenPrice).to.eq.BN(tokenPriceSet); - }); - - it("should get multiple token prices correctly", async () => { - await tokenPriceRegistry.setPriceForTokenList([erc20First.address, erc20Second.address], [1800, 1900]); - const tokenPricesSet = await tokenPriceRegistry.getPriceForTokenList([erc20First.address, erc20Second.address]); - expect(1800).to.eq.BN(tokenPricesSet[0]); - expect(1900).to.eq.BN(tokenPricesSet[1]); - }); - - it("should set token price correctly", async () => { - const tokenPrice = new BN(10).pow(new BN(18)).muln(1800); - await tokenPriceRegistry.setPriceForTokenList([erc20First.address], [tokenPrice]); - const tokenPriceSet = await tokenPriceRegistry.getTokenPrice(erc20First.address); - expect(tokenPrice).to.eq.BN(tokenPriceSet); - }); - - it("should set multiple token prices correctly", async () => { - await tokenPriceRegistry.setPriceForTokenList([erc20First.address, erc20Second.address], [1800, 1900]); - const tokenPrice1Set = await tokenPriceRegistry.getTokenPrice(erc20First.address); - expect(1800).to.eq.BN(tokenPrice1Set); - const tokenPrice2Set = await tokenPriceRegistry.getTokenPrice(erc20Second.address); - expect(1900).to.eq.BN(tokenPrice2Set); - }); - - it("should be able to get the ether value of a given amount of tokens", async () => { - const tokenPrice = new BN(10).pow(new BN(18)).muln(1800); - await tokenPriceRegistry.setPriceForTokenList([erc20First.address], [tokenPrice]); - const etherValue = await getEtherValue("15000000000000000000", erc20First.address); - // expectedValue = 1800*10^18/10^18 (price for 1 token wei) * 15*10^18 (amount) = 1800 * 15*10^18 = 27,000 * 10^18 - const expectedValue = new BN(10).pow(new BN(18)).muln(27000); - expect(expectedValue).to.eq.BN(etherValue); - }); - - it("should be able to get the ether value for a token with 0 decimals", async () => { - const tokenPrice = new BN(10).pow(new BN(36)).muln(23000); - await tokenPriceRegistry.setPriceForTokenList([erc20ZeroDecimals.address], [tokenPrice]); - const etherValue = await getEtherValue(100, erc20ZeroDecimals.address); - // expectedValue = 23000*10^36 * 100 / 10^18 = 2,300,000 * 10^18 - const expectedValue = new BN(10).pow(new BN(18)).muln(2300000); - expect(expectedValue).to.eq.BN(etherValue); - }); - - it("should return 0 as the ether value for a low priced token", async () => { - await tokenPriceRegistry.setPriceForTokenList([erc20First.address], [23000]); - const etherValue = await getEtherValue(100, erc20First.address); - assert.equal(etherValue, 0); // 2,300,000 - }); - }); - - describe("Daily limit", () => { - it("should migrate daily limit for existing wallets", async () => { - // create wallet with previous module and funds - const proxy = await Proxy.new(walletImplementation.address); - const existingWallet = await BaseWallet.at(proxy.address); - - await existingWallet.init(owner, [previousTransferManager.address]); - await existingWallet.send(100000000); - - // change the limit - await previousTransferManager.changeLimit(existingWallet.address, 4000000, { from: owner }); - await utils.increaseTime(SECURITY_PERIOD + 1); - let limit = await previousTransferManager.getCurrentLimit(existingWallet.address); - expect(limit).to.eq.BN(4000000); - // transfer some funds - await previousTransferManager.transferToken(existingWallet.address, ETH_TOKEN, recipient, 1000000, ZERO_BYTES32, { from: owner }); - // add new module - await previousTransferManager.addModule(existingWallet.address, versionManager.address, { from: owner }); - const tx = await versionManager.upgradeWallet(existingWallet.address, await versionManager.lastVersion(), { from: owner }); - const txReceipt = tx.receipt; - await utils.hasEvent(txReceipt, transferManager, "DailyLimitMigrated"); - // check result - limit = await transferManager.getCurrentLimit(existingWallet.address); - expect(limit).to.eq.BN(4000000); - const unspent = await transferManager.getDailyUnspent(existingWallet.address); - // unspent should have been migrated - expect(unspent[0]).to.eq.BN(4000000 - 1000000); - }); - - it("should set the default limit for new wallets", async () => { - const limit = await transferManager.getCurrentLimit(wallet.address); - expect(limit).to.eq.BN(ETH_LIMIT); - }); - - it("should only increase the limit after the security period", async () => { - await transferManager.changeLimit(wallet.address, 4000000, { from: owner }); - let limit = await transferManager.getCurrentLimit(wallet.address); - expect(limit).to.eq.BN(ETH_LIMIT); - await utils.increaseTime(SECURITY_PERIOD + 1); - limit = await transferManager.getCurrentLimit(wallet.address); - expect(limit).to.eq.BN(4000000); - }); - - it("should decrease the limit immediately", async () => { - let limit = await transferManager.getCurrentLimit(wallet.address); - assert.equal(limit.toNumber(), ETH_LIMIT, "limit should be ETH_LIMIT"); - await transferManager.changeLimit(wallet.address, ETH_LIMIT / 2, { from: owner }); - limit = await transferManager.getCurrentLimit(wallet.address); - assert.equal(limit.toNumber(), ETH_LIMIT / 2, "limit should be decreased immediately"); - }); - - it("should change the limit via relayed transaction", async () => { - await manager.relay(transferManager, "changeLimit", [wallet.address, 4000000], wallet, [owner]); - await utils.increaseTime(SECURITY_PERIOD + 1); - const limit = await transferManager.getCurrentLimit(wallet.address); - assert.equal(limit.toNumber(), 4000000, "limit should be changed"); - }); - - it("should correctly set the pending limit", async () => { - const tx = await transferManager.changeLimit(wallet.address, 4000000, { from: owner }); - const timestamp = await utils.getTimestamp(tx.receipt.block); - const { _pendingLimit, _changeAfter } = await transferManager.getPendingLimit(wallet.address); - assert.equal(_pendingLimit.toNumber(), 4000000); - assert.closeTo(_changeAfter.toNumber(), timestamp + SECURITY_PERIOD, 1); // timestamp is sometimes off by 1 - }); - - it("should be able to disable the limit", async () => { - const tx = await transferManager.disableLimit(wallet.address, { from: owner }); - const txReceipt = tx.receipt; - await utils.hasEvent(txReceipt, transferManager, "DailyLimitDisabled"); - let limitDisabled = await transferManager.isLimitDisabled(wallet.address); - assert.isFalse(limitDisabled); - await utils.increaseTime(SECURITY_PERIOD + 1); - limitDisabled = await transferManager.isLimitDisabled(wallet.address); - assert.isTrue(limitDisabled); - }); - - it("should return the correct unspent daily limit amount", async () => { - await wallet.send(new BN(ETH_LIMIT)); - const transferAmount = ETH_LIMIT - 100; - await transferManager.transferToken(wallet.address, ETH_TOKEN, recipient, transferAmount, ZERO_BYTES32, { from: owner }); - const { _unspent } = await transferManager.getDailyUnspent(wallet.address); - assert.equal(_unspent.toNumber(), 100); - }); - - it("should return the correct spent daily limit amount", async () => { - await wallet.send(new BN(ETH_LIMIT)); - // Transfer 100 wei - const tx = await transferManager.transferToken(wallet.address, ETH_TOKEN, recipient, 100, ZERO_BYTES32, { from: owner }); - const timestamp = await utils.getTimestamp(tx.receipt.block); - // Then transfer 200 wei more - await transferManager.transferToken(wallet.address, ETH_TOKEN, recipient, 200, ZERO_BYTES32, { from: owner }); - - const dailySpent = await limitStorage.getDailySpent(wallet.address); - assert.equal(dailySpent[0], 300); - assert.closeTo(new BN(dailySpent[1]).toNumber(), timestamp + (3600 * 24), 1); // timestamp is sometimes off by 1 - }); - - it("should return 0 if the entire daily limit amount has been spent", async () => { - await wallet.send(new BN(ETH_LIMIT)); - await transferManager.transferToken(wallet.address, ETH_TOKEN, recipient, ETH_LIMIT, ZERO_BYTES32, { from: owner }); - const { _unspent } = await transferManager.getDailyUnspent(wallet.address); - assert.equal(_unspent.toNumber(), 0); - }); - }); - - describe("Token transfers", () => { - async function doDirectTransfer({ - token, signer = owner, to, amount, relayed = false, - }) { - const fundsBefore = (token === ETH_TOKEN ? await utils.getBalance(to) : await token.balanceOf(to)); - const unspentBefore = await transferManager.getDailyUnspent(wallet.address); - const params = [wallet.address, token === ETH_TOKEN ? ETH_TOKEN : token.address, to, amount, ZERO_BYTES32]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(transferManager, "transferToken", params, wallet, [signer]); - } else { - const tx = await transferManager.transferToken(...params, { from: signer }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, transferManager, "Transfer"); - const fundsAfter = (token === ETH_TOKEN ? await utils.getBalance(to) : await token.balanceOf(to)); - const unspentAfter = await transferManager.getDailyUnspent(wallet.address); - assert.equal(fundsAfter.sub(fundsBefore).toNumber(), amount, "should have transfered amount"); - const ethValue = (token === ETH_TOKEN ? amount : (await getEtherValue(amount, token.address)).toNumber()); - if (ethValue < ETH_LIMIT) { - assert.equal(unspentBefore[0].sub(unspentAfter[0]).toNumber(), ethValue, "should have updated the daily spent in ETH"); - } - return txReceipt; - } - - async function doPendingTransfer({ - token, to, amount, delay, relayed = false, - }) { - const tokenAddress = token === ETH_TOKEN ? ETH_TOKEN : token.address; - const fundsBefore = (token === ETH_TOKEN ? await utils.getBalance(to) : await token.balanceOf(to)); - const params = [wallet.address, tokenAddress, to, amount, ZERO_BYTES32]; - let txReceipt; let - tx; - if (relayed) { - txReceipt = await manager.relay(transferManager, "transferToken", params, wallet, [owner]); - } else { - tx = await transferManager.transferToken(...params, { from: owner }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, transferManager, "PendingTransferCreated"); - let fundsAfter = (token === ETH_TOKEN ? await utils.getBalance(to) : await token.balanceOf(to)); - assert.equal(fundsAfter.sub(fundsBefore).toNumber(), 0, "should not have transfered amount"); - if (delay === 0) { - const id = ethers.utils.solidityKeccak256(["uint8", "address", "address", "uint256", "bytes", "uint256"], - [ACTION_TRANSFER, tokenAddress, recipient, amount, ZERO_BYTES32, txReceipt.blockNumber]); - return id; - } - await utils.increaseTime(delay); - tx = await transferManager.executePendingTransfer(wallet.address, - tokenAddress, recipient, amount, ZERO_BYTES32, txReceipt.blockNumber); - txReceipt = tx.receipt; - await utils.hasEvent(txReceipt, transferManager, "PendingTransferExecuted"); - fundsAfter = (token === ETH_TOKEN ? await utils.getBalance(to) : await token.balanceOf(to)); - return assert.equal(fundsAfter.sub(fundsBefore).toNumber(), amount, "should have transfered amount"); - } - - describe("Small token transfers", () => { - it("should let the owner send ETH", async () => { - await doDirectTransfer({ token: ETH_TOKEN, to: recipient, amount: 10000 }); - }); - - it("should let the owner send ETH (relayed)", async () => { - await doDirectTransfer({ - token: ETH_TOKEN, to: recipient, amount: 10000, relayed: true, - }); - }); - - it("should let the owner send ERC20", async () => { - await doDirectTransfer({ token: erc20, to: recipient, amount: 10 }); - }); - - it("should let the owner send ERC20 (relayed)", async () => { - await doDirectTransfer({ - token: erc20, to: recipient, amount: 10, relayed: true, - }); - }); - - it("should only let the owner send ETH", async () => { - const params = [wallet.address, ETH_TOKEN, recipient, 10000, ZERO_BYTES32]; - await truffleAssert.reverts( - transferManager.transferToken(...params, { from: nonowner }), - "BF: must be owner or feature"); - }); - - it("should calculate the daily unspent when the owner send ETH", async () => { - let unspent = await transferManager.getDailyUnspent(wallet.address); - assert.equal(unspent[0].toNumber(), ETH_LIMIT, "unspent should be the limit at the beginning of a period"); - await doDirectTransfer({ token: ETH_TOKEN, to: recipient, amount: 10000 }); - unspent = await transferManager.getDailyUnspent(wallet.address); - assert.equal(unspent[0].toNumber(), ETH_LIMIT - 10000, "should be the limit minus the transfer"); - }); - - it("should calculate the daily unspent in ETH when the owner send ERC20", async () => { - let unspent = await transferManager.getDailyUnspent(wallet.address); - assert.equal(unspent[0].toNumber(), ETH_LIMIT, "unspent should be the limit at the beginning of a period"); - await doDirectTransfer({ token: erc20, to: recipient, amount: 10 }); - unspent = await transferManager.getDailyUnspent(wallet.address); - const ethValue = await getEtherValue(10, erc20.address); - assert.equal(unspent[0].toNumber(), ETH_LIMIT - ethValue.toNumber(), "should be the limit minus the transfer"); - }); - }); - - describe("Large token transfers ", () => { - it("should create and execute a pending ETH transfer", async () => { - await doPendingTransfer({ - token: ETH_TOKEN, to: recipient, amount: ETH_LIMIT * 2, delay: 3, relayed: false, - }); - }); - - it("should create and execute a pending ETH transfer (relayed)", async () => { - await doPendingTransfer({ - token: ETH_TOKEN, to: recipient, amount: ETH_LIMIT * 2, delay: 3, relayed: true, - }); - }); - - it("should create and execute a pending ERC20 transfer", async () => { - await doPendingTransfer({ - token: erc20, to: recipient, amount: ETH_LIMIT * 2, delay: 3, relayed: false, - }); - }); - - it("should create and execute a pending ERC20 transfer (relayed)", async () => { - await doPendingTransfer({ - token: erc20, to: recipient, amount: ETH_LIMIT * 2, delay: 3, relayed: true, - }); - }); - - it("should not execute a pending ETH transfer before the confirmation window", async () => { - const params = [wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32]; - const tx = await transferManager.transferToken(...params, { from: owner }); - - await truffleAssert.reverts( - transferManager.executePendingTransfer(wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32, tx.receipt.blockNumber), - "TT: transfer outside of the execution window"); - }); - - it("should not execute a pending ETH transfer before the confirmation window (relayed)", async () => { - const params = [wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32]; - const txReceipt = await manager.relay(transferManager, "transferToken", params, wallet, [owner]); - - await truffleAssert.reverts( - transferManager.executePendingTransfer(wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32, txReceipt.blockNumber), - "TT: transfer outside of the execution window"); - }); - - it("should not execute a pending ETH transfer after the confirmation window", async () => { - const params = [wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32]; - const tx = await transferManager.transferToken(...params, { from: owner }); - - await utils.increaseTime(10); - await truffleAssert.reverts( - transferManager.executePendingTransfer(wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32, tx.receipt.blockNumber), - "TT: transfer outside of the execution window"); - }); - - it("should not execute a pending ETH transfer after the confirmation window (relayed)", async () => { - const params = [wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32]; - const txReceipt = await manager.relay(transferManager, "transferToken", params, wallet, [owner]); - - await utils.increaseTime(10); - await truffleAssert.reverts( - transferManager.executePendingTransfer(wallet.address, ETH_TOKEN, recipient, ETH_LIMIT * 2, ZERO_BYTES32, txReceipt.blockNumber), - "TT: transfer outside of the execution window"); - }); - - it("should cancel a pending ETH transfer", async () => { - const id = await doPendingTransfer({ - token: ETH_TOKEN, to: recipient, amount: ETH_LIMIT * 2, delay: 0, - }); - await utils.increaseTime(1); - const tx = await transferManager.cancelPendingTransfer(wallet.address, id, { from: owner }); - const txReceipt = tx.receipt; - await utils.hasEvent(txReceipt, transferManager, "PendingTransferCanceled"); - const executeAfter = await transferManager.getPendingTransfer(wallet.address, id); - assert.equal(executeAfter, 0, "should have cancelled the pending transfer"); - }); - - it("should cancel a pending ERC20 transfer", async () => { - const id = await doPendingTransfer({ - token: erc20, to: recipient, amount: ETH_LIMIT * 2, delay: 0, - }); - await utils.increaseTime(1); - const tx = await transferManager.cancelPendingTransfer(wallet.address, id, { from: owner }); - const txReceipt = tx.receipt; - await utils.hasEvent(txReceipt, transferManager, "PendingTransferCanceled"); - const executeAfter = await transferManager.getPendingTransfer(wallet.address, id); - assert.equal(executeAfter, 0, "should have cancelled the pending transfer"); - }); - - it("should send immediately ETH to a whitelisted address", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - await utils.increaseTime(3); - await doDirectTransfer({ token: ETH_TOKEN, to: recipient, amount: ETH_LIMIT * 2 }); - }); - - it("should send immediately ERC20 to a whitelisted address", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - await utils.increaseTime(3); - await doDirectTransfer({ token: erc20, to: recipient, amount: ETH_LIMIT * 2 }); - }); - }); - }); - - describe("Token Approvals", () => { - async function doDirectApprove({ signer = owner, amount, relayed = false }) { - const unspentBefore = await transferManager.getDailyUnspent(wallet.address); - const params = [wallet.address, erc20.address, spender, amount]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(transferManager, "approveToken", params, wallet, [signer]); - } else { - const tx = await transferManager.approveToken(...params, { from: signer }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, transferManager, "Approved"); - const unspentAfter = await transferManager.getDailyUnspent(wallet.address); - - const amountInEth = await getEtherValue(amount, erc20.address); - if (amountInEth < ETH_LIMIT) { - assert.equal(unspentBefore[0].sub(unspentAfter[0]).toNumber(), amountInEth, "should have updated the daily limit"); - } - const approval = await erc20.allowance(wallet.address, spender); - - assert.equal(approval.toNumber(), amount, "should have approved the amount"); - return txReceipt; - } - - it("should approve an ERC20 immediately when the amount is under the limit", async () => { - await doDirectApprove({ amount: 10 }); - }); - - it("should approve an ERC20 immediately when the amount is under the limit (relayed) ", async () => { - await doDirectApprove({ amount: 10, relayed: true }); - }); - - it("should approve an ERC20 immediately when the amount is under the existing approved amount", async () => { - await doDirectApprove({ amount: 100 }); - await transferManager.approveToken(wallet.address, erc20.address, spender, 10, { from: owner }); - const approval = await erc20.allowance(wallet.address, spender); - assert.equal(approval.toNumber(), 10); - }); - - it("should not approve an ERC20 transfer when the signer is not the owner ", async () => { - const params = [wallet.address, erc20.address, spender, 10]; - truffleAssert.reverts( - transferManager.approveToken(...params, { from: nonowner }), - "BF: must be owner or feature"); - }); - - it("should approve an ERC20 immediately when the spender is whitelisted ", async () => { - await transferManager.addToWhitelist(wallet.address, spender, { from: owner }); - await utils.increaseTime(3); - await doDirectApprove({ amount: ETH_LIMIT + 10000 }); - }); - - it("should fail to approve an ERC20 when the amount is above the daily limit ", async () => { - const params = [wallet.address, erc20.address, spender, ETH_LIMIT + 10000]; - truffleAssert.reverts( - transferManager.approveToken(...params, { from: owner }), - "above daily limit"); - }); - }); - - describe("Call contract", () => { - let contract; - - beforeEach(async () => { - contract = await TestContract.new(); - assert.equal(await contract.state(), 0, "initial contract state should be 0"); - }); - - async function doCallContract({ value, state, relayed = false }) { - const dataToTransfer = contract.contract.methods.setState(state).encodeABI(); - const unspentBefore = await transferManager.getDailyUnspent(wallet.address); - const params = [wallet.address, contract.address, value, dataToTransfer]; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(transferManager, "callContract", params, wallet, [owner]); - } else { - const tx = await transferManager.callContract(...params, { from: owner }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, transferManager, "CalledContract"); - const unspentAfter = await transferManager.getDailyUnspent(wallet.address); - if (value < ETH_LIMIT) { - assert.equal(unspentBefore[0].sub(unspentAfter[0]).toNumber(), value, "should have updated the daily limit"); - } - assert.equal((await contract.state()).toNumber(), state, "the state of the external contract should have been changed"); - return txReceipt; - } - - it("should not be able to call the wallet itselt", async () => { - const dataToTransfer = contract.contract.methods.setState(4).encodeABI(); - const params = [wallet.address, wallet.address, 10, dataToTransfer]; - await truffleAssert.reverts(transferManager.callContract(...params, { from: owner }), "BT: Forbidden contract"); - }); - - it("should not be able to call a feature of the wallet", async () => { - const dataToTransfer = contract.contract.methods.setState(4).encodeABI(); - const params = [wallet.address, transferManager.address, 10, dataToTransfer]; - await truffleAssert.reverts(transferManager.callContract(...params, { from: owner }), "BT: Forbidden contract"); - }); - - it("should not be able to call a supported ERC20 token contract", async () => { - const dataToTransfer = contract.contract.methods.setState(4).encodeABI(); - const params = [wallet.address, erc20.address, 10, dataToTransfer]; - await truffleAssert.reverts(transferManager.callContract(...params, { from: owner }), "TM: Forbidden contract"); - }); - - it("should be able to call a supported token contract which is whitelisted", async () => { - await transferManager.addToWhitelist(wallet.address, erc20.address, { from: owner }); - await utils.increaseTime(3); - const dataToTransfer = erc20.contract.methods.transfer(infrastructure, 4).encodeABI(); - const params = [wallet.address, erc20.address, 0, dataToTransfer]; - await transferManager.callContract(...params, { from: owner }); - }); - - it("should call a contract and transfer ETH value when under the daily limit", async () => { - await doCallContract({ value: 10, state: 3 }); - }); - - it("should call a contract and transfer ETH value when under the daily limit (relayed) ", async () => { - await doCallContract({ value: 10, state: 3, relayed: true }); - }); - - it("should call a contract and transfer ETH value above the daily limit when the contract is whitelisted", async () => { - await transferManager.addToWhitelist(wallet.address, contract.address, { from: owner }); - await utils.increaseTime(3); - await doCallContract({ value: ETH_LIMIT + 10000, state: 6 }); - }); - - it("should fail to call a contract and transfer ETH when the amount is above the daily limit ", async () => { - await truffleAssert.reverts(doCallContract({ value: ETH_LIMIT + 10000, state: 6 }, "above daily limit")); - }); - }); - - describe("Approve token and Call contract", () => { - let contract; - - beforeEach(async () => { - contract = await TestContract.new(); - assert.equal(await contract.state(), 0, "initial contract state should be 0"); - }); - - async function doApproveTokenAndCallContract({ - signer = owner, consumer = contract.address, amount, state, relayed = false, wrapEth = false, - }) { - const fun = consumer === contract.address ? "setStateAndPayToken" : "setStateAndPayTokenWithConsumer"; - const token = wrapEth ? weth : erc20; - const dataToTransfer = contract.contract.methods[fun](state, token.address, amount).encodeABI(); - const unspentBefore = await transferManager.getDailyUnspent(wallet.address); - const params = [wallet.address] - .concat(wrapEth ? [] : [erc20.address]) - .concat([consumer, amount, contract.address, dataToTransfer]); - const method = wrapEth ? "approveWethAndCallContract" : "approveTokenAndCallContract"; - let txReceipt; - if (relayed) { - txReceipt = await manager.relay(transferManager, method, params, wallet, [signer]); - } else { - const tx = await transferManager[method](...params, { from: signer }); - txReceipt = tx.receipt; - } - await utils.hasEvent(txReceipt, transferManager, "ApprovedAndCalledContract"); - const unspentAfter = await transferManager.getDailyUnspent(wallet.address); - const amountInEth = wrapEth ? amount : await getEtherValue(amount, erc20.address); - - if (amountInEth < ETH_LIMIT) { - assert.equal(unspentBefore[0].sub(unspentAfter[0]).toNumber(), amountInEth, "should have updated the daily limit"); - } - assert.equal((await contract.state()).toNumber(), state, "the state of the external contract should have been changed"); - const tokenBalance = await token.balanceOf(contract.address); - assert.equal(tokenBalance.toNumber(), amount, "the contract should have transfered the tokens"); - return txReceipt; - } - - // approveTokenAndCallContract - - it("should approve the token and call the contract when under the limit", async () => { - await doApproveTokenAndCallContract({ amount: 10, state: 3 }); - }); - - it("should approve the token and call the contract when under the limit (relayed) ", async () => { - await doApproveTokenAndCallContract({ amount: 10, state: 3, relayed: true }); - }); - - it("should restore existing approved amount after call", async () => { - await transferManager.approveToken(wallet.address, erc20.address, contract.address, 10, { from: owner }); - const dataToTransfer = contract.contract.methods.setStateAndPayToken(3, erc20.address, 5).encodeABI(); - await transferManager.approveTokenAndCallContract( - wallet.address, - erc20.address, - contract.address, - 5, - contract.address, - dataToTransfer, - { from: owner } - ); - const approval = await erc20.allowance(wallet.address, contract.address); - - // Initial approval of 10 is restored, after approving and spending 5 - assert.equal(approval.toNumber(), 10); - - const erc20Balance = await erc20.balanceOf(contract.address); - assert.equal(erc20Balance.toNumber(), 5, "the contract should have transfered the tokens"); - }); - - it("should be able to spend less than approved in call", async () => { - await transferManager.approveToken(wallet.address, erc20.address, contract.address, 10, { from: owner }); - const dataToTransfer = contract.contract.methods.setStateAndPayToken(3, erc20.address, 4).encodeABI(); - await transferManager.approveTokenAndCallContract( - wallet.address, - erc20.address, - contract.address, - 5, - contract.address, - dataToTransfer, - { from: owner } - ); - const approval = await erc20.allowance(wallet.address, contract.address); - // Initial approval of 10 is restored, after approving and spending 4 - assert.equal(approval.toNumber(), 10); - - const erc20Balance = await erc20.balanceOf(contract.address); - assert.equal(erc20Balance.toNumber(), 4, "the contract should have transfered the tokens"); - }); - - it("should not be able to spend more than approved in call", async () => { - await transferManager.approveToken(wallet.address, erc20.address, contract.address, 10, { from: owner }); - const dataToTransfer = contract.contract.methods.setStateAndPayToken(3, erc20.address, 6).encodeABI(); - await truffleAssert.reverts(transferManager.approveTokenAndCallContract( - wallet.address, - erc20.address, - contract.address, - 5, - contract.address, - dataToTransfer, - { from: owner } - ), "BT: insufficient amount for call"); - }); - - it("should approve the token and call the contract when the token is above the limit and the contract is whitelisted ", async () => { - await transferManager.addToWhitelist(wallet.address, contract.address, { from: owner }); - await utils.increaseTime(3); - await doApproveTokenAndCallContract({ amount: ETH_LIMIT + 10000, state: 6 }); - }); - - it("should approve the token and call the contract when contract to call is different to token spender", async () => { - const consumer = await contract.tokenConsumer(); - await doApproveTokenAndCallContract({ amount: 10, state: 3, consumer }); - }); - - it("should approve token and call contract when contract != spender, amount > limit and contract is whitelisted", async () => { - const consumer = await contract.tokenConsumer(); - await transferManager.addToWhitelist(wallet.address, contract.address, { from: owner }); - await utils.increaseTime(3); - await doApproveTokenAndCallContract({ amount: ETH_LIMIT + 10000, state: 6, consumer }); - }); - - it("should fail to approve token and call contract when contract != spender, amount > limit and spender is whitelisted", async () => { - const amount = ETH_LIMIT + 10000; - const consumer = await contract.tokenConsumer(); - await transferManager.addToWhitelist(wallet.address, consumer, { from: owner }); - await utils.increaseTime(3); - const dataToTransfer = contract.contract.methods.setStateAndPayTokenWithConsumer(6, erc20.address, amount).encodeABI(); - await truffleAssert.reverts( - transferManager.approveTokenAndCallContract( - wallet.address, erc20.address, consumer, amount, contract.address, dataToTransfer, { from: owner } - ), - "TM: Approve above daily limit", - ); - }); - - it("should fail to approve the token and call the contract when the token is above the daily limit ", async () => { - const dataToTransfer = contract.contract.methods.setStateAndPayToken(6, erc20.address, ETH_LIMIT + 10000).encodeABI(); - const params = [wallet.address, erc20.address, contract.address, ETH_LIMIT + 10000, contract.address, dataToTransfer]; - - await truffleAssert.reverts( - transferManager.approveTokenAndCallContract(...params, { from: owner }), - "TM: Approve above daily limit"); - }); - - it("should fail to approve token if the amount to be approved is greater than the current balance", async () => { - const startingBalance = await erc20.balanceOf(wallet.address); - await erc20.burn(wallet.address, startingBalance); - const dataToTransfer = contract.contract.methods.setStateAndPayToken(3, erc20.address, 1).encodeABI(); - await truffleAssert.reverts(transferManager.approveTokenAndCallContract( - wallet.address, - erc20.address, - contract.address, - 1, - contract.address, - dataToTransfer, - { from: owner }), "BT: insufficient balance"); - }); - - // approveWethAndCallContract - - it("should approve WETH and call the contract when under the limit", async () => { - await doApproveTokenAndCallContract({ amount: 10, state: 3, wrapEth: true }); - }); - - it("should approve WETH and call the contract under the limit when already holding the WETH", async () => { - const amount = 10; - await weth.deposit({ value: amount }); - await weth.transfer(wallet.address, amount); - await doApproveTokenAndCallContract({ amount, state: 3, wrapEth: true }); - }); - }); - - describe("Static calls", () => { - it("should delegate isValidSignature static calls to the TransferManager", async () => { - const ERC1271_ISVALIDSIGNATURE_BYTES32 = utils.sha3("isValidSignature(bytes32,bytes)").slice(0, 10); - const isValidSignatureDelegate = await wallet.enabled(ERC1271_ISVALIDSIGNATURE_BYTES32); - assert.equal(isValidSignatureDelegate, versionManager.address); - - const walletAsTransferManager = await TransferManager.at(wallet.address); - const msg = "0x1234"; - const messageHash = web3.eth.accounts.hashMessage(msg); - const signature = await utils.signMessage(msg, owner); - - const valid = await walletAsTransferManager.isValidSignature(messageHash, signature); - assert.equal(valid, ERC1271_ISVALIDSIGNATURE_BYTES32); - }); - - it("should revert isValidSignature static call for invalid signature", async () => { - const walletAsTransferManager = await TransferManager.at(wallet.address); - const msg = "0x1234"; - const messageHash = web3.eth.accounts.hashMessage(msg); - const signature = await utils.signMessage(messageHash, owner); - - await truffleAssert.reverts( - walletAsTransferManager.isValidSignature(messageHash, `${signature}a1`), "TM: invalid signature length", - ); - }); - - it("should revert isValidSignature static call for invalid signer", async () => { - const walletAsTransferManager = await TransferManager.at(wallet.address); - const msg = "0x1234"; - const messageHash = web3.eth.accounts.hashMessage(msg); - const signature = await utils.signMessage(messageHash, owner); - - await truffleAssert.reverts( - walletAsTransferManager.isValidSignature(messageHash, signature), "TM: Invalid signer", - ); - }); - }); -}); diff --git a/test/uniswapV2-liquidity.js b/test/uniswapV2-liquidity.js new file mode 100644 index 000000000..ec661d8d6 --- /dev/null +++ b/test/uniswapV2-liquidity.js @@ -0,0 +1,310 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { expect, assert } = chai; +chai.use(bnChai(BN)); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); +const UniZap = artifacts.require("UniZap"); + +// Argent +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TokenRegistry = artifacts.require("TokenRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ERC20 = artifacts.require("TestERC20"); +const UniZapFilter = artifacts.require("UniswapV2UniZapFilter"); + +// Utils +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, assertFailedWithError } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("ArgentModule", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const recipient = accounts[4]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let filter; + let dappRegistry; + let tokenRegistry; + let uniswapRouter; + let token; + let weth; + let lpToken; + let uniZap; + + before(async () => { + // Deploy and mint test tokens + token = await ERC20.new([infrastructure], web3.utils.toWei("100"), 18); + weth = await WETH.new(); + await weth.send(web3.utils.toWei("1")); + + // Deploy and fund UniswapV2 + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + // create token pool + await token.approve(uniswapRouter.address, web3.utils.toWei("3")); + await weth.approve(uniswapRouter.address, web3.utils.toWei("1")); + const timestamp = await utils.getTimestamp(); + await uniswapRouter.addLiquidity( + token.address, + weth.address, + web3.utils.toWei("3"), + web3.utils.toWei("1"), + 1, + 1, + infrastructure, + timestamp + 300, + ); + + // get LP Token address + lpToken = await ERC20.at(await uniswapFactory.getPair(weth.address, token.address)); + + // deploy UniZap + uniZap = await UniZap.new(uniswapFactory.address, uniswapRouter.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + + // make LP token tradable + tokenRegistry = await TokenRegistry.new(); + await tokenRegistry.setTradableForTokenList([lpToken.address], [true]); + // deploy unizap filter + const uniInitCode = await uniswapFactory.getKeccakOfPairCreationCode(); + filter = await UniZapFilter.new(tokenRegistry.address, uniswapFactory.address, uniInitCode, weth.address); + + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + await dappRegistry.addDapp(0, uniZap.address, filter.address); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("1")); + await token.mint(wallet.address, web3.utils.toWei("10")); + }); + + async function getBalance(tokenAddress, _wallet) { + let balance; + if (tokenAddress === ETH_TOKEN) { + balance = await utils.getBalance(_wallet.address); + } else if (tokenAddress === token.address) { + balance = await token.balanceOf(_wallet.address); + } else { + balance = await lpToken.balanceOf(_wallet.address); + } + return balance; + } + + async function addLiquidity(tokenAddress, amount, to) { + const transactions = []; + const balancesBefore = []; + const balancesAfter = []; + const deadline = (await utils.getTimestamp()) + 10; + + if (tokenAddress === ETH_TOKEN) { + const data = uniZap.contract.methods.swapExactETHAndAddLiquidity(token.address, 0, to, deadline).encodeABI(); + transactions.push(encodeTransaction(uniZap.address, amount, data)); + balancesBefore.push(await getBalance(ETH_TOKEN, wallet)); + } else { + let data = token.contract.methods.approve(uniZap.address, amount).encodeABI(); + transactions.push(encodeTransaction(token.address, 0, data)); + data = uniZap.contract.methods.swapExactTokensAndAddLiquidity(token.address, weth.address, amount, 0, to, deadline).encodeABI(); + transactions.push(encodeTransaction(uniZap.address, 0, data)); + balancesBefore.push(await getBalance(token.address, wallet)); + } + + balancesBefore.push(await getBalance(lpToken.address, wallet)); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner]); + const { success } = await utils.parseRelayReceipt(txReceipt); + if (success) { + if (tokenAddress === ETH_TOKEN) { + balancesAfter.push(await getBalance(ETH_TOKEN, wallet)); + } else { + balancesAfter.push(await getBalance(token.address, wallet)); + } + balancesAfter.push(await getBalance(lpToken.address, wallet)); + expect(balancesBefore[0].sub(balancesAfter[0])).to.gt.BN(0); // should have send weth/token + expect(balancesBefore[1].sub(balancesAfter[1])).to.lt.BN(0); // should have received lp token + } + + return txReceipt; + } + + async function removeLiquidity(tokenAddress, amount, to) { + const transactions = []; + const balancesBefore = []; + const balancesAfter = []; + const deadline = (await utils.getTimestamp()) + 10; + + let data = lpToken.contract.methods.approve(uniZap.address, amount).encodeABI(); + transactions.push(encodeTransaction(lpToken.address, 0, data)); + if (tokenAddress === ETH_TOKEN) { + data = uniZap.contract.methods.removeLiquidityAndSwapToETH(token.address, amount, 0, to, deadline).encodeABI(); + transactions.push(encodeTransaction(uniZap.address, 0, data)); + balancesBefore.push(await getBalance(ETH_TOKEN, wallet)); + } else { + data = uniZap.contract.methods.removeLiquidityAndSwapToToken(weth.address, token.address, amount, 0, to, deadline).encodeABI(); + transactions.push(encodeTransaction(uniZap.address, 0, data)); + balancesBefore.push(await getBalance(token.address, wallet)); + } + + balancesBefore.push(await getBalance(lpToken.address, wallet)); + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner]); + const { success } = await utils.parseRelayReceipt(txReceipt); + if (success) { + if (tokenAddress === ETH_TOKEN) { + balancesAfter.push(await getBalance(ETH_TOKEN, wallet)); + } else { + balancesAfter.push(await getBalance(token.address, wallet)); + } + balancesAfter.push(await getBalance(lpToken.address, wallet)); + expect(balancesBefore[0].sub(balancesAfter[0])).to.lt.BN(0); // should have received weth/token + expect(balancesBefore[1].sub(balancesAfter[1])).to.gt.BN(0); // should have burn lp tokens + } + + return txReceipt; + } + + describe("UniZap methods", () => { + it("should add liquidity with ETH", async () => { + await addLiquidity(ETH_TOKEN, web3.utils.toWei("1", "finney"), wallet.address); + }); + + it("should add liquidity with token", async () => { + await addLiquidity(token, web3.utils.toWei("1", "finney"), wallet.address); + }); + + it("should block adding liquidity when the recipient is not the wallet", async () => { + const txReceipt = await addLiquidity(token, web3.utils.toWei("1", "finney"), recipient); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should block adding liquidity when the pair is not valid", async () => { + await tokenRegistry.setTradableForTokenList([lpToken.address], [false]); + const txReceipt = await addLiquidity(token, web3.utils.toWei("1", "finney"), wallet.address); + await tokenRegistry.setTradableForTokenList([lpToken.address], [true]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should remove liquidity to ETH", async () => { + await addLiquidity(ETH_TOKEN, web3.utils.toWei("1", "finney"), wallet.address); + const lpBalance = await lpToken.balanceOf(wallet.address); + await removeLiquidity(ETH_TOKEN, lpBalance.div(new BN(2)).toString(), wallet.address); + }); + + it("should remove liquidity to token", async () => { + await addLiquidity(ETH_TOKEN, web3.utils.toWei("1", "finney"), wallet.address); + const lpBalance = await lpToken.balanceOf(wallet.address); + await removeLiquidity(token.address, lpBalance.div(new BN(2)).toString(), wallet.address); + }); + + it("should block removing liquidity when the recipient is not the wallet", async () => { + await addLiquidity(ETH_TOKEN, web3.utils.toWei("1", "finney"), wallet.address); + const lpBalance = await lpToken.balanceOf(wallet.address); + const txReceipt = await removeLiquidity(token.address, lpBalance.div(new BN(2)).toString(), recipient); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should block removing liquidity when the pair is not valid", async () => { + await tokenRegistry.setTradableForTokenList([lpToken.address], [false]); + await addLiquidity(ETH_TOKEN, web3.utils.toWei("1", "finney"), wallet.address); + const lpBalance = await lpToken.balanceOf(wallet.address); + const txReceipt = await removeLiquidity(token.address, lpBalance.div(new BN(2)).toString(), wallet.address); + await tokenRegistry.setTradableForTokenList([lpToken.address], [true]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + }); + + describe("ETH and ERC20 methods", () => { + it("should block sending ETH to the zap", async () => { + const transaction = await encodeTransaction(uniZap.address, web3.utils.toWei("1", "finney"), ZERO_BYTES); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should block sending an ERC20 to the zap", async () => { + const data = token.contract.methods.transfer(uniZap.address, web3.utils.toWei("1", "finney")).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + assertFailedWithError(txReceipt, "TM: call not authorised"); + }); + + it("should approve an ERC20", async () => { + const data = token.contract.methods.approve(uniZap.address, web3.utils.toWei("1", "finney")).encodeABI(); + const transaction = encodeTransaction(token.address, 0, data); + const txReceipt = await manager.relay(module, "multiCall", [wallet.address, [transaction]], wallet, [owner]); + const { success } = await utils.parseRelayReceipt(txReceipt); + assert.isTrue(success, "transfer failed"); + }); + }); +}); diff --git a/test/upgrade.js b/test/upgrade.js new file mode 100644 index 000000000..02bbbbc9f --- /dev/null +++ b/test/upgrade.js @@ -0,0 +1,135 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const Upgrader = artifacts.require("SimpleUpgrader"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const { initNonce, parseRelayReceipt } = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const utils = require("../utils/utilities.js"); +const RelayManager = require("../utils/relay-manager"); + +contract("TransactionManager", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + + let transferStorage; + let guardianStorage; + let module; + let newModule; + let upgrader1; + let dappRegistry; + + let wallet; + let factory; + + before(async () => { + registry = await Registry.new(); + + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + newModule = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + upgrader1 = await Upgrader.new( + registry.address, + [newModule.address], + [module.address]); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentMdoule")); + await registry.registerModule(newModule.address, ethers.utils.formatBytes32String("NewArgentModule")); + await registry.registerModule(upgrader1.address, ethers.utils.formatBytes32String("Upgrader")); + + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + await wallet.send(new BN("1000000000000000000")); + }); + + describe("upgrader modules", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + it("should remove 1 and add 1 module", async () => { + let isAuthorised = await wallet.authorised(newModule.address); + assert.equal(isAuthorised, false, "new module should not be authorised"); + + const txReceipt = await manager.relay( + module, + "addModule", + [wallet.address, upgrader1.address], + wallet, + [owner]); + const success = await parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + isAuthorised = await wallet.authorised(newModule.address); + assert.equal(isAuthorised, false, "new module should be authorised"); + console.log("GAS for upgrade: ", txReceipt.gasUsed); + }); + }); +}); diff --git a/test/upgraderToVersionManager.js b/test/upgraderToVersionManager.js deleted file mode 100644 index 76fd9c7f2..000000000 --- a/test/upgraderToVersionManager.js +++ /dev/null @@ -1,147 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); -const TruffleContract = require("@truffle/contract"); - -const LegacyTransferManagerContract = require("../build-legacy/v1.6.0/TransferManager.json"); - -const LegacyTransferManager = TruffleContract(LegacyTransferManagerContract); - -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const Registry = artifacts.require("ModuleRegistry"); -const VersionManager = artifacts.require("VersionManager"); -const TransferStorage = artifacts.require("TransferStorage"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const RelayerManager = artifacts.require("RelayerManager"); -const TransferManager = artifacts.require("TransferManager"); -const UpgraderToVersionManager = artifacts.require("UpgraderToVersionManager"); - -const SECURITY_PERIOD = 3600; -const SECURITY_WINDOW = 3600; -const ETH_LIMIT = 1000000; - -const RelayManager = require("../utils/relay-manager"); -const utils = require("../utils/utilities.js"); - -contract("UpgraderToVersionManager", (accounts) => { - const manager = new RelayManager(); - - const owner = accounts[1]; - const recipient = accounts[2]; - - let transferStorage; - let lockStorage; - let guardianStorage; - let limitStorage; - let transferManager; - let previousTransferManager; - let wallet; - let walletImplementation; - let relayerManager; - let versionManager; - let upgrader; - - before(async () => { - LegacyTransferManager.defaults({ from: accounts[0] }); - LegacyTransferManager.setProvider(web3.currentProvider); - - walletImplementation = await BaseWallet.new(); - const registry = await Registry.new(); - lockStorage = await LockStorage.new(); - guardianStorage = await GuardianStorage.new(); - transferStorage = await TransferStorage.new(); - - // Deploy old architecture - previousTransferManager = await LegacyTransferManager.new( - registry.address, - transferStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - SECURITY_PERIOD, - SECURITY_WINDOW, - ETH_LIMIT, - ethers.constants.AddressZero); - - // Deploy new modules - limitStorage = await LimitStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - transferStorage.address, - limitStorage.address); - upgrader = await UpgraderToVersionManager.new( - registry.address, - lockStorage.address, - [previousTransferManager.address], // toDisable - versionManager.address); - await registry.registerModule(versionManager.address, ethers.utils.formatBytes32String("VersionManager")); - await registry.registerModule(upgrader.address, ethers.utils.formatBytes32String("Upgrader")); - - // Deploy new features - transferManager = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - ethers.constants.AddressZero, - versionManager.address, - SECURITY_PERIOD, - SECURITY_WINDOW, - ETH_LIMIT, - ethers.constants.AddressZero, - previousTransferManager.address); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - limitStorage.address, - ethers.constants.AddressZero, - versionManager.address); - await manager.setRelayerManager(relayerManager); - await versionManager.addVersion([transferManager.address, relayerManager.address], [transferManager.address]); - }); - - it("should fail to upgrade a pre-VersionManager wallet to a version lower than minVersion", async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [previousTransferManager.address]); - const prevVersion = await versionManager.lastVersion(); - await versionManager.addVersion([], []); - const lastVersion = await versionManager.lastVersion(); - await versionManager.setMinVersion(lastVersion); - await truffleAssert.reverts( - previousTransferManager.addModule(wallet.address, upgrader.address, { from: owner }), - "VM: invalid _toVersion", - ); - await versionManager.setMinVersion(prevVersion); - }); - - describe("After migrating a pre-VersionManager wallet", () => { - beforeEach(async () => { - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - - await wallet.init(owner, [previousTransferManager.address]); - await previousTransferManager.addModule(wallet.address, upgrader.address, { from: owner }); - }); - - it("should add/remove an account to/from the whitelist", async () => { - await transferManager.addToWhitelist(wallet.address, recipient, { from: owner }); - await utils.increaseTime(SECURITY_PERIOD + 1); - let isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.equal(isTrusted, true, "should be trusted after the security period"); - await transferManager.removeFromWhitelist(wallet.address, recipient, { from: owner }); - isTrusted = await transferManager.isWhitelisted(wallet.address, recipient); - assert.equal(isTrusted, false, "should no removed from whitelist immediately"); - }); - - it("should change the limit via relayed transaction", async () => { - await manager.relay(transferManager, "changeLimit", [wallet.address, 4000000], wallet, [owner]); - await utils.increaseTime(SECURITY_PERIOD + 1); - const limit = await transferManager.getCurrentLimit(wallet.address); - assert.equal(limit.toNumber(), 4000000, "limit should be changed"); - }); - }); -}); diff --git a/test/versionManager.js b/test/versionManager.js deleted file mode 100644 index 9ae7afb5b..000000000 --- a/test/versionManager.js +++ /dev/null @@ -1,196 +0,0 @@ -/* global artifacts */ -const ethers = require("ethers"); -const truffleAssert = require("truffle-assertions"); - -const GuardianManager = artifacts.require("GuardianManager"); -const LockStorage = artifacts.require("LockStorage"); -const GuardianStorage = artifacts.require("GuardianStorage"); -const Proxy = artifacts.require("Proxy"); -const BaseWallet = artifacts.require("BaseWallet"); -const RelayerManager = artifacts.require("RelayerManager"); -const VersionManager = artifacts.require("VersionManager"); -const Registry = artifacts.require("ModuleRegistry"); -const TestFeature = artifacts.require("TestFeature"); -const TransferStorage = artifacts.require("TransferStorage"); -const LimitStorage = artifacts.require("LimitStorage"); -const TokenPriceRegistry = artifacts.require("TokenPriceRegistry"); -const TransferManager = artifacts.require("TransferManager"); -const UpgraderToVersionManager = artifacts.require("UpgraderToVersionManager"); - -const RelayManager = require("../utils/relay-manager"); - -contract("VersionManager", (accounts) => { - const manager = new RelayManager(accounts); - const owner = accounts[1]; - - let wallet; - let walletImplementation; - let registry; - let lockStorage; - let guardianStorage; - let guardianManager; - let relayerManager; - let versionManager; - let testFeature; - - before(async () => { - walletImplementation = await BaseWallet.new(); - }); - - beforeEach(async () => { - registry = await Registry.new(); - lockStorage = await LockStorage.new(); - guardianStorage = await GuardianStorage.new(); - versionManager = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - relayerManager = await RelayerManager.new( - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero, - versionManager.address); - guardianManager = await GuardianManager.new( - lockStorage.address, - guardianStorage.address, - versionManager.address, - 24, - 12); - testFeature = await TestFeature.new( - lockStorage.address, - versionManager.address, - 42); - await versionManager.addVersion([guardianManager.address, relayerManager.address, testFeature.address], []); - await manager.setRelayerManager(relayerManager); - - const proxy = await Proxy.new(walletImplementation.address); - wallet = await BaseWallet.at(proxy.address); - await wallet.init(owner, [versionManager.address]); - await versionManager.upgradeWallet(wallet.address, await versionManager.lastVersion(), { from: owner }); - }); - - describe("VersionManager owner", () => { - it("should not let the VersionManager owner add a storage twice", async () => { - await truffleAssert.reverts(versionManager.addStorage(lockStorage.address), "VM: storage already added"); - }); - - it("should not let the VersionManager owner add an inconsistent version", async () => { - // Should fail: the _featuresToInit array includes a feature not listed in the _features array - await truffleAssert.reverts( - versionManager.addVersion([relayerManager.address], [guardianManager.address]), - "VM: invalid _featuresToInit", - ); - }); - - it("should not let the VersionManager owner set an invalid minVersion", async () => { - const lastVersion = await versionManager.lastVersion(); - - await truffleAssert.reverts( - versionManager.setMinVersion(0), - "VM: invalid _minVersion", - ); - - await truffleAssert.reverts( - versionManager.setMinVersion(lastVersion.addn(1)), - "VM: invalid _minVersion", - ); - }); - }); - - describe("Wallet owner", () => { - it("should not let the relayer call a forbidden method", async () => { - await truffleAssert.reverts( - manager.relay(versionManager, "setOwner", [wallet.address, owner], wallet, [owner]), - "VM: unknown method", - ); - }); - - it("should fail to upgrade a wallet when already on the last version", async () => { - const lastVersion = await versionManager.lastVersion(); - await truffleAssert.reverts( - versionManager.upgradeWallet(wallet.address, lastVersion, { from: owner }), - "VM: already on new version", - ); - }); - - it("should fail to upgrade a wallet to a version lower than minVersion", async () => { - const badVersion = await versionManager.lastVersion(); - await versionManager.addVersion([], []); - await versionManager.setMinVersion(await versionManager.lastVersion()); - - await truffleAssert.reverts( - versionManager.upgradeWallet(wallet.address, badVersion, { from: owner }), - "VM: invalid _toVersion", - ); - }); - - it("should not let a feature call an unauthorised storage", async () => { - // Note: we are calling the deprecated GuardianStorage.setLock so this particular method gets touched by coverage - const data1 = guardianStorage.contract.methods.setLock(wallet.address, 1).encodeABI(); - - await testFeature.invokeStorage(wallet.address, guardianStorage.address, data1, { from: owner }); - let lock = await guardianStorage.getLock(wallet.address); - assert.equal(lock, 1, "Lock should have been set"); - const data0 = guardianStorage.contract.methods.setLock(wallet.address, 0).encodeABI(); - - await testFeature.invokeStorage(wallet.address, guardianStorage.address, data0, { from: owner }); - lock = await guardianStorage.getLock(wallet.address); - assert.equal(lock, 0, "Lock should have been unset"); - - const newGuardianStorage = await GuardianStorage.new(); // not authorised in VersionManager - await truffleAssert.reverts( - testFeature.invokeStorage(wallet.address, newGuardianStorage.address, data1, { from: owner }), - "VM: invalid storage invoked", - ); - lock = await newGuardianStorage.getLock(wallet.address); - assert.equal(lock, 0, "Lock should not be set"); - }); - - it("should not allow the fallback to be called via a non-static call", async () => { - // Deploy new VersionManager with TransferManager - const versionManager2 = await VersionManager.new( - registry.address, - lockStorage.address, - guardianStorage.address, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - const tokenPriceRegistry = await TokenPriceRegistry.new(); - const transferStorage = await TransferStorage.new(); - const limitStorage = await LimitStorage.new(); - const transferManager = await TransferManager.new( - lockStorage.address, - transferStorage.address, - limitStorage.address, - tokenPriceRegistry.address, - versionManager2.address, - 3600, - 3600, - 10000, - ethers.constants.AddressZero, - ethers.constants.AddressZero); - await versionManager2.addVersion([transferManager.address], []); - await registry.registerModule(versionManager2.address, ethers.utils.formatBytes32String("VersionManager2")); - - // Deploy Upgrader to new VersionManager - const upgrader = await UpgraderToVersionManager.new( - registry.address, - lockStorage.address, - [versionManager.address], // toDisable - versionManager2.address); - await registry.registerModule(upgrader.address, ethers.utils.formatBytes32String("Upgrader")); - - // Upgrade wallet to new VersionManger - await versionManager.addModule(wallet.address, upgrader.address, { from: owner }); - - // Attempt to call a malicious (non-static) call on the old VersionManager - const data = await testFeature.contract.methods.badStaticCall().encodeABI(); - await truffleAssert.reverts( - transferManager.callContract(wallet.address, versionManager.address, 0, data, { from: owner }), - "VM: not in a staticcall", - ); - }); - }); -}); diff --git a/test/whitelist.js b/test/whitelist.js new file mode 100644 index 000000000..328eab895 --- /dev/null +++ b/test/whitelist.js @@ -0,0 +1,256 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const ERC721 = artifacts.require("TestERC721"); +const UniswapV2Router01 = artifacts.require("DummyUniV2Router"); + +const ERC20 = artifacts.require("TestERC20"); + +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, encodeTransaction, addTrustedContact, initNonce } = require("../utils/utilities.js"); + +const ZERO_BYTES = "0x"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; + +const RelayManager = require("../utils/relay-manager"); + +contract("ArgentModule", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const recipient = accounts[4]; + const refundAddress = accounts[7]; + const relayer = accounts[9]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let erc20; + let dappRegistry; + + before(async () => { + registry = await Registry.new(); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + dappRegistry = await DappRegistry.new(0); + + const uniswapRouter = await UniswapV2Router01.new(); + + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + const decimals = 12; // number of decimal for TOKN contract + erc20 = await ERC20.new([infrastructure, wallet.address], 10000000, decimals); // TOKN contract with 10M tokens (5M TOKN for wallet and 5M TOKN for account[0]) + await wallet.send(new BN("1000000000000000000")); + }); + + describe("whitelist", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + it("should whitelist an address", async () => { + const target = accounts[6]; + const txReceipt = await manager.relay( + module, + "addToWhitelist", + [wallet.address, target], + wallet, + [owner]); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + await utils.increaseTime(3); + const isTrusted = await module.isWhitelisted(wallet.address, target); + assert.isTrue(isTrusted, "should be trusted after the security period"); + console.log(`Gas for whitelisting: ${txReceipt.gasUsed}`); + }); + + it("should not add wallet to whitelist", async () => { + const txReceipt = await manager.relay( + module, + "addToWhitelist", + [wallet.address, wallet.address], + wallet, + [owner]); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "TM: Cannot whitelist wallet"); + }); + + it("should not add module to whitelist", async () => { + const txReceipt = await manager.relay( + module, + "addToWhitelist", + [wallet.address, module.address], + wallet, + [owner]); + + const { success, error } = utils.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, "TM: Cannot whitelist module"); + }); + }); + + describe("transfer ETH", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + it("should send ETH to a whitelisted address", async () => { + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); + const balanceStart = await utils.getBalance(recipient); + + const transaction = encodeTransaction(recipient, 10, ZERO_BYTES); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + const balanceEnd = await utils.getBalance(recipient); + assert.equal(balanceEnd.sub(balanceStart), 10, "should have received ETH"); + + console.log(`Gas for ETH transfer: ${txReceipt.gasUsed}`); + }); + }); + + describe("transfer/Approve ERC20", () => { + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + // init erc20 - recipient storage slot + await erc20.transfer(recipient, new BN("100")); + }); + + it("should send ERC20 to a whitelisted address", async () => { + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); + const balanceStart = await erc20.balanceOf(recipient); + + const data = erc20.contract.methods.transfer(recipient, 100).encodeABI(); + const transaction = encodeTransaction(erc20.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + const balanceEnd = await erc20.balanceOf(recipient); + assert.equal(balanceEnd.sub(balanceStart), 100, "should have received tokens"); + console.log(`Gas for EC20 transfer: ${txReceipt.gasUsed}`); + }); + + it("should approve ERC20 for a whitelisted address", async () => { + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); + + const data = erc20.contract.methods.approve(recipient, 100).encodeABI(); + const transaction = encodeTransaction(erc20.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + const balance = await erc20.allowance(wallet.address, recipient); + assert.equal(balance, 100, "should have been approved tokens"); + console.log(`Gas for EC20 approve: ${txReceipt.gasUsed}`); + }); + }); + + describe("transfer ERC721", () => { + let erc721; + const tokenId = 7; + + beforeEach(async () => { + await initNonce(wallet, module, manager, SECURITY_PERIOD); + + erc721 = await ERC721.new(); + await erc721.mint(wallet.address, tokenId); + }); + + it("should send an ERC721 to a whitelisted address", async () => { + await addTrustedContact(wallet, recipient, module, SECURITY_PERIOD); + + const data = erc721.contract.methods.safeTransferFrom(wallet.address, recipient, tokenId).encodeABI(); + const transaction = encodeTransaction(erc721.address, 0, data); + + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + const success = await utils.parseRelayReceipt(txReceipt).success; + assert.isTrue(success, "transfer failed"); + console.log(`Gas for ERC721 transfer: ${txReceipt.gasUsed}`); + }); + }); +}); diff --git a/test/yearn.js b/test/yearn.js new file mode 100644 index 000000000..cc91978c7 --- /dev/null +++ b/test/yearn.js @@ -0,0 +1,212 @@ +/* global artifacts */ + +const ethers = require("ethers"); +const chai = require("chai"); +const BN = require("bn.js"); +const bnChai = require("bn-chai"); + +const { assert } = chai; +chai.use(bnChai(BN)); + +// UniswapV2 +const UniswapV2Factory = artifacts.require("UniswapV2FactoryMock"); +const UniswapV2Router01 = artifacts.require("UniswapV2Router01Mock"); +const WETH = artifacts.require("WETH9"); + +// Argent +const WalletFactory = artifacts.require("WalletFactory"); +const BaseWallet = artifacts.require("BaseWallet"); +const Registry = artifacts.require("ModuleRegistry"); +const TransferStorage = artifacts.require("TransferStorage"); +const GuardianStorage = artifacts.require("GuardianStorage"); +const ArgentModule = artifacts.require("ArgentModule"); +const DappRegistry = artifacts.require("DappRegistry"); +const Filter = artifacts.require("YearnFilter"); +const Vault = artifacts.require("yVault"); +const Controller = artifacts.require("Controller"); +const Strategy = artifacts.require("StrategyMock"); + +// Utils +const utils = require("../utils/utilities.js"); +const { ETH_TOKEN, initNonce, encodeCalls, encodeTransaction } = require("../utils/utilities.js"); + +const ZERO_ADDRESS = ethers.constants.AddressZero; +const SECURITY_PERIOD = 2; +const SECURITY_WINDOW = 2; +const LOCK_PERIOD = 4; +const RECOVERY_PERIOD = 4; +const AMOUNT = web3.utils.toWei("0.01"); + +const RelayManager = require("../utils/relay-manager"); + +contract("yEarn Filter", (accounts) => { + let manager; + + const infrastructure = accounts[0]; + const owner = accounts[1]; + const guardian1 = accounts[2]; + const relayer = accounts[4]; + const refundAddress = accounts[7]; + + let registry; + let transferStorage; + let guardianStorage; + let module; + let wallet; + let factory; + let dappRegistry; + + let uniswapRouter; + + let weth; + + let pool; + let wethPool; + + before(async () => { + // Deploy test token + weth = await WETH.new(); + + // Deploy yVault + const ctrl = await Controller.new(ZERO_ADDRESS); + const strat = await Strategy.new(); + await ctrl.approveStrategy(weth.address, strat.address); + await ctrl.setStrategy(weth.address, strat.address); + wethPool = await Vault.new(weth.address, ctrl.address); + pool = await Vault.new(weth.address, ctrl.address); + + // Deploy and fund UniswapV2 + const uniswapFactory = await UniswapV2Factory.new(ZERO_ADDRESS); + uniswapRouter = await UniswapV2Router01.new(uniswapFactory.address, weth.address); + + // deploy Argent + registry = await Registry.new(); + dappRegistry = await DappRegistry.new(0); + guardianStorage = await GuardianStorage.new(); + transferStorage = await TransferStorage.new(); + module = await ArgentModule.new( + registry.address, + guardianStorage.address, + transferStorage.address, + dappRegistry.address, + uniswapRouter.address, + SECURITY_PERIOD, + SECURITY_WINDOW, + RECOVERY_PERIOD, + LOCK_PERIOD); + await registry.registerModule(module.address, ethers.utils.formatBytes32String("ArgentModule")); + const wethFilter = await Filter.new(true); + const filter = await Filter.new(false); + await dappRegistry.addDapp(0, pool.address, filter.address); + await dappRegistry.addDapp(0, wethPool.address, wethFilter.address); + await dappRegistry.addDapp(0, relayer, ZERO_ADDRESS); + + const walletImplementation = await BaseWallet.new(); + factory = await WalletFactory.new( + walletImplementation.address, + guardianStorage.address, + refundAddress); + await factory.addManager(infrastructure); + manager = new RelayManager(guardianStorage.address, ZERO_ADDRESS); + }); + + beforeEach(async () => { + // create wallet + const walletAddress = await utils.createWallet(factory.address, owner, [module.address], guardian1); + wallet = await BaseWallet.at(walletAddress); + + // fund wallet + await wallet.send(web3.utils.toWei("1")); + await weth.deposit({ value: web3.utils.toWei("1") }); + await weth.transfer(wallet.address, web3.utils.toWei("1")); + + await initNonce(wallet, module, manager, SECURITY_PERIOD); + }); + + const multiCall = async (transactions) => { + const txReceipt = await manager.relay( + module, + "multiCall", + [wallet.address, transactions], + wallet, + [owner], + 1, + ETH_TOKEN, + relayer); + return utils.parseRelayReceipt(txReceipt); + }; + + const deposit = async () => multiCall(encodeCalls([ + [weth, "approve", [pool.address, AMOUNT]], + [pool, "deposit", [AMOUNT]] + ])); + + const depositETH = async () => multiCall(encodeCalls([[wethPool, "depositETH", [], AMOUNT]])); + + const withdraw = async ({ all }) => { + const bal = (await pool.balanceOf(wallet.address)).toString(); + return multiCall(encodeCalls([all ? [pool, "withdrawAll"] : [pool, "withdraw", [bal]]])); + }; + + const withdrawETH = async ({ all }) => { + const bal = (await wethPool.balanceOf(wallet.address)).toString(); + return multiCall(encodeCalls([all ? [wethPool, "withdrawAllETH"] : [wethPool, "withdrawETH", [bal]]])); + }; + + it("should allow deposits", async () => { + const { success, error } = await deposit(); + assert.isTrue(success, `deposit failed: "${error}"`); + }); + + it("should allow ETH deposits", async () => { + const { success, error } = await depositETH(); + assert.isTrue(success, `depositETH failed: "${error}"`); + }); + + it("should allow withdrawals (withdraw(amount))", async () => { + await deposit(); + const { success, error } = await withdraw({ all: false }); + assert.isTrue(success, `withdraw(amount) failed: "${error}"`); + }); + + it("should allow withdrawals (withdrawETH(amount))", async () => { + await depositETH(); + const { success, error } = await withdrawETH({ all: false }); + assert.isTrue(success, `withdrawETH(amount) failed: "${error}"`); + }); + + it("should allow withdrawals (withdrawAll())", async () => { + await deposit(); + const { success, error } = await withdraw({ all: true }); + assert.isTrue(success, `withdrawAll() failed: "${error}"`); + }); + + it("should allow withdrawals (withdrawAllETH())", async () => { + await depositETH(); + const { success, error } = await withdrawETH({ all: true }); + assert.isTrue(success, `withdrawAllETH() failed: "${error}"`); + }); + + it("should not allow direct transfers to pool", async () => { + const { success, error } = await multiCall(encodeCalls([[weth, "transfer", [pool.address, AMOUNT]]])); + assert.isFalse(success, "transfer should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow unsupported method", async () => { + const { success, error } = await multiCall(encodeCalls([[pool, "earn"]])); + assert.isFalse(success, "earn() should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should not allow sending ETH to non-weth pool", async () => { + const { success, error } = await multiCall([encodeTransaction(pool.address, AMOUNT, "0x")]); + assert.isFalse(success, "sending ETH should have failed"); + assert.equal(error, "TM: call not authorised"); + }); + + it("should allow sending ETH to weth pool", async () => { + const { success, error } = await multiCall([encodeTransaction(wethPool.address, AMOUNT, "0x")]); + assert.isTrue(success, `sending ETH failed: "${error}"`); + }); +}); diff --git a/truffle-config-contracts-legacy-1.6.js b/truffle-config-contracts-legacy-1.6.js deleted file mode 100644 index 9dbacb8df..000000000 --- a/truffle-config-contracts-legacy-1.6.js +++ /dev/null @@ -1,20 +0,0 @@ -const baseConfig = require("./truffle-config.base.js"); - -module.exports = { - ...baseConfig, - contracts_directory: "contracts-legacy/v1.6.0", - contracts_build_directory: "build-legacy/v1.6.0", - - compilers: { - solc: { - version: "0.5.4", - docker: true, - settings: { - optimizer: { - enabled: true, - runs: 999, - }, - }, - }, - }, -}; diff --git a/truffle-config-contracts-test.js b/truffle-config-contracts-test.js index 42189d97c..e68f08073 100644 --- a/truffle-config-contracts-test.js +++ b/truffle-config-contracts-test.js @@ -6,8 +6,8 @@ module.exports = { compilers: { solc: { - version: "0.6.12", - docker: true, + version: "0.8.3", + docker: false, settings: { optimizer: { enabled: true, diff --git a/truffle-config-infrastructure-0.5.js b/truffle-config-infrastructure-0.5.js index 7df15c25d..7b39fb394 100644 --- a/truffle-config-infrastructure-0.5.js +++ b/truffle-config-infrastructure-0.5.js @@ -7,7 +7,7 @@ module.exports = { compilers: { solc: { version: "0.5.4", - docker: true, + docker: false, settings: { optimizer: { enabled: true, diff --git a/truffle-config-infrastructure.js b/truffle-config-infrastructure.js index 730a7b1f2..34d8f667e 100644 --- a/truffle-config-infrastructure.js +++ b/truffle-config-infrastructure.js @@ -6,8 +6,8 @@ module.exports = { compilers: { solc: { - version: "0.6.12", - docker: true, + version: "0.8.3", + docker: false, settings: { optimizer: { enabled: true, diff --git a/truffle-config-lib.js b/truffle-config-lib-0.5.js similarity index 82% rename from truffle-config-lib.js rename to truffle-config-lib-0.5.js index 22264b42e..c169e70bf 100644 --- a/truffle-config-lib.js +++ b/truffle-config-lib-0.5.js @@ -2,12 +2,12 @@ const baseConfig = require("./truffle-config.base.js"); module.exports = { ...baseConfig, - contracts_directory: "lib", + contracts_directory: "lib_0.5", compilers: { solc: { version: "0.5.4", - docker: true, + docker: false, settings: { optimizer: { enabled: true, diff --git a/truffle-config-contracts-legacy-1.3.js b/truffle-config-lib-0.7.js similarity index 56% rename from truffle-config-contracts-legacy-1.3.js rename to truffle-config-lib-0.7.js index bb0369a50..29ad8bf46 100644 --- a/truffle-config-contracts-legacy-1.3.js +++ b/truffle-config-lib-0.7.js @@ -2,17 +2,16 @@ const baseConfig = require("./truffle-config.base.js"); module.exports = { ...baseConfig, - contracts_directory: "contracts-legacy/v1.3.0", - contracts_build_directory: "build-legacy/v1.3.0", + contracts_directory: "lib_0.7", compilers: { solc: { - version: "0.5.4", - docker: true, + version: "0.7.5", + docker: false, settings: { optimizer: { enabled: true, - runs: 999, + runs: 200, }, }, }, diff --git a/truffle-config-modules.js b/truffle-config-modules.js index c46887de7..5a5fee62a 100644 --- a/truffle-config-modules.js +++ b/truffle-config-modules.js @@ -6,12 +6,12 @@ module.exports = { compilers: { solc: { - version: "0.6.12", - docker: true, + version: "0.8.3", + docker: false, settings: { optimizer: { enabled: true, - runs: 999, + runs: 300, }, }, }, diff --git a/truffle-config-wallet.js b/truffle-config-wallet.js index 74522d3c8..db9fc9624 100644 --- a/truffle-config-wallet.js +++ b/truffle-config-wallet.js @@ -6,8 +6,8 @@ module.exports = { compilers: { solc: { - version: "0.6.12", - docker: true, + version: "0.8.3", + docker: false, settings: { optimizer: { enabled: true, diff --git a/truffle-config.base.js b/truffle-config.base.js index 60cd7c362..d736785ca 100644 --- a/truffle-config.base.js +++ b/truffle-config.base.js @@ -23,13 +23,13 @@ const HDWalletProvider = require("@truffle/hdwallet-provider"); // const deployManager = require("./utils/deploy-manager.js"); const _gasPrice = process.env.DEPLOYER_GAS_PRICE || 20000000000; -const _gasLimit = 6000000; +const _gasLimit = 8000000; function getKeys() { // NOTE: While https://github.com/trufflesuite/truffle/issues/1054 is implemented we are using a temporary fix // const { pkey, infuraKey } = await deployManager.getProps(); // return (pkey, infuraKey); - return { pkey: process.env.PKEY, infuraKey: process.env.INFURA_KEY }; + return { pkey: process.env.PKEY_TEST, infuraKey: process.env.INFURA_KEY }; } // const fs = require('fs'); @@ -88,6 +88,13 @@ module.exports = { gasPrice: _gasPrice, }, + prodFork: { + host: "localhost", + port: 3601, + gasPrice: 0, + network_id: "1", + }, + // Another network with more advanced options... // advanced: { // port: 8777, // Custom port diff --git a/truffle-config.js b/truffle-config.js index a155fcc66..95528a07f 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -6,8 +6,8 @@ module.exports = { compilers: { solc: { - version: "0.6.12", - docker: true, + version: "0.8.3", + docker: false, settings: { optimizer: { enabled: true, diff --git a/utils/config-schema.json b/utils/config-schema.json index f2e0465a7..455e52c81 100644 --- a/utils/config-schema.json +++ b/utils/config-schema.json @@ -76,7 +76,10 @@ "maker": { "type": "object", "properties": { - "tub": { + "pot": { + "$ref": "#/definitions/ethaddress" + }, + "migration": { "$ref": "#/definitions/ethaddress" } } @@ -86,6 +89,18 @@ "properties": { "factory": { "$ref": "#/definitions/ethaddress" + }, + "factoryV2": { + "$ref": "#/definitions/ethaddress" + }, + "v2Router": { + "$ref": "#/definitions/ethaddress" + }, + "initCodeV2": { + "type": "string" + }, + "unizap": { + "$ref": "#/definitions/ethaddress" } } }, @@ -96,6 +111,82 @@ "$ref": "#/definitions/ethaddress" } } + }, + "paraswap": { + "type": "object", + "properties": { + "contract": { + "$ref": "#/definitions/ethaddress" + }, + "paraswapProxy": { + "$ref": "#/definitions/ethaddress" + }, + "adapters": { + "type": "object" + }, + "uniswapForks": { + "type": "array", + "items": { "type": "object" } + }, + "targetExchanges": { + "type": "object" + }, + "proxies": { + "type": "object" + }, + "marketMakers": { + "type": "array", + "items": { "$ref": "#/definitions/ethaddress" } + } + } + }, + "aave": { + "type": "object", + "properties": { + "contract": { + "$ref": "#/definitions/ethaddress" + }, + "lendingPool": { + "$ref": "#/definitions/ethaddress" + }, + "lendingPoolCore": { + "$ref": "#/definitions/ethaddress" + }, + "aTokens": { + "type": "array", + "items": { "$ref": "#/definitions/ethaddress" } + } + } + }, + "balancer": { + "type": "object", + "properties": { + "pools": { + "type": "array", + "items": { "$ref": "#/definitions/ethaddress" } + } + } + }, + "yearn": { + "type": "object", + "properties": { + "pools": { + "type": "array", + "items": { "$ref": "#/definitions/ethaddress" } + }, + "wethpools": { + "type": "array", + "items": { "$ref": "#/definitions/ethaddress" } + } + } + }, + "lido": { + "type": "object", + "properties": { + "contract": { + "$ref": "#/definitions/ethaddress" + } + } } } }, @@ -108,6 +199,9 @@ "WalletFactory": { "$ref": "#/definitions/ethaddress" }, + "WalletFactory16": { + "$ref": "#/definitions/ethaddress" + }, "ENSResolver": { "$ref": "#/definitions/ethaddress" }, @@ -120,19 +214,16 @@ "ModuleRegistry": { "$ref": "#/definitions/ethaddress" }, - "BaseWallet": { + "DappRegistry": { "$ref": "#/definitions/ethaddress" }, - "CompoundRegistry": { - "$ref": "#/definitions/ethaddress" - }, - "MakerRegistry": { + "BaseWallet": { "$ref": "#/definitions/ethaddress" }, - "DexRegistry": { + "ArgentWalletDetector": { "$ref": "#/definitions/ethaddress" }, - "ArgentWalletDetector": { + "MultiCallHelper": { "$ref": "#/definitions/ethaddress" } }, @@ -143,85 +234,44 @@ "ENSManager", "TokenPriceProvider", "ModuleRegistry", - "BaseWallet", - "CompoundRegistry", - "MakerRegistry", - "DexRegistry" + "BaseWallet" ], + "additionalProperties": true + }, + "filters": { + "type": "object", + "patternProperties": { + "\\w+Filter$": { + "oneOf": [ + { "$ref": "#/definitions/ethaddress" }, + { + "type": "array", + "items": { + "$ref": "#/definitions/ethaddress" + } + } + ] + } + }, "additionalProperties": false }, "modules": { "type": "object", "properties": { - "ModuleManager": { - "$ref": "#/definitions/ethaddress" - }, - "GuardianManager": { - "$ref": "#/definitions/ethaddress" - }, "GuardianStorage": { "$ref": "#/definitions/ethaddress" }, - "LimitStorage": { - "$ref": "#/definitions/ethaddress" - }, - "TokenPriceStorage": { - "$ref": "#/definitions/ethaddress" - }, - "TokenPriceRegistry": { - "$ref": "#/definitions/ethaddress" - }, - "LockManager": { - "$ref": "#/definitions/ethaddress" - }, - "RecoveryManager": { - "$ref": "#/definitions/ethaddress" - }, - "ApprovedTransfer": { - "$ref": "#/definitions/ethaddress" - }, - "TransferManager": { + "TokenRegistry": { "$ref": "#/definitions/ethaddress" }, "TransferStorage": { "$ref": "#/definitions/ethaddress" }, - "TokenExchanger": { - "$ref": "#/definitions/ethaddress" - }, - "NftTransfer": { - "$ref": "#/definitions/ethaddress" - }, - "CompoundManager": { - "$ref": "#/definitions/ethaddress" - }, - "MakerManager": { - "$ref": "#/definitions/ethaddress" - }, - "MakerV2Manager": { - "$ref": "#/definitions/ethaddress" - }, - "RelayerManager": { - "$ref": "#/definitions/ethaddress" - }, - "VersionManager": { + "ArgentModule": { "$ref": "#/definitions/ethaddress" } }, - "required": [ - "GuardianManager", - "GuardianStorage", - "LockManager", - "RecoveryManager", - "ApprovedTransfer", - "TransferManager", - "TransferStorage", - "TokenExchanger", - "NftTransfer", - "CompoundManager", - "MakerManager", - "MakerV2Manager" - ], + "required": ["GuardianStorage", "TransferStorage"], "additionalProperties": true }, "backend": { @@ -235,6 +285,9 @@ }, "refundCollector": { "$ref": "#/definitions/ethaddress" + }, + "tradeCommissionCollector": { + "$ref": "#/definitions/ethaddress" } }, "required": ["accounts"], @@ -326,10 +379,7 @@ "securityWindow": { "type": "integer" }, - "defaultLimit": { - "type": "string" - }, - "feeRatio": { + "timelockPeriod": { "type": "integer" } }, diff --git a/utils/config/development.json b/utils/config/development.json index 7533bca4e..0a44796ee 100644 --- a/utils/config/development.json +++ b/utils/config/development.json @@ -1 +1,181 @@ -{"ENS":{"deployOwnRegistry":true,"ensRegistry":"0x8437d85b21a4bf71e53FB49CC957752286246cd2","domain":"argent.xyz"},"backend":{"accounts":["0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"],"refundCollector":"0xdd32a8E9B39713b5Be04Cafb9C13761Ad92504A6"},"multisig":{"owners":["0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"],"threshold":1,"autosign":true},"settings":{"deployer":{"type":"ganache"},"lockPeriod":480,"recoveryPeriod":480,"securityPeriod":240,"securityWindow":240,"feeRatio":15,"defaultLimit":"1000000000000000000"},"CryptoKitties":{"contract":"0x0000000000000000000000000000000000000000"},"defi":{"weth":"0x0000000000000000000000000000000000000000","maker":{"deployOwn":true,"tub":"0x0000000000000000000000000000000000000000","pot":"0x0000000000000000000000000000000000000000","jug":"0x0000000000000000000000000000000000000000","migration":"0x19BCEBD43D23F75E7554669DF8db6dFA4c73bfa0"},"uniswap":{"deployOwn":true,"factory":"0xF6F2BD751fA1EE87648709B8229b5B22e5Ab4E3b"},"compound":{"comptroller":"0x0000000000000000000000000000000000000000","markets":{"0x0000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000"}},"paraswap":{"deployOwn":true,"contract":"0xb48c8Fa8Bc892f2F48A5Ea3C5dD3335aFECB9b0c","authorisedExchanges":{"Kyber":"0x4fe4749EF7E87AF72cF740cF96565d8e067Fc288"}}},"contracts":{"MultiSigWallet":"0x49a7da6e47679c88563D718B2B42d199DA30b86f","WalletFactory":"0x4fd02791a3A4cbE14315634C3AdD9aDBef5d73d8","ENSResolver":"0x2E5c38D7bD4d89561d3d914812C61f2DbaC7DEF6","ENSManager":"0xbFA8b09BC366B66c4434E2ab27E5A6Ae3E66B4A0","TokenPriceProvider":"0xE09C322B745434Fbb2D18c107c13795D4bDF3657","ModuleRegistry":"0xbC84993e855325AAF055A96582C366bD320165C2","BaseWallet":"0xea8beafa3ABF2eC7e2404550EcaECfcD999C0d5B","CompoundRegistry":"0x2B4162a630c1C51AC40e2d73dFeB5864606d888e","MakerRegistry":"0x355d26E5f04255F60E910F1aA1251DE5D10996E8","DexRegistry":"0xC1781f6Bfe39f3FAf8B84329670194E7447e7A40"},"modules":{"GuardianStorage":"0x2A88e232235E40f54D05B765EE03E86171439B16","TransferStorage":"0xba1B19248020B0dfcDc9C53eAE31722B478334aE","GuardianManager":"0x08238aD7707355dcC4A2f7819C00B0a22a4955C1","LockManager":"0xD46123b08c56530A8e09bB65B4DFEfA72418e97B","RecoveryManager":"0xef5C8Ae82202f2dAAE319A9b664Fc00074b565f4","ApprovedTransfer":"0x7200B43892c05231Fb5841dEf2782ceA5bbf4E14","TokenExchanger":"0x4F475aD35D69b274e4EC58D76eA4Ce7081336049","NftTransfer":"0x7e294552E729f9987910C9090B5F4155D29139db","MakerManager":"0x2718cAce241213B18F67C6d66B245EE77DB0Ce2a","TransferManager":"0xAD99276C293B7c491A8070d5E45EB380fC0f8976","CompoundManager":"0xf32E0eB1795746757232e240FC8b7EE23E678fb4","MakerV2Manager":"0x37446F84f72519553C739036E301a0CdCcC8c069","LimitStorage":"0x30ac1D5546Af7382986f8357919f6BDeDa09442F","TokenPriceStorage":"0x5dA4F81c17776495e3d7999ad9217748D257f074","RelayerModule":"0x171eb25c80da6adF0436668126285ca51e37eB30","RelayerManager":"0x6F6BfF298e182882091B8D0FF40208d09aE96B62","VersionManager":"0xA2a1ccaB58a416459576764881380408555f0fAf","LockStorage":"0x880d32dE65d6Ce64b2CeFE51C086A6deCB6D79C9","TokenPriceRegistry":"0x1c205F14B71b7d02A5F6af719F2d6abe6CE1140D"},"gitCommit":"460043a3e1cd619aa37a0dce8d7c1658c2eb7606"} \ No newline at end of file +{ + "ENS": { + "deployOwnRegistry": true, + "ensRegistry": "0xd3bfB856c417Ad968B245d3f98aeC598f03F7270", + "domain": "argent.xyz" + }, + "backend": { + "accounts": ["0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"], + "refundCollector": "0xdd32a8E9B39713b5Be04Cafb9C13761Ad92504A6", + "tradeCommissionCollector": "0xfb2a52012F5E757Ba748d1F8D7f582B8d1a613C0" + }, + "multisig": { + "owners": ["0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"], + "threshold": 1, + "autosign": true + }, + "settings": { + "deployer": { "type": "ganache" }, + "lockPeriod": 480, + "timelockPeriod": 2, + "recoveryPeriod": 480, + "securityPeriod": 240, + "securityWindow": 240 + }, + "CryptoKitties": { "contract": "0x0000000000000000000000000000000000000000" }, + "defi": { + "weth": "0x0000000000000000000000000000000000000000", + "maker": { + "deployOwn": true, + "tub": "0x0000000000000000000000000000000000000000", + "pot": "0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7", + "jug": "0x0000000000000000000000000000000000000000", + "migration": "0xa7ed470Dd9f1e2B656708D87e64e9ddCc79583c0" + }, + "uniswap": { + "deployOwn": true, + "factory": "0xEdbb3C98DC9A226518c75E8F641cD0C3F8e3a364", + "v2Router": "0x45cf31B8A87097E0ac46953Fd757dF8427B9d96D", + "unizap": "0xc6a5Da387E927c8dd3cDF783004a85Eed2A052B1", + "factoryV2": "0x2B9b7E41652d7cd3038a6531AB081805c58870E7", + "initCode": "0x505fef446e85683f8d469f9a29612718156edbc931150eb74e2f3ffae9b35392", + "initCodeV2": "0xe72aaebeb0e8262e90854f8e0767fe9e988cf6a05a826b919d1f07b1c092715d", + "paraswapUniV2Router": "0x86d3579b043585A97532514016dCF0C2d6C4b6a1" + }, + "compound": { + "comptroller": "0x0000000000000000000000000000000000000000", + "markets": { + "0x6b175474e89094c44da98b954eedeac495271d0f": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643" + } + }, + "paraswap": { + "deployOwn": true, + "contract": "0x05b02F6d3d0a602f12212E04dc9a13336e2e6D39", + "adapters": { "uniswap": "0x60003b9Ad41c1cB8bCB9cEff16bdD03cf06f4c5F" }, + "targetExchanges": {}, + "marketMakers": [], + "proxies": { "zeroexv2": "0x95e6f48254609a6ee006f7d493c8e5fb97094cef" }, + "uniswapProxy": "0x3A125bE5bB8A3E1C171947c384795B4a488B74A0", + "uniswapForks": [ + { + "factory": "0x0000000000000000000000000000000000000001", + "initCode": "0x0000000000000000000000000000000000000000000000000000000000000001", + "paraswapUniV2Router": "0xBc1315CD2671BC498fDAb42aE1214068003DC51e" + }, + { + "factory": "0x0000000000000000000000000000000000000002", + "initCode": "0x0000000000000000000000000000000000000000000000000000000000000002", + "paraswapUniV2Router": "0xEC4c8110E5B5Bf0ad8aa89e3371d9C3b8CdCD778" + }, + { + "factory": "0x0000000000000000000000000000000000000003", + "initCode": "0x0000000000000000000000000000000000000000000000000000000000000003", + "paraswapUniV2Router": "0xF806F9972F9A34FC05394cA6CF2cc606297Ca6D5" + } + ] + }, + "aave": { + "contract": "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9", + "lendingPool": "0x9E5C7835E4b13368fd628196C4f1c6cEc89673Fa", + "lendingPoolCore": "0x4295ee704716950a4de7438086d6f0fbc0ba9472", + "aTokens": ["0x2433A1b6FcF156956599280C3Eb1863247CFE675"] + }, + "balancer": { + "pools": [ + "0x59a19d8c652fa0284f44113d0ff9aba70bd46fb4", + "0x1eff8af5d577060ba4ac8a29a13525bb0ee2a3d5", + "0x8a649274e4d777ffc6851f13d23a86bbfa2f2fbf", + "0x5b475f2cc362265ddf6a958f3519a35b305d2824", + "0xf54025af2dc86809be1153c1f20d77adb7e8ecf4", + "0xe036cce08cf4e23d33bc6b18e53caf532afa8513", + "0x72cd8f4504941bf8c5a21d1fd83a96499fd71d2c", + "0x454c1d458f9082252750ba42d60fae0887868a3b", + "0x9dde0b1d39d0d2c6589cde1bfed3542d2a3c5b11", + "0xb1f9ec02480dd9e16053b010dfc6e6c4b72ecad5", + "0xe867be952ee17d2d294f2de62b13b9f4af521e9a", + "0xe0e6b25b22173849668c85e06bc2ce1f69baff8c", + "0xe93e8aa4e88359dacf33c491cf5bd56eb6c110c1", + "0xee9a6009b926645d33e10ee5577e9c8d3c95c165", + "0xd3c8dcfcf2a5203f6a3210591dabea14e181db2d", + "0x9762c600a39bda3359f50a1fb1f90945cead379d", + "0x2e22a35a29f0ccafe8c6fb935f7d0269f706ea72", + "0xa5da8cc7167070b62fdcb332ef097a55a68d8824", + "0xf9ab7776c7baeed1d65f0492fe2bb3951a1787ef", + "0x9866772a9bdb4dc9d2c5a4753e8658b8b0ca1fc3", + "0x7c90a3cd7ec80dd2f633ed562480abbeed3be546", + "0x834fb8276b4e8a24010e2108fdd7f8417c8922bd", + "0x6b9887422e2a4ae11577f59ea9c01a6c998752e2", + "0xa0313dea2ed259edd2d95986cc9046d1a65f649b", + "0x5b18df96d3c8b9f1d1b9e38752498f92d1e2d490", + "0x41284a88d970d3552a26fae680692ed40b34010c", + "0x5e37910cfb8de1b14ec4e4bac0bec27c35dc07d5", + "0x57755f7dec33320bca83159c26e93751bfd30fbe", + "0xe010fcda8894c16a8acfef7b37741a760faeddc4" + ] + }, + "yearn": { + "pools": [ + "0x597aD1e0c13Bfe8025993D9e79C69E1c0233522e", + "0x5dbcF33D8c2E976c6b560249878e6F1491Bca25c", + "0x37d19d1c4E1fa9DC47bD1eA12f742a0887eDa74a", + "0xACd43E627e64355f1861cEC6d3a6688B31a6F952", + "0x2f08119C6f07c006695E079AAFc638b8789FAf18", + "0xBA2E7Fed597fd0E3e70f5130BcDbbFE06bB94fe1", + "0x2994529C0652D127b7842094103715ec5299bBed", + "0x7Ff566E1d69DEfF32a7b244aE7276b9f90e9D0f6", + "0x9cA85572E6A3EbF24dEDd195623F188735A5179f", + "0xec0d8D3ED5477106c6D4ea27D90a60e594693C90", + "0x629c759D1E83eFbF63d84eb3868B564d9521C129", + "0x881b06da56BB5675c54E4Ed311c21E54C5025298", + "0x29E240CFD7946BA20895a7a02eDb25C210f9f324" + ], + "wethPools": ["0xe1237aA7f535b0CC33Fd973D66cBf830354D16c7"] + }, + "lido": { "contract": "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" } + }, + "contracts": { + "MultiSigWallet": "0x32B283164B5A283A01208CA894b534b9eA9C4632", + "WalletFactory": "0x04d62488Da62903eAe5cd03815d7819337230c87", + "ENSResolver": "0xa6bdC6E136DDA9362a65f86A6441CaCf8571F9f9", + "ENSManager": "0xDE2D1DE7bbc81560cB34421bf562C41B1bB415F2", + "TokenPriceProvider": "0xE09C322B745434Fbb2D18c107c13795D4bDF3657", + "ModuleRegistry": "0x3d7Eec9C41c7A99489fD17e6B087f9C827b16d3F", + "BaseWallet": "0x91658d67482022A0dd3F0472770f26E14e65B131", + "DappRegistry": "0xF09d4404cDF1b70FFB323287F5097c0B61E3c2A9", + "ArgentWalletDetector": "0xC58F9C1af2ED42B4902Aa59964a761cfa35305C3", + "MultiCallHelper": "0xD4De9f4FB84033fBE33a0c0EfF490F003e3f4fE9", + "TokenRegistry": "0x9b352e98398a4db853A7814E60989519990FA357", + "OnlyApproveFilter": "0x9e90054F4B6730cffAf1E6f6ea10e1bF9dD26dbb" + }, + "filters": { + "OnlyApproveFilter": "0x4c51C686CBE79e26B8Cc93b04BF0f530E7B0a872", + "CompoundFilter": ["0x03F298Cd34804364B9f28D224f9E1e26b80740d3"], + "ParaswapFilter": "0x18706F2EeAe13383bB8e2C6d8E9C1f11B00112AE", + "ParaswapUniV2RouterFilter": [ + "0xFfec8Cd7CDBfeD2A94Ee2CA84698F6cd3CaB8391", + "0x573eBC3c7C9651a12d31Fe5F4326eB8Db9302f9d", + "0xd5d1ad8a7D81d41345826C44Dc692580F5CA7466", + "0x03BA748C043Ef8c2122f438fC4aB0ac41c276613" + ], + "CurveFilter": "0x8d6ce59F3FDb84A36FEB7Dea7f4baC67746B980a", + "AaveV2Filter": "0x989B76F53109Cc63432f1DB2FEe80bf3200e52Aa", + "BalancerFilter": "0x2FF3E6C37d41c55D85406Ae15909201439C498EA", + "YearnFilter": [ + "0xa95Fc1a5C9eB48FBc51dD4421B2d267a544c2201", + "0x366b3001D730E58D45895cf635D5582f8B88Abe9" + ], + "LidoFilter": "0x8C9E0820C488d9211F9ab6e151E1473F35BC57DD", + "PotFilter": "0xf69b1A0d17410AFC23160d44d2Fb099db9806B1E", + "DaiJoinFilter": "0x84F46628D78e7de013279c33e7B262488ce4421D", + "VatFilter": "0xdDc226267137188F78E117bc9B12c38b80beaDa7", + "UniswapV2UniZapFilter": "0x1a7e1bbF2C50446DaF10Aa1d369480492bE67bF7", + "AaveV1LendingPoolFilter": "0x7A0Ec30CA80AeaCb3A8346780eBE239A869ea854", + "AaveV1ATokenFilter": "0x3804d2E6bCc4AAE38a801B55F42EfE367E88006f" + }, + "modules": { + "GuardianStorage": "0x4bE79425D3A40eBeCDfF4B7A393F879185F6E96d", + "TransferStorage": "0x44cAA0c71800eEA0Ec750Ba1A9Ff420cbc393952", + "ArgentModule": "0xc01A8D9662D4C2A1ef5d7A828642038D274C3FC4" + }, + "gitCommit": "cef25febe19d75a6364287b7533baf75ceec9117" +} diff --git a/utils/config/kovan-fork.json b/utils/config/kovan-fork.json deleted file mode 100644 index 959b6a0f7..000000000 --- a/utils/config/kovan-fork.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "ENS": { - "deployOwnRegistry": true, - "ensRegistry": "0x63347C9F2863dcBb723cd8cF1b51b2E87dbD917c", - "domain": "argent.xyz" - }, - "backend": { - "accounts": [] - }, - "multisig": { - "owners": [], - "threshold": 1, - "autosign": true - }, - "settings": { - "deployer": { - "type": "ganache" - }, - "privateKey": { - "type": "plain", - "options": { - "envvar": "KOVAN_PRIV_KEY" - } - }, - "lockPeriod": 480, - "recoveryPeriod": 480, - "securityPeriod": 240, - "securityWindow": 240, - "feeRatio": 15, - "defaultLimit": "1000000000000000000" - }, - "Kyber": { - "deployOwn": true, - "contract": "0x7F6eB19B148058B71D8BC9D1723E55e4d2da40a6" - }, - "CryptoKitties": { - "contract": "0x0000000000000000000000000000000000000000" - }, - "defi": { - "maker": { - "tub": "0xa71937147b55deb8a530c7229c442fd3f31b7db2", - "migration": "0x411b2faa662c8e3e5cf8f01dfdae0aee482ca7b0", - "pot": "0xea190dbdc7adf265260ec4da6e9675fd4f5a78bb", - "jug": "0xcbb7718c9f39d05aeede1c472ca8bf804b2f1ead", - "batJoin": "0x2a4c485b1b8dfb46accfbecaf75b6188a59dbd0a", - "batFaucet": "0x57aAeAE905376a4B1899bA81364b4cE2519CBfB3", - "mkrFaucet": "0xCbd3e165cE589657feFd2D38Ad6B6596A1f734F6" - }, - "uniswap": { - "factory": "0x944365B2980cB75D5C540b44D9e3e63CF0fb2111" - }, - "compound": { - "comptroller": "0x142d11cb90a2b40f7d0c55ed1804988dfc316fae", - "markets": { - "0xc4375b7de8af5a38a93548eb8453a498222c4ff2": "0x0a1e4d0b5c71b955c0a5993023fc48ba6e380496", - "0xd0a1e359811322d97991e03f863a0c30c2cf029c": "0xd83f707f003a1f0b1535028ab356fce2667ab855" - } - } - }, - "contracts": { - "MultiSigWallet": "0xEC5F4731e40231ae57DfB0a4C644eD55F90aFeb3", - "WalletFactory": "0xe18806A33C74cBE5E14b90109e33Ad8512BB1A5a", - "ENSResolver": "0x17f8c87AabD5b9dEBDA5D942b852E6F7174232D0", - "ENSManager": "0x9812e6aa9Ac3500f8Dd4523058d9E48EE4B7A069", - "TokenPriceProvider": "0xb2760fB88EAC409A173ABCC6e9FB5ACc8a8D46e9", - "ModuleRegistry": "0xB9d7cB1d1b9440526777B39629c7035c33D8C64E", - "BaseWallet": "0x88ba95809C47bbA1cf9A657fcd3C81a78d608aA5", - "CompoundRegistry": "0xB3a945f078B44b98f93145f7a820e95a0a98e386", - "MakerRegistry": "0xe9B43D909a17cE24C7d43624E7553376Fa971041" - }, - "modules": { - "GuardianStorage": "0xD6E5cD0666A2607CfaBddB9aD0E30131e5359515", - "TransferStorage": "0x95B7d87586Fd218F56FDe21b93E3bc2eE4e9B406", - "GuardianManager": "0x96b8b73D55C713CEBfb1eee3D5F476766F679669", - "LockManager": "0x9b7fC8EF0a625451FA0F21422d2Dd4164f6cf82E", - "RecoveryManager": "0x479120703c51aE2a242F1D04815289f8BB2CdAED", - "ApprovedTransfer": "0xA825F25958F9C68573A3Ceb827345107CEdE0Aa8", - "TokenTransfer": "0xb81eC49d93Ed336771C1bC6448bFB262403fEef4", - "TokenExchanger": "0xd9CE60816ADF1E365f04Dea96AAF2775942C42C6", - "NftTransfer": "0xCAb6F3134894C10Bd357335D00412e0586fcE85E", - "MakerManager": "0x74BB0268ABf905C1Eb12f8300249070B56E76d77", - "MakerV2Manager": "0x69750794c8F3a6A584f850745aF3552158984b99", - "CompoundManager": "0xbf32494BedC3AcAccBb54F8560262A80609a1ABD", - "TransferManager": "0xcE7968f585fCe79AD1161A766F13aBd3375a0185" - }, - "gitCommit": "e93c93e71c50b186f1c73097425044343703cdff" -} diff --git a/utils/config/kovan.json b/utils/config/kovan.json deleted file mode 100644 index 072d7750b..000000000 --- a/utils/config/kovan.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "ENS": { - "deployOwnRegistry": true, - "ensRegistry": "0xe03e107077B97cDf32351eC254BBd0A1FEaB2AA2", - "domain": "argent.xyz" - }, - "backend": { - "accounts": [] - }, - "multisig": { - "owners": [], - "threshold": 1, - "autosign": true - }, - "settings": { - "deployer": { - "type": "infura", - "options": { - "network": "kovan", - "envvar": "INFURA_KEY" - } - }, - "privateKey": { - "type": "plain", - "options": { - "envvar": "KOVAN_PRIV_KEY" - } - }, - "lockPeriod": 480, - "recoveryPeriod": 480, - "securityPeriod": 240, - "securityWindow": 240, - "feeRatio": 15, - "defaultLimit": "1000000000000000000" - }, - "Kyber": { - "deployOwn": true, - "contract": "0xaC684E5464B91524E13351c2360290c21C7E5100" - }, - "CryptoKitties": { - "contract": "0x0000000000000000000000000000000000000000" - }, - "defi": { - "maker": { - "tub": "0xa71937147b55deb8a530c7229c442fd3f31b7db2", - "migration": "0x411b2faa662c8e3e5cf8f01dfdae0aee482ca7b0", - "pot": "0xea190dbdc7adf265260ec4da6e9675fd4f5a78bb", - "jug": "0xcbb7718c9f39d05aeede1c472ca8bf804b2f1ead", - "batJoin": "0x2a4c485b1b8dfb46accfbecaf75b6188a59dbd0a", - "batFaucet": "0x57aAeAE905376a4B1899bA81364b4cE2519CBfB3", - "mkrFaucet": "0xCbd3e165cE589657feFd2D38Ad6B6596A1f734F6" - }, - "uniswap": { - "factory": "0x944365B2980cB75D5C540b44D9e3e63CF0fb2111" - }, - "compound": { - "comptroller": "0x142d11cb90a2b40f7d0c55ed1804988dfc316fae", - "markets": { - "0xc4375b7de8af5a38a93548eb8453a498222c4ff2": "0x0a1e4d0b5c71b955c0a5993023fc48ba6e380496", - "0xd0a1e359811322d97991e03f863a0c30c2cf029c": "0xd83f707f003a1f0b1535028ab356fce2667ab855" - } - } - }, - "contracts": { - "MultiSigWallet": "0xEC5F4731e40231ae57DfB0a4C644eD55F90aFeb3", - "WalletFactory": "0xe18806A33C74cBE5E14b90109e33Ad8512BB1A5a", - "ENSResolver": "0x17f8c87AabD5b9dEBDA5D942b852E6F7174232D0", - "ENSManager": "0x9812e6aa9Ac3500f8Dd4523058d9E48EE4B7A069", - "TokenPriceProvider": "0xb2760fB88EAC409A173ABCC6e9FB5ACc8a8D46e9", - "ModuleRegistry": "0xB9d7cB1d1b9440526777B39629c7035c33D8C64E", - "BaseWallet": "0x88ba95809C47bbA1cf9A657fcd3C81a78d608aA5", - "CompoundRegistry": "0xB3a945f078B44b98f93145f7a820e95a0a98e386", - "MakerRegistry": "0x5E102B9ADf82208fDcABDc9D24a4DF562cE7F896" - }, - "modules": { - "GuardianStorage": "0xD6E5cD0666A2607CfaBddB9aD0E30131e5359515", - "TransferStorage": "0x95B7d87586Fd218F56FDe21b93E3bc2eE4e9B406", - "GuardianManager": "0x96b8b73D55C713CEBfb1eee3D5F476766F679669", - "LockManager": "0x9b7fC8EF0a625451FA0F21422d2Dd4164f6cf82E", - "RecoveryManager": "0x479120703c51aE2a242F1D04815289f8BB2CdAED", - "ApprovedTransfer": "0xA825F25958F9C68573A3Ceb827345107CEdE0Aa8", - "TokenTransfer": "0xb81eC49d93Ed336771C1bC6448bFB262403fEef4", - "TokenExchanger": "0xd9CE60816ADF1E365f04Dea96AAF2775942C42C6", - "NftTransfer": "0xCAb6F3134894C10Bd357335D00412e0586fcE85E", - "MakerManager": "0x74BB0268ABf905C1Eb12f8300249070B56E76d77", - "MakerV2Manager": "0x7bb678505B6729f40bB1BE34572685766009492f", - "CompoundManager": "0xbf32494BedC3AcAccBb54F8560262A80609a1ABD", - "TransferManager": "0xcE7968f585fCe79AD1161A766F13aBd3375a0185" - }, - "gitCommit": "0afe14d8a6131725287700d51390b50c22a7f79a" -} diff --git a/utils/configurator.js b/utils/configurator.js index cac5b30b4..6818018b8 100644 --- a/utils/configurator.js +++ b/utils/configurator.js @@ -22,6 +22,11 @@ class Configurator { Object.assign(this._config.contracts, contracts); } + updateFilterAddresses(filters) { + if (!this._config.filters) this._config.filters = {}; + Object.assign(this._config.filters, filters); + } + updateModuleAddresses(modules) { if (!this._config.modules) this._config.modules = {}; Object.assign(this._config.modules, modules); @@ -31,9 +36,11 @@ class Configurator { this._config.ENS.ensRegistry = address; } - updateParaswap(address, authorisedExchanges) { + updateParaswap(address, uniswapProxy, adapters, targetExchanges) { this._config.defi.paraswap.contract = address; - this._config.defi.paraswap.authorisedExchanges = { ...authorisedExchanges }; + this._config.defi.paraswap.uniswapProxy = uniswapProxy; + this._config.defi.paraswap.adapters = { ...adapters }; + this._config.defi.paraswap.targetExchanges = { ...targetExchanges }; } updateMakerMigration(address) { @@ -44,6 +51,13 @@ class Configurator { this._config.defi.uniswap.factory = address; } + updateUniswapV2(factory, router, zap, initCode) { + this._config.defi.uniswap.factoryV2 = factory; + this._config.defi.uniswap.v2Router = router; + this._config.defi.uniswap.unizap = zap; + this._config.defi.uniswap.initCodeV2 = initCode; + } + updateBackendAccounts(accounts) { this._config.backend.accounts = accounts; } diff --git a/utils/defi-deployer.js b/utils/defi-deployer.js index e8a98a928..e53944320 100644 --- a/utils/defi-deployer.js +++ b/utils/defi-deployer.js @@ -40,19 +40,20 @@ module.exports = { const uniswapFactory = await UniswapFactory.new(); const uniswapTemplateExchange = await UniswapExchange.new(); await uniswapFactory.initializeFactory(uniswapTemplateExchange.address); + const uniswapExchanges = {}; for (let i = 0; i < tokens.length; i += 1) { const token = tokens[i]; await uniswapFactory.createExchange(token.address, { from: infrastructure }); const uniswapExchangeAddress = await uniswapFactory.getExchange(token.address); const tokenExchange = await UniswapExchange.at(uniswapExchangeAddress); - + uniswapExchanges[token.address] = tokenExchange; const tokenLiquidity = new BN(ethLiquidity).mul(WAD).div(ethPerToken[i]); await token.mint(infrastructure, tokenLiquidity); await token.approve(tokenExchange.address, tokenLiquidity, { from: infrastructure }); const { timestamp } = await web3.eth.getBlock("latest"); await tokenExchange.addLiquidity(1, tokenLiquidity, timestamp + 300, { value: ethLiquidity, gasLimit: 150000, from: infrastructure }); } - return { uniswapFactory }; + return { uniswapFactory, uniswapExchanges }; }, deployMaker: async (infrastructure) => { @@ -170,6 +171,7 @@ module.exports = { bat, weth, vat, + daiJoin, batJoin, wethJoin, tub, diff --git a/utils/paraswap/sell-helper.js b/utils/paraswap/sell-helper.js index ef8f2c6db..11ebd3a53 100644 --- a/utils/paraswap/sell-helper.js +++ b/utils/paraswap/sell-helper.js @@ -1,11 +1,19 @@ const web3Coder = require("web3-eth-abi"); +const ethers = require("ethers"); + +const PARASWAP_ETH_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const ZERO_ADDRESS = ethers.constants.AddressZero; +const ZERO_BYTES32 = ethers.constants.HashZero; const getTargetExchange = (tokenFrom, exchangeName, exchangeAddress, targetExchanges) => targetExchanges[exchangeName]; const getPayLoad = (fromToken, toToken, exchange, data) => { - const { path } = data; + const { path, orders, order, signatures, signature } = data; switch (exchange.toLowerCase()) { case "uniswapv2": + case "sushiswap": + case "linkswap": + case "defiswap": return web3Coder.encodeParameter( { ParentStruct: { @@ -14,6 +22,73 @@ const getPayLoad = (fromToken, toToken, exchange, data) => { }, { path }, ); + case "curve": + return web3Coder.encodeParameter( + { + ParentStruct: { + i: "int128", + j: "int128", + deadline: "uint256", + underlyingSwap: "bool" + } + }, + { i: 0, j: 1, deadline: 4102444800, underlyingSwap: false } + ); + + case "paraswappoolv4": + return web3Coder.encodeParameter( + { + ParentStruct: { + order: { + makerToken: "address", + takerToken: "address", + makerAmount: "uint128", + takerAmount: "uint128", + maker: "address", + taker: "address", + txOrigin: "address", + pool: "bytes32", + expiry: "uint64", + salt: "uint256", + }, + signature: { + signatureType: "uint256", + v: "uint8", + r: "bytes32", + s: "bytes32", + } + } + }, + { + order, + signature + }); + case "paraswappoolv2": + return web3Coder.encodeParameter( + { + ParentStruct: { + "orders[]": { + makerAddress: "address", // Address that created the order. + takerAddress: "address", // Address that is allowed to fill the order. If set to 0, any address is allowed to fill the order. + feeRecipientAddress: "address", // Address that will recieve fees when order is filled. + senderAddress: "address", // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. + makerAssetAmount: "uint256", // Amount of makerAsset being offered by maker. Must be greater than 0. + takerAssetAmount: "uint256", // Amount of takerAsset being bid on by maker. Must be greater than 0. + makerFee: "uint256", // Fee paid to feeRecipient by maker when order is filled. + takerFee: "uint256", // Fee paid to feeRecipient by taker when order is filled. + expirationTimeSeconds: "uint256", // Timestamp in seconds at which order expires. + salt: "uint256", // Arbitrary number to facilitate uniqueness of the order's hash. + makerAssetData: "bytes", // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The leading bytes4 references the id of the asset proxy. + takerAssetData: "bytes" + }, + signatures: "bytes[]" + } + }, + { + orders, + signatures + } + ); default: return "0x"; @@ -34,30 +109,159 @@ const getRouteParams = (srcToken, destToken, route, exchanges, targetExchanges) }; }; -const makePathes = (srcToken, destToken, priceRoute, exchanges, targetExchanges, isMultiPath) => { - if (isMultiPath) { - return priceRoute.map((_routes) => { - const { tokenFrom, tokenTo } = _routes[0].data; - const routes = _routes.map((route) => getRouteParams(tokenFrom, tokenTo, route, exchanges, targetExchanges)); - let totalNetworkFee = 0; - for (let i = 0; i < routes.length; i += 1) { - totalNetworkFee += Number(routes[i].networkFee); - } - return { - to: tokenTo, - totalNetworkFee, - routes, - }; - }); +const getParaswappoolV2Data = ({ maker }) => ({ + signatures: [], + orders: [{ + makerAddress: maker, + takerAddress: ZERO_ADDRESS, + feeRecipientAddress: ZERO_ADDRESS, + senderAddress: ZERO_ADDRESS, + makerAssetAmount: 0, + takerAssetAmount: 0, + makerFee: 0, + takerFee: 0, + expirationTimeSeconds: 0, + salt: 0, + makerAssetData: "0x", + takerAssetData: "0x" + }] +}); + +const getParaswappoolV4Data = ({ fromToken, toToken, maker }) => ({ + signature: { signatureType: 0, v: 0, r: ZERO_BYTES32, s: ZERO_BYTES32 }, + order: { + makerToken: toToken, + takerToken: fromToken, + makerAmount: 0, + takerAmount: 0, + maker, + taker: ZERO_ADDRESS, + txOrigin: ZERO_ADDRESS, + pool: ZERO_BYTES32, + expiry: 0, + salt: 0, + } +}); + +const getParaswappoolData = ({ fromToken, toToken, maker, version = 2 }) => { + if (version === 2) return getParaswappoolV2Data({ maker }); + return getParaswappoolV4Data({ fromToken, toToken, maker }); +}; + +const getParaswappoolRoutes = ({ fromToken, toToken, maker }) => [{ + exchange: "paraswappoolv2", + percent: "50", + data: { + tokenFrom: fromToken, + tokenTo: toToken, + ...getParaswappoolData({ maker, version: 2 }) + }, +}, { + exchange: "paraswappoolv4", + percent: "50", + data: { + tokenFrom: fromToken, + tokenTo: toToken, + ...getParaswappoolData({ fromToken, toToken, maker, version: 4 }) } +}]; + +const getUniswapRoutes = ({ fromToken, toToken }) => [{ + exchange: "uniswap", + percent: "20", + data: { tokenFrom: fromToken, tokenTo: toToken }, +}, +{ + exchange: "uniswapv2", + percent: "20", + data: { tokenFrom: fromToken, tokenTo: toToken, path: [fromToken, toToken] }, +}, +{ + exchange: "sushiswap", + percent: "20", + data: { tokenFrom: fromToken, tokenTo: toToken, path: [fromToken, toToken] }, +}, +{ + exchange: "linkswap", + percent: "20", + data: { tokenFrom: fromToken, tokenTo: toToken, path: [fromToken, toToken] }, +}, +{ + exchange: "defiswap", + percent: "20", + data: { tokenFrom: fromToken, tokenTo: toToken, path: [fromToken, toToken] }, +}, +]; + +const getCurveRoutes = ({ fromToken, toToken }) => [{ + exchange: "curve", + percent: "100", + data: { tokenFrom: fromToken, tokenTo: toToken }, +}]; - return priceRoute.map((route) => ({ - to: destToken, - totalNetworkFee: route.data.networkFee ? route.data.networkFee : 0, - routes: [getRouteParams(srcToken, destToken, route, exchanges, targetExchanges)], - })); +const getWethRoutes = ({ fromToken, toToken }) => [{ + exchange: "weth", + percent: "100", + data: { tokenFrom: fromToken, tokenTo: toToken }, +}]; + +const getRoutesForExchange = ({ fromToken, toToken, maker, exchange }) => { + switch (exchange) { + case "paraswappoolv2": + case "paraswappoolv4": + return getParaswappoolRoutes({ fromToken, toToken, maker }); + case "uniswap": + case "uniswapv2": + case "sushiswap": + case "linkswap": + case "defiswap": + return getUniswapRoutes({ fromToken, toToken }); + case "curve": + return getCurveRoutes({ fromToken, toToken }); + case "weth": + return getWethRoutes({ fromToken, toToken }); + default: + throw new Error(`unknown exchange "${exchange}"`); + } }; +function getSimpleSwapParams({ + targetExchange, swapMethod, swapParams, + fromTokenContract = { address: PARASWAP_ETH_TOKEN }, toTokenContract = { address: PARASWAP_ETH_TOKEN }, + proxy = null, + fromAmount = 1, toAmount = 1, + beneficiary = ZERO_ADDRESS +}) { + let exchangeData = "0x"; + const callees = []; + const startIndexes = []; + const values = []; + + startIndexes.push(0); + if (fromTokenContract.address !== PARASWAP_ETH_TOKEN) { + callees.push(fromTokenContract.address); + values.push(0, 0); + exchangeData += fromTokenContract.contract.methods.approve((proxy || targetExchange).address, fromAmount).encodeABI().slice(2); + startIndexes.push(exchangeData.length / 2 - 1); + } else { + values.push(fromAmount); + } + callees.push(targetExchange.address); + if (swapMethod) { + exchangeData += targetExchange.contract.methods[swapMethod](...(swapParams || [])).encodeABI().slice(2); + } + startIndexes.push(exchangeData.length / 2 - 1); + return [ + fromTokenContract.address, toTokenContract.address, + fromAmount, toAmount, + 0, callees, exchangeData, startIndexes, values, + beneficiary, "abc", false + ]; +} + module.exports = { - makePathes, + getRouteParams, + getParaswappoolData, + getSimpleSwapParams, + getRoutesForExchange }; diff --git a/utils/relay-manager.js b/utils/relay-manager.js index dfd7e40fa..8f78f4461 100644 --- a/utils/relay-manager.js +++ b/utils/relay-manager.js @@ -1,55 +1,40 @@ /* global artifacts */ -const BN = require("bn.js"); const ethers = require("ethers"); const { ETH_TOKEN } = require("./utilities.js"); const utils = require("./utilities.js"); -const ITokenPriceRegistry = artifacts.require("ITokenPriceRegistry"); +const TestSimpleOracle = artifacts.require("TestSimpleOracle"); const IGuardianStorage = artifacts.require("IGuardianStorage"); -const IFeature = artifacts.require("IFeature"); class RelayManager { - async setRelayerManager(relayerManager) { - this.relayerManager = relayerManager; - - const guardianStorageAddress = await relayerManager.guardianStorage(); - this.guardianStorage = await IGuardianStorage.at(guardianStorageAddress); + constructor(guardianStorage, priceOracle) { + this.guardianStorage = guardianStorage; + this.priceOracle = priceOracle; } // Relays without refund by default, unless the gasPrice is explicitely set to be >0 - async relay(_feature, _method, _params, _wallet, _signers, + async relay(_module, _method, _params, _wallet, _signers, _gasPrice = 0, _refundToken = ETH_TOKEN, _refundAddress = ethers.constants.AddressZero) { const relayerAccount = await utils.getAccount(9); const nonce = await utils.getNonceForRelay(); const chainId = await utils.getChainId(); - const methodData = _feature.contract.methods[_method](..._params).encodeABI(); + const methodData = _module.contract.methods[_method](..._params).encodeABI(); - const gasLimit = await this.getGasLimitRefund(_feature, _method, _params, _wallet, _signers, _gasPrice); + const gasLimit = await RelayManager.getGasLimitRefund(_module, _method, _params, _wallet, _signers, _gasPrice); // Uncomment when debugging gas limits - // await this.debugGasLimits(_feature, _method, _params, _wallet, _signers); + // await this.debugGasLimits(_module, _method, _params, _wallet, _signers); // When the refund is in token (not ETH), calculate the amount needed for refund let gasPrice = _gasPrice; if (_refundToken !== ETH_TOKEN) { - const tokenPriceRegistryAddress = await this.relayerManager.tokenPriceRegistry(); - const tokenPriceRegistry = await ITokenPriceRegistry.at(tokenPriceRegistryAddress); - const tokenPrice = await tokenPriceRegistry.getTokenPrice(_refundToken); - - // How many tokens to pay per unit of gas spent - // refundCost = gasLimit * gasPrice - // tokenAmount = refundCost * 10^18 / tokenPrice - gasPrice = new BN(10).pow(new BN(18)) - .muln(_gasPrice) - .div(tokenPrice) - .toNumber(); + gasPrice = (await (await TestSimpleOracle.at(this.priceOracle)).ethToToken(_refundToken, _gasPrice)).toNumber(); } const signatures = await utils.signOffchain( _signers, - this.relayerManager.address, - _feature.address, + _module.address, 0, methodData, chainId, @@ -60,9 +45,8 @@ class RelayManager { _refundAddress, ); - const executeData = this.relayerManager.contract.methods.execute( + const executeData = _module.contract.methods.execute( _wallet.address, - _feature.address, methodData, nonce, signatures, @@ -88,9 +72,9 @@ class RelayManager { if (process.env.COVERAGE) { gas += 50000; } - const tx = await this.relayerManager.execute( + + const tx = await _module.execute( _wallet.address, - _feature.address, methodData, nonce, signatures, @@ -109,7 +93,8 @@ class RelayManager { return tx.receipt; } - /* Returns the gas limit to use as gasLimit parameter in execute function + /* NEEDS TO BE UPDATED + Returns the gas limit to use as gasLimit parameter in execute function + 5200 (isFeatureAuthorisedInVersionManager check) + 0 / 1000 / 4800 based on which contract implements getRequiredSignatures() + 2052 (getSignHash call) @@ -121,14 +106,8 @@ class RelayManager { Ignoring multiplication and comparison as that is <10 gas per operation */ - async getGasLimitRefund(_feature, _method, _params, _wallet, _signers, _gasPrice) { - let requiredSigsGas = 0; - const { contractName } = _feature.constructor; - if (contractName === "ApprovedTransfer" || contractName === "RecoveryManager") { - requiredSigsGas = 4800; - } else if (contractName === "GuardianManager") { - requiredSigsGas = 1000; - } + static async getGasLimitRefund(_module, _method, _params, _wallet, _signers, _gasPrice) { + const requiredSigsGas = 4800; // Estimate cost of checkAndUpdateUniqueness call let nonceCheckGas = 0; @@ -143,7 +122,7 @@ class RelayManager { let gasEstimateFeatureCall = 0; try { - gasEstimateFeatureCall = await _feature.contract.methods[_method](..._params).estimateGas({ from: this.relayerManager.address }); + gasEstimateFeatureCall = await _module.contract.methods[_method](..._params).estimateGas({ from: _module.address }); gasEstimateFeatureCall -= 21000; } catch (err) { // eslint-disable-line no-empty } finally { @@ -158,8 +137,8 @@ class RelayManager { } let refundGas = 0; - const methodData = _feature.contract.methods[_method](..._params).encodeABI(); - const requiredSignatures = await _feature.getRequiredSignatures(_wallet.address, methodData); + const methodData = _module.contract.methods[_method](..._params).encodeABI(); + const requiredSignatures = await _module.getRequiredSignatures(_wallet.address, methodData); // Relayer only refund when gasPrice > 0 and the owner is signing if (_gasPrice > 0 && requiredSignatures[1].toNumber() === 1) { @@ -169,7 +148,7 @@ class RelayManager { refundGas = 40000; } - // We can achieve better overall estimate if instead of adding a 50K buffer in gas calculation for relayer.execute + // We can achieve better overall estimate if instead of adding a 72k buffer in gas calculation for relayer.execute // we add token transfer cost selectively for token refunds. // In tests we are using a simple ERC20 transfer cost, however this varies by supported token, e.g. ZRX, BAT, DAI, USDC, or USDT // if (_refundToken !== ETH_TOKEN) { @@ -183,12 +162,11 @@ class RelayManager { return gasLimit; } - async debugGasLimits(_feature, _method, _params, _wallet, _signers) { + async debugGasLimits(_module, _method, _params, _wallet, _signers) { let requiredSigsGas = 0; // Get the owner signature requirements - const feature = await IFeature.at(_feature.address); - const methodData = _feature.contract.methods[_method](..._params).encodeABI(); - requiredSigsGas = await feature.getRequiredSignatures.estimateGas(_wallet.address, methodData); + const methodData = _module.contract.methods[_method](..._params).encodeABI(); + requiredSigsGas = await _module.getRequiredSignatures.estimateGas(_wallet.address, methodData); requiredSigsGas -= 21000; let ownerSigner = 0; @@ -198,7 +176,7 @@ class RelayManager { for (let index = 0; index < _signers.length; index += 1) { const signer = _signers[index]; - const isGuardian = await this.guardianStorage.isGuardian(_wallet.address, signer); + const isGuardian = await (await IGuardianStorage.at(this.guardianStorage)).isGuardian(_wallet.address, signer); if (signer === walletOwner) { ownerSigner += 1; } else if (isGuardian) { diff --git a/utils/utilities.js b/utils/utilities.js index f532db1c6..e9a505d3d 100644 --- a/utils/utilities.js +++ b/utils/utilities.js @@ -1,18 +1,21 @@ +/* global artifacts */ + const readline = require("readline"); const ethers = require("ethers"); const BN = require("bn.js"); const chai = require("chai"); -const { expect } = chai; -const ETH_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const WalletFactory = artifacts.require("WalletFactory"); + +const { assert, expect } = chai; +const ETH_TOKEN = ethers.constants.AddressZero; +const ZERO_BYTES = "0x"; -module.exports = { +const utilities = { ETH_TOKEN, - namehash(_name) { - return ethers.utils.namehash(_name); - }, + namehash: (name) => ethers.utils.namehash(name), sha3: (input) => { if (ethers.utils.isHexString(input)) { @@ -36,11 +39,11 @@ module.exports = { }); }), - async signOffchain(signers, from, to, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress) { - const messageHash = this.getMessageHash(from, to, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress); + signOffchain: async (signers, from, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress) => { + const messageHash = utilities.getMessageHash(from, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress); const signatures = await Promise.all( signers.map(async (signer) => { - const sig = await this.signMessage(messageHash, signer); + const sig = await utilities.signMessage(messageHash, signer); return sig.slice(2); }) ); @@ -49,12 +52,11 @@ module.exports = { return joinedSignatures; }, - getMessageHash(from, to, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress) { + getMessageHash: (from, value, data, chainId, nonce, gasPrice, gasLimit, refundToken, refundAddress) => { const message = `0x${[ "0x19", "0x00", from, - to, ethers.utils.hexZeroPad(ethers.utils.hexlify(value), 32), data, ethers.utils.hexZeroPad(ethers.utils.hexlify(chainId), 32), @@ -69,7 +71,7 @@ module.exports = { return messageHash; }, - async signMessage(message, signer) { + signMessage: async (message, signer) => { const sig = await web3.eth.sign(message, signer); let v = parseInt(sig.substring(130, 132), 16); if (v < 27) v += 27; @@ -79,45 +81,43 @@ module.exports = { personalSign: async (signHash, signer) => ethers.utils.joinSignature(signer.signingKey.signDigest(signHash)), - sortWalletByAddress(wallets) { - return wallets.sort((s1, s2) => { - const bn1 = ethers.BigNumber.from(s1); - const bn2 = ethers.BigNumber.from(s2); - if (bn1.lt(bn2)) return -1; - if (bn1.gt(bn2)) return 1; - return 0; - }); - }, + sortWalletByAddress: (wallets) => wallets.sort((s1, s2) => { + const bn1 = ethers.BigNumber.from(s1); + const bn2 = ethers.BigNumber.from(s2); + if (bn1.lt(bn2)) return -1; + if (bn1.gt(bn2)) return 1; + return 0; + }), // Parses the RelayerModule.execute receipt to decompose the success value of the transaction // and additionally if an error was raised in the sub-call to optionally return that - parseRelayReceipt(txReceipt) { + parseRelayReceipt: (txReceipt) => { const { args } = txReceipt.logs.find((e) => e.event === "TransactionExecuted"); let errorBytes; let error; - if (args.returnData) { + if (!args.success && args.returnData) { if (args.returnData.startsWith("0x08c379a0")) { // Remove the encoded error signatures 08c379a0 const noErrorSelector = `0x${args.returnData.slice(10)}`; const errorBytesArray = ethers.utils.defaultAbiCoder.decode(["bytes"], noErrorSelector); errorBytes = errorBytesArray[0]; // eslint-disable-line prefer-destructuring } else { - errorBytes = args.returnData; + errorBytes = args.returnData; console.log(errorBytes); } error = ethers.utils.toUtf8String(errorBytes); } return { success: args.success, error }; }, - async hasEvent(txReceipt, emitter, eventName) { - const event = await this.getEvent(txReceipt, emitter, eventName); + hasEvent: async (txReceipt, emitter, eventName) => { + const event = await utilities.getEvent(txReceipt, emitter, eventName); return expect(event, "Event does not exist in recept").to.exist; }, - async getEvent(txReceipt, emitter, eventName) { + getEvent: async (txReceipt, emitter, eventName) => { const receipt = await web3.eth.getTransactionReceipt(txReceipt.transactionHash); - const logs = await this.decodeLogs(receipt.logs, emitter, eventName); + const logs = await utilities.decodeLogs(receipt.logs, emitter, eventName); const event = logs.find((e) => e.event === eventName); return event; }, @@ -125,7 +125,7 @@ module.exports = { // Copied from https://github.com/OpenZeppelin/openzeppelin-test-helpers // This decodes longs for a single event type, and returns a decoded object in // the same form truffle-contract uses on its receipts - decodeLogs(logs, emitter, eventName) { + decodeLogs: (logs, emitter, eventName) => { let address; const { abi } = emitter; @@ -155,7 +155,7 @@ module.exports = { .map((decoded) => ({ event: eventName, args: decoded })); }, - versionFingerprint(modules) { + versionFingerprint: (modules) => { const concat = modules.map((module) => module.address).sort((m1, m2) => { const bn1 = ethers.BigNumber.from(m1); const bn2 = ethers.BigNumber.from(m2); @@ -170,49 +170,38 @@ module.exports = { return ethers.utils.keccak256(concat).slice(0, 10); }, - getRandomAddress() { - return ethers.Wallet.createRandom().address; - }, - - generateSaltValue() { - return ethers.utils.hexZeroPad( - ethers.BigNumber.from(ethers.utils.randomBytes(32)).toHexString(), - 32, - ); - }, + getRandomAddress: () => ethers.Wallet.createRandom().address, - async getBalance(account) { + getBalance: async (account) => { const balance = await web3.eth.getBalance(account); return new BN(balance); }, - async getTimestamp(blockNumber) { + getTimestamp: async (blockNumber) => { const blockN = !blockNumber ? "latest" : blockNumber; const { timestamp } = await web3.eth.getBlock(blockN); return timestamp; }, - async getChainId() { - // TODO: The web3 version packaged with truffle is 1.2.1 while the getChainId logic - // we need here was introduced in 1.2.2 - // Uncomment when https://github.com/trufflesuite/truffle/issues/2688#issuecomment-709879003 is resolved - // const chainId = await web3.eth.getChainId(); - // console.log("chainId", chainId) - // return chainId; - return 1895; - }, - - async web3GetClient() { - return new Promise((resolve, reject) => { - web3.eth.getNodeInfo((err, res) => { - if (err !== null) return reject(err); - return resolve(res); - }); + // TODO: The web3 version packaged with truffle is 1.2.1 while the getChainId logic + // we need here was introduced in 1.2.2 + // Uncomment when https://github.com/trufflesuite/truffle/issues/2688#issuecomment-709879003 is resolved + // NOTE: Although the above issue is resolve this is still blocking the truffle upgrade + // https://github.com/trufflesuite/ganache-cli/issues/702#issuecomment-723816610 + // const chainId = await web3.eth.getChainId(); + // console.log("chainId", chainId) + // return chainId; + getChainId: async () => 1895, + + web3GetClient: async () => new Promise((resolve, reject) => { + web3.eth.getNodeInfo((err, res) => { + if (err !== null) return reject(err); + return resolve(res); }); - }, + }), - async increaseTime(seconds) { - const client = await this.web3GetClient(); + increaseTime: async (seconds) => { + const client = await utilities.web3GetClient(); const p = new Promise((resolve, reject) => { if (client.indexOf("TestRPC") === -1) { console.warning("Client is not ganache-cli and cannot forward time"); @@ -249,15 +238,81 @@ module.exports = { return p; }, - async getNonceForRelay() { + getNonceForRelay: async () => { const block = await web3.eth.getBlockNumber(); const timestamp = new Date().getTime(); return `0x${ethers.utils.hexZeroPad(ethers.utils.hexlify(block), 16) .slice(2)}${ethers.utils.hexZeroPad(ethers.utils.hexlify(timestamp), 16).slice(2)}`; }, - async getAccount(index) { + getAccount: async (index) => { const accounts = await web3.eth.getAccounts(); return accounts[index]; + }, + + encodeFunctionCall: (method, params) => { + if (typeof method === "object") return web3.eth.abi.encodeFunctionCall(method, params); + if (typeof method === "string") { + const paramStart = method.indexOf("("); + const name = method.substring(0, paramStart); + const inputs = method + .substring(paramStart + 1, method.length - 1) + .split(",") + .map((type, idx) => ({ type, name: `arg${idx}` })); + return web3.eth.abi.encodeFunctionCall({ name, inputs, type: "function" }, params); + } + throw new Error(`Invalid method "${method}"`); + }, + + encodeTransaction: (to, value, data) => ({ to, value, data }), + + encodeCalls: (calls) => (Array.isArray(calls[0]) ? calls : [calls]).map(([instance, method, params = [], value = 0]) => utilities.encodeTransaction( + instance.address, value, instance.contract.methods[method](...params).encodeABI()) + ), + + addTrustedContact: async (wallet, target, module, securityPeriod) => { + const owner = await wallet.owner(); + await module.addToWhitelist(wallet.address, target, { from: owner }); + await utilities.increaseTime(securityPeriod + 2); + const isTrusted = await module.isWhitelisted(wallet.address, target); + assert.isTrue(isTrusted, "should be trusted after the security period"); + }, + + // set the relayer nonce to > 0 + initNonce: async (wallet, module, manager, securityPeriod) => { + const nonceInitialiser = await utilities.getAccount(8); + await utilities.addTrustedContact(wallet, nonceInitialiser, module, securityPeriod); + const owner = await wallet.owner(); + const transaction = utilities.encodeTransaction(nonceInitialiser, 1, ZERO_BYTES); + await manager.relay( + module, + "multiCall", + [wallet.address, [transaction]], + wallet, + [owner]); + const nonce = await module.getNonce(wallet.address); + assert.isTrue(nonce.gt(0), "nonce init failed"); + }, + + assertFailedWithError: (txReceipt, msg) => { + const { success, error } = utilities.parseRelayReceipt(txReceipt); + assert.isFalse(success); + assert.equal(error, msg); + }, + + generateSaltValue: () => ethers.utils.hexZeroPad(ethers.BigNumber.from(ethers.utils.randomBytes(20)).toHexString(), 20), + + createWallet: async (factoryAddress, owner, modules, guardian) => { + const salt = utilities.generateSaltValue(); + const managerSig = "0x"; + const factory = await WalletFactory.at(factoryAddress); + + const tx = await factory.createCounterfactualWallet( + owner, modules, guardian, salt, 0, ethers.constants.AddressZero, ZERO_BYTES, managerSig); + + const event = await utilities.getEvent(tx.receipt, factory, "WalletCreated"); + return event.args.wallet; } }; + +module.exports = utilities; diff --git a/utils/versions/development/latest.json b/utils/versions/development/latest.json index 253b71e24..0c8913d33 100644 --- a/utils/versions/development/latest.json +++ b/utils/versions/development/latest.json @@ -1 +1 @@ -{"modules":[{"address":"0xA2a1ccaB58a416459576764881380408555f0fAf","name":"VersionManager"}],"fingerprint":"0xd80bd999","version":"1.0.0","createdAt":1612975758} \ No newline at end of file +{"version":"2.5.0","createdAt":1619089886,"modules":[{"address":"0xc01A8D9662D4C2A1ef5d7A828642038D274C3FC4","name":"ArgentModule"}],"fingerprint":"0xfb59b1dc"} \ No newline at end of file diff --git a/utils/versions/kovan-fork/latest.json b/utils/versions/kovan-fork/latest.json deleted file mode 100644 index c34d05afc..000000000 --- a/utils/versions/kovan-fork/latest.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"1.5.2","createdAt":1574338831,"modules":[{"address":"0x6ECCdA0FecB562194d88634D27BBa71923Aea147","name":"GuardianManager"},{"address":"0xF550f98977e9c0428AD6C80718B8673f376982E6","name":"LockManager"},{"address":"0xaf37A95CAfc7C099b0456558a044599FDe479F0E","name":"RecoveryManager"},{"address":"0x8F10C308a7bC1BA961146b0F9F055184edfB62B8","name":"TokenExchanger"},{"address":"0x73c891Ea8dF8FDF7B11D6cb1293C5f027BBd98EC","name":"NftTransfer"},{"address":"0x9CC109e69AA1CB3716DB4354673E3d72A7887CF1","name":"MakerManager"},{"address":"0x4E411a5CCBdd3029a9e14251D6d1A0bC311734A3","name":"CompoundManager"},{"address":"0x263FF313cBE78CDbF6FAB9aC8Dd47727f58Bc65D","name":"UniswapManager"},{"address":"0x247E448D0b1B374eE1BaE4eC6032B45DD55e98F9","name":"TransferManager"},{"address":"0xB21D47fFc3f90314Faab6df5772A58694890e668","name":"ApprovedTransfer"},{"address":"0x69750794c8F3a6A584f850745aF3552158984b99","name":"MakerV2Manager"}],"fingerprint":"0xfbedb8f7"} \ No newline at end of file diff --git a/utils/versions/kovan/latest.json b/utils/versions/kovan/latest.json deleted file mode 100644 index 438fbccff..000000000 --- a/utils/versions/kovan/latest.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"1.5.1","createdAt":1573760786,"modules":[{"address":"0x96b8b73D55C713CEBfb1eee3D5F476766F679669","name":"GuardianManager"},{"address":"0x9b7fC8EF0a625451FA0F21422d2Dd4164f6cf82E","name":"LockManager"},{"address":"0x479120703c51aE2a242F1D04815289f8BB2CdAED","name":"RecoveryManager"},{"address":"0xd9CE60816ADF1E365f04Dea96AAF2775942C42C6","name":"TokenExchanger"},{"address":"0xCAb6F3134894C10Bd357335D00412e0586fcE85E","name":"NftTransfer"},{"address":"0x74BB0268ABf905C1Eb12f8300249070B56E76d77","name":"MakerManager"},{"address":"0xbf32494BedC3AcAccBb54F8560262A80609a1ABD","name":"CompoundManager"},{"address":"0x0960772cAf2Ff08764cF15F86c09fA4F20B0Bc9c","name":"UniswapManager"},{"address":"0xcE7968f585fCe79AD1161A766F13aBd3375a0185","name":"TransferManager"},{"address":"0xA825F25958F9C68573A3Ceb827345107CEdE0Aa8","name":"ApprovedTransfer"},{"address":"0x7bb678505B6729f40bB1BE34572685766009492f","name":"MakerV2Manager"}],"fingerprint":"0x56356b4e"} \ No newline at end of file