commit e4f5c4b8fdb3733457a203b998f61afd4b2b29b3
Author: nolash <dev@holbrook.no>
Date: Sun, 6 Dec 2020 16:57:26 +0100
Add tests
Diffstat:
13 files changed, 388 insertions(+), 0 deletions(-)
diff --git a/python/LICENSE b/python/LICENSE
@@ -0,0 +1,45 @@
+ Bprotocol Foundation (Bancor) LICENSE
+
+1. SUBJECT TO THE PROVISIONS SET FORTH HEREIN, INCLUDING “EFFECTIVE DATE”, YOU CAN
+ USE THIS CODE, FILE AND/OR SOFTWARE (“SOFTWARE”) ONLY IN CONNECTION WITH THE
+ BANCOR LIQUIDITY NETWORK AND/OR THE USE OF BNT ("PERMITTED USE"). ANY OTHER USE IS
+ PROHIBITED UNLESS THE USER SHALL RECEIVE AN EXPLICIT PRIOR WRITTEN APPROVAL FROM
+ BPROTOCOL FOUNDATION (BANCOR) TO DO SO (PLEASE CONTACT license@bancor.network IN
+ THIS REGARD), WHICH APPROVAL, IF GIVEN, MAY REQUIRE THE OBTAINMENT OF SEPARATE
+ LICENSE UNDER A DIFFERENT LICENSING MODEL. USING THIS SOFTWARE NOT IN THE FRAME OF
+ SUCH PERMITTED USE MAY, AMONG OTHERS, ALSO BREACH PATENT RIGHTS CONCERNING PATENTS
+ WHICH ARE EMBODIED/INCORPORATED/USED IN THIS SOFTWARE.
+
+2. ANY SUCH PERMITTED USE SHOULD ALSO COMPLY WITH THE TERMS BELOW.
+
+3. Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+A. Redistributions of source code must retain the above copyright notice, this list
+ of conditions and the following disclaimer.
+B. Redistributions in binary form must reproduce the above copyright notice, this
+ list of conditions and the following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+C. Neither the name of the copyright holder nor the names of its contributors may be
+ used to endorse or promote products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+EFFECTIVE DATE: THIS LICENSE SHALL APPLY ONLY TO SOFTWARE (OR ANY VERSION THEREOF),
+THAT HAS BEEN PUBLISHED AFTER THE DATE AND TIME THIS LICENSE HAS BEEN FIRST PUBLISHED
+(“EFFECTIVE DATE”); Any previous versions published prior to the effective date (“Older Versions”)
+shall remain licensed under the Apache License, Version 2.0 (the "Older Versions License");
+You may obtain a copy of the Older Version License at http://www.apache.org/licenses/LICENSE-2.0
+you may not use this file except in compliance with the Older Version License. Unless
+required by applicable law or agreed to in writing, Older Versions distributed under the
+Older Version License are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, either express or implied. See the Older Version License for the specific
+language governing permissions and limitations under the Older Version License.
diff --git a/python/setup.cfg b/python/setup.cfg
@@ -0,0 +1,32 @@
+[metadata]
+name = simple-multisig
+version = 0.0.1
+description = Simple approval escrow for ERC20 spend approvals
+author = Louis Holbrook
+author_email = dev@holbrook.no
+url = https://gitlab.com/nolash/simple-multisig
+keywords =
+ ethereum
+classifiers =
+ Programming Language :: Python :: 3
+ Operating System :: OS Independent
+ Development Status :: 3 - Alpha
+ Environment :: No Input/Output (Daemon)
+ Intended Audience :: Developers
+ License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
+ Topic :: Internet
+ #Topic :: Blockchain :: EVM
+license = GPL3
+licence_files =
+ LICENSE
+
+[options]
+python_requires = >= 3.6
+packages =
+ simple_multisig.runnable
+install_requires =
+ web3==5.12.2
+
+[options.entry_points]
+console_scripts =
+ simple-multisig-deploy = simple_multisig.runnable.deploy:main
diff --git a/python/setup.py b/python/setup.py
@@ -0,0 +1,4 @@
+from setuptools import setup
+
+setup(
+ )
diff --git a/python/simple_multisig/data/GiftableToken.abi.json b/python/simple_multisig/data/GiftableToken.abi.json
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"}],"name":"addMinter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"}],"name":"removeMinter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
diff --git a/python/simple_multisig/data/GiftableToken.bin b/python/simple_multisig/data/GiftableToken.bin
@@ -0,0 +1 @@
+60806040523480156200001157600080fd5b506040516200120338038062001203833981810160405260608110156200003757600080fd5b81019080805160405193929190846401000000008211156200005857600080fd5b838201915060208201858111156200006f57600080fd5b82518660018202830111640100000000821117156200008d57600080fd5b8083526020830192505050908051906020019080838360005b83811015620000c3578082015181840152602081019050620000a6565b50505050905090810190601f168015620000f15780820380516001836020036101000a031916815260200191505b50604052602001805160405193929190846401000000008211156200011557600080fd5b838201915060208201858111156200012c57600080fd5b82518660018202830111640100000000821117156200014a57600080fd5b8083526020830192505050908051906020019080838360005b838110156200018057808201518184015260208101905062000163565b50505050905090810190601f168015620001ae5780820380516001836020036101000a031916815260200191505b5060405260200180519060200190929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082600290805190602001906200021a929190620002af565b50816003908051906020019062000233929190620002af565b5080600460006101000a81548160ff021916908360ff16021790555060018060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555050505062000365565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282620002e7576000855562000333565b82601f106200030257805160ff191683800117855562000333565b8280016001018555821562000333579182015b828111156200033257825182559160200191906001019062000315565b5b50905062000342919062000346565b5090565b5b808211156200036157600081600090555060010162000347565b5090565b610e8e80620003756000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c806355b6ed5c1161007157806355b6ed5c146102bd57806370a082311461033557806395d89b411461038d578063983b2d5614610410578063a0712d681461046a578063a9059cbb146104ae576100b4565b806306fdde03146100b9578063095ea7b31461013c57806318160ddd146101a057806323b872dd146101be5780633092afd514610242578063313ce5671461029c575b600080fd5b6100c1610512565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6101886004803603604081101561015257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506105b0565b60405180821515815260200191505060405180910390f35b6101a86106ab565b6040518082815260200191505060405180910390f35b61022a600480360360608110156101d457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106b1565b60405180821515815260200191505060405180910390f35b6102846004803603602081101561025857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109a8565b60405180821515815260200191505060405180910390f35b6102a4610a9a565b604051808260ff16815260200191505060405180910390f35b61031f600480360360408110156102d357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610aad565b6040518082815260200191505060405180910390f35b6103776004803603602081101561034b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ad2565b6040518082815260200191505060405180910390f35b610395610aea565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d55780820151818401526020810190506103ba565b50505050905090810190601f1680156104025780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104526004803603602081101561042657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610b88565b60405180821515815260200191505060405180910390f35b6104966004803603602081101561048057600080fd5b8101908080359060200190929190505050610c43565b60405180821515815260200191505060405180910390f35b6104fa600480360360408110156104c457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610d01565b60405180821515815260200191505060405180910390f35b60028054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156105a85780601f1061057d576101008083540402835291602001916105a8565b820191906000526020600020905b81548152906001019060200180831161058b57829003601f168201915b505050505081565b600081600760003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b60055481565b600081600760008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561073c57600080fd5b81600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561078857600080fd5b81600760008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205403600760008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600660008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055503373ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167f5f7542858008eeb041631f30e6109ae94b83a58e9a58261dd2c42c508850f939856040518082815260200191505060405180910390a4600190509392505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610a3057508173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b610a3957600080fd5b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555060019050919050565b600460009054906101000a900460ff1681565b6007602052816000526040600020602052806000526040600020600091509150505481565b60066020528060005260406000206000915090505481565b60038054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610b805780601f10610b5557610100808354040283529160200191610b80565b820191906000526020600020905b815481529060010190602001808311610b6357829003601f168201915b505050505081565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610be357600080fd5b60018060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555060019050919050565b6000600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16610c9b57600080fd5b81600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508160056000828254019250508190555060019050919050565b600081600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610d4f57600080fd5b81600660003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555081600660008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a3600190509291505056fea2646970667358221220c0d6d8018b8639a774d77580663d7ecd2fc2417fbfc966cd14a6c11bbf21f02d64736f6c63430007050033
+\ No newline at end of file
diff --git a/python/simple_multisig/data/SimpleMultisig.abi.json b/python/simple_multisig/data/SimpleMultisig.abi.json
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address[]","name":"_approvers","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"serial","type":"uint256"}],"name":"NewExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"serial","type":"uint256"}],"name":"NewRejection","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_sender","type":"address"},{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"address","name":"_token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"NewRequest","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approvers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"execute","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"reject","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"request","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requests","outputs":[{"internalType":"uint256","name":"serial","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"serial","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
diff --git a/python/simple_multisig/data/SimpleMultisig.bin b/python/simple_multisig/data/SimpleMultisig.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50604051610db0380380610db08339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b8382019150602082018581111561006957600080fd5b825186602082028301116401000000008211171561008657600080fd5b8083526020830192505050908051906020019060200280838360005b838110156100bd5780820151818401526020810190506100a2565b5050505090500160405250505060005b815181101561014e576001600260008484815181106100e857fe5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555080806001019150506100cd565b50600160008190555050610c49806101676000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80630a144391146100675780636c355091146100c157806381d12c5814610143578063b8adaa11146101e3578063f6d713dd14610227578063fe0d94c114610245575b600080fd5b6100a96004803603602081101561007d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610289565b60405180821515815260200191505060405180910390f35b61012d600480360360608110156100d757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506102a9565b6040518082815260200191505060405180910390f35b61016f6004803603602081101561015957600080fd5b81019080803590602001909291905050506104b7565b604051808681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019550505050505060405180910390f35b61020f600480360360208110156101f957600080fd5b810190808035906020019092919050505061054d565b60405180821515815260200191505060405180910390f35b61022f6108ad565b6040518082815260200191505060405180910390f35b6102716004803603602081101561025b57600080fd5b81019080803590602001909291905050506108b3565b60405180821515815260200191505060405180910390f35b60026020528060005260406000206000915054906101000a900460ff1681565b600080600160008054815260200190815260200160002090506000548160000181905550848160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550338160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838160030160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082816004018190555060008081548092919060010191905055508060030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168160020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168260010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fb609ae609609ee99268d05bc1371102cafe8d6b964bf082439ab16be2a01c87c84600401548560000154604051808381526020018281526020019250505060405180910390a480600001549150509392505050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060040154905085565b6000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166105a557600080fd5b600060016000848152602001908152602001600020905060008160000154116105cd57600080fd5b600060608260030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168360010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168460010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168560040154604051602401808473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106107555780518252602082019150602081019050602083039250610732565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146107b7576040519150601f19603f3d011682016040523d82523d6000602084013e6107bc565b606091505b5091509150816107c857fe5b7f607c56ea76669e46679e89091227c1a3f91841a4cf7588d5a6c84555fb59fdfd83600001546040518082815260200191505060405180910390a160016000846000015481526020019081526020016000206000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556003820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560048201600090555050819350505050919050565b60005481565b6000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1661090b57600080fd5b6000600160008481526020019081526020016000209050600081600001541161093357600080fd5b600060608260030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168360010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168460020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168560040154604051602401808473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610abb5780518252602082019150602081019050602083039250610a98565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610b1d576040519150601f19603f3d011682016040523d82523d6000602084013e610b22565b606091505b509150915081610b2e57fe5b7fa445ec1ee84565906bc8646dcb62f6d1a75fc7a782a03e72220fb97f60ba89ab83600001546040518082815260200191505060405180910390a160016000846000015481526020019081526020016000206000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556003820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556004820160009055505081935050505091905056fea2646970667358221220602e22c4a3c6f16979c452060dfbb623b84957d760a0bda14e3d5356f5d4ca4064736f6c63430007050033
+\ No newline at end of file
diff --git a/python/simple_multisig/runnable/deploy.py b/python/simple_multisig/runnable/deploy.py
@@ -0,0 +1,65 @@
+"""Deploys simple multisig contract
+
+.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
+.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
+
+"""
+
+# standard imports
+import os
+import json
+import argparse
+import logging
+
+# third-party imports
+import web3
+
+
+logging.basicConfig(level=logging.WARNING)
+logg = logging.getLogger()
+
+logging.getLogger('web3').setLevel(logging.WARNING)
+logging.getLogger('urllib3').setLevel(logging.WARNING)
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
+argparser.add_argument('-a', '--approver', dest='a', action='append', type=str, help='approver to add')
+argparser.add_argument('--contracts-dir', dest='contracts_dir', default='.', help='Directory containing bytecode and abi')
+argparser.add_argument('-v', action='store_true', help='Be verbose')
+args = argparser.parse_args()
+
+if args.v:
+ logg.setLevel(logging.DEBUG)
+
+def main():
+ w3 = web3.Web3(web3.Web3.HTTPProvider(args.p))
+
+ f = open(os.path.join(args.contracts_dir, 'SimpleMultisig.abi.json'), 'r')
+ abi = json.load(f)
+ f.close()
+
+ f = open(os.path.join(args.contracts_dir, 'SimpleMultisig.bin'), 'r')
+ bytecode = f.read()
+ f.close()
+
+ w3.eth.defaultAccount = w3.eth.accounts[0]
+
+ c = w3.eth.contract(abi=abi, bytecode=bytecode)
+
+ approvers = [w3.eth.accounts[0]]
+ if args.a != None:
+ for a in args.a:
+ approvers.append(a)
+
+ tx_hash = c.constructor(approvers).transact({'from': w3.eth.accounts[0]})
+
+ rcpt = w3.eth.getTransactionReceipt(tx_hash)
+
+ address = rcpt.contractAddress
+ #c = w3.eth.contract(abi=abi, address=address)
+
+ print(address)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python/tests/test_app.py b/python/tests/test_app.py
@@ -0,0 +1,150 @@
+import os
+import unittest
+import json
+import logging
+
+import web3
+import eth_tester
+import eth_abi
+
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+logging.getLogger('web3').setLevel(logging.WARNING)
+logging.getLogger('eth.vm').setLevel(logging.WARNING)
+
+testdir = os.path.dirname(__file__)
+
+
+class Test(unittest.TestCase):
+
+ contract = None
+
+ def setUp(self):
+ eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
+ 'gas_limit': 9000000,
+ })
+
+ f = open(os.path.join(testdir, '../simple_multisig/data/SimpleMultisig.bin'), 'r')
+ bytecode = f.read()
+ f.close()
+
+ f = open(os.path.join(testdir, '../simple_multisig/data/SimpleMultisig.abi.json'), 'r')
+ self.abi_wallet = json.load(f)
+ f.close()
+
+ backend = eth_tester.PyEVMBackend(eth_params)
+ self.eth_tester = eth_tester.EthereumTester(backend)
+ provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
+ self.w3 = web3.Web3(provider)
+ c = self.w3.eth.contract(abi=self.abi_wallet, bytecode=bytecode)
+ tx_hash = c.constructor([self.w3.eth.accounts[0]]).transact({'from': self.w3.eth.accounts[0]})
+
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+
+ self.address_wallet = r.contractAddress
+
+
+ f = open(os.path.join(testdir, '../simple_multisig/data/GiftableToken.bin'), 'r')
+ bytecode = f.read()
+ f.close()
+
+ f = open(os.path.join(testdir, '../simple_multisig/data/GiftableToken.abi.json'), 'r')
+ self.abi_token = json.load(f)
+ f.close()
+
+ c = self.w3.eth.contract(abi=self.abi_token, bytecode=bytecode)
+ tx_hash = c.constructor('Foo Token', 'FOO', 18).transact({'from': self.w3.eth.accounts[0]})
+
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+
+ self.address_token = r.contractAddress
+ c = self.w3.eth.contract(abi=self.abi_token, address=self.address_token)
+
+ tx_hash = c.functions.mint(1000).transact({'from': self.w3.eth.accounts[0]})
+ tx_hash = c.functions.transfer(self.w3.eth.accounts[1], 100).transact({'from': self.w3.eth.accounts[0]})
+ tx_hash = c.functions.transfer(self.w3.eth.accounts[2], 100).transact({'from': self.w3.eth.accounts[0]})
+
+
+ def tearDown(self):
+ pass
+
+
+ def test_basic(self):
+ c = self.w3.eth.contract(abi=self.abi_wallet, address=self.address_wallet)
+ self.assertTrue(c.functions.approvers(self.w3.eth.accounts[0]).call())
+ self.assertFalse(c.functions.approvers(self.w3.eth.accounts[1]).call())
+
+
+ def test_approval_missing(self):
+ w = self.w3.eth.contract(abi=self.abi_wallet, address=self.address_wallet)
+ t = self.w3.eth.contract(abi=self.abi_token, address=self.address_token)
+
+ tx_hash = w.functions.request(self.w3.eth.accounts[3], t.address, 10).transact({'from': self.w3.eth.accounts[2]})
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+
+ topic_match = 'b609ae609609ee99268d05bc1371102cafe8d6b964bf082439ab16be2a01c87c'
+ log = r.logs[0]
+ topic = log.topics[0]
+ self.assertEqual(topic.hex()[2:], topic_match)
+ serial = int(log.data[66:], 16)
+ self.eth_tester.mine_block()
+
+ with self.assertRaises(TypeError):
+ w.functions.execute(serial).transact({'from': self.w3.eth.accounts[0]})
+
+
+ def test_approval(self):
+ w = self.w3.eth.contract(abi=self.abi_wallet, address=self.address_wallet)
+ t = self.w3.eth.contract(abi=self.abi_token, address=self.address_token)
+
+ t.functions.approve(w.address, 10).transact({'from': self.w3.eth.accounts[2]})
+ self.eth_tester.mine_block()
+
+ tx_hash = w.functions.request(self.w3.eth.accounts[3], t.address, 10).transact({'from': self.w3.eth.accounts[2]})
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+
+ topic_match = 'b609ae609609ee99268d05bc1371102cafe8d6b964bf082439ab16be2a01c87c'
+ log = r.logs[0]
+ topic = log.topics[0]
+ self.assertEqual(topic.hex()[2:], topic_match)
+ serial = int(log.data[66:], 16)
+ self.eth_tester.mine_block()
+
+ with self.assertRaises(eth_abi.exceptions.InsufficientDataBytes):
+ w.functions.execute(serial).transact({'from': self.w3.eth.accounts[3]})
+
+ tx_hashh = w.functions.execute(serial).transact({'from': self.w3.eth.accounts[0]})
+ r = self.w3.eth.getTransactionReceipt(tx_hashh)
+
+ self.assertEqual(t.functions.balanceOf(self.w3.eth.accounts[2]).call(), 90)
+ self.assertEqual(t.functions.balanceOf(self.w3.eth.accounts[3]).call(), 10)
+
+
+ def test_rejection(self):
+ w = self.w3.eth.contract(abi=self.abi_wallet, address=self.address_wallet)
+ t = self.w3.eth.contract(abi=self.abi_token, address=self.address_token)
+
+ t.functions.approve(w.address, 10).transact({'from': self.w3.eth.accounts[2]})
+ self.eth_tester.mine_block()
+
+ tx_hash = w.functions.request(self.w3.eth.accounts[3], t.address, 10).transact({'from': self.w3.eth.accounts[2]})
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+
+ topic_match = 'b609ae609609ee99268d05bc1371102cafe8d6b964bf082439ab16be2a01c87c'
+ log = r.logs[0]
+ topic = log.topics[0]
+ self.assertEqual(topic.hex()[2:], topic_match)
+ serial = int(log.data[66:], 16)
+ self.eth_tester.mine_block()
+
+ tx_hash = w.functions.reject(serial).transact({'from': self.w3.eth.accounts[0]})
+ r = self.w3.eth.getTransactionReceipt(tx_hash)
+ print('logs {}'.format(r.logs))
+
+ self.assertEqual(t.functions.balanceOf(self.w3.eth.accounts[2]).call(), 100)
+ self.assertEqual(t.functions.balanceOf(self.w3.eth.accounts[3]).call(), 0)
+ self.assertEqual(t.functions.allowances(self.w3.eth.accounts[2], w.address).call(), 0)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/solidity/Makefile b/solidity/Makefile
@@ -0,0 +1,8 @@
+all:
+ solc --bin SimpleMultisig.sol | awk 'NR>3' > SimpleMultisig.bin
+ truncate -s -1 SimpleMultisig.bin
+ solc --abi SimpleMultisig.sol | awk 'NR>3' > SimpleMultisig.abi.json
+
+install: all
+ cp -v *{json,bin} ../python/simple_multisig/data/
+
diff --git a/solidity/SimpleMultisig.abi.json b/solidity/SimpleMultisig.abi.json
@@ -0,0 +1 @@
+[{"inputs":[{"internalType":"address[]","name":"_approvers","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"serial","type":"uint256"}],"name":"NewExecution","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"serial","type":"uint256"}],"name":"NewRejection","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_sender","type":"address"},{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"address","name":"_token","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"NewRequest","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"approvers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"execute","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_serial","type":"uint256"}],"name":"reject","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"request","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"requests","outputs":[{"internalType":"uint256","name":"serial","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"serial","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
diff --git a/solidity/SimpleMultisig.bin b/solidity/SimpleMultisig.bin
@@ -0,0 +1 @@
+608060405234801561001057600080fd5b50604051610db0380380610db08339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b8382019150602082018581111561006957600080fd5b825186602082028301116401000000008211171561008657600080fd5b8083526020830192505050908051906020019060200280838360005b838110156100bd5780820151818401526020810190506100a2565b5050505090500160405250505060005b815181101561014e576001600260008484815181106100e857fe5b602002602001015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff02191690831515021790555080806001019150506100cd565b50600160008190555050610c49806101676000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c80630a144391146100675780636c355091146100c157806381d12c5814610143578063b8adaa11146101e3578063f6d713dd14610227578063fe0d94c114610245575b600080fd5b6100a96004803603602081101561007d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610289565b60405180821515815260200191505060405180910390f35b61012d600480360360608110156100d757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506102a9565b6040518082815260200191505060405180910390f35b61016f6004803603602081101561015957600080fd5b81019080803590602001909291905050506104b7565b604051808681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019550505050505060405180910390f35b61020f600480360360208110156101f957600080fd5b810190808035906020019092919050505061054d565b60405180821515815260200191505060405180910390f35b61022f6108ad565b6040518082815260200191505060405180910390f35b6102716004803603602081101561025b57600080fd5b81019080803590602001909291905050506108b3565b60405180821515815260200191505060405180910390f35b60026020528060005260406000206000915054906101000a900460ff1681565b600080600160008054815260200190815260200160002090506000548160000181905550848160020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550338160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838160030160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555082816004018190555060008081548092919060010191905055508060030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168160020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168260010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fb609ae609609ee99268d05bc1371102cafe8d6b964bf082439ab16be2a01c87c84600401548560000154604051808381526020018281526020019250505060405180910390a480600001549150509392505050565b60016020528060005260406000206000915090508060000154908060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060040154905085565b6000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff166105a557600080fd5b600060016000848152602001908152602001600020905060008160000154116105cd57600080fd5b600060608260030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168360010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168460010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168560040154604051602401808473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b602083106107555780518252602082019150602081019050602083039250610732565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146107b7576040519150601f19603f3d011682016040523d82523d6000602084013e6107bc565b606091505b5091509150816107c857fe5b7f607c56ea76669e46679e89091227c1a3f91841a4cf7588d5a6c84555fb59fdfd83600001546040518082815260200191505060405180910390a160016000846000015481526020019081526020016000206000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556003820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905560048201600090555050819350505050919050565b60005481565b6000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1661090b57600080fd5b6000600160008481526020019081526020016000209050600081600001541161093357600080fd5b600060608260030160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168360010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168460020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168560040154604051602401808473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040518082805190602001908083835b60208310610abb5780518252602082019150602081019050602083039250610a98565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610b1d576040519150601f19603f3d011682016040523d82523d6000602084013e610b22565b606091505b509150915081610b2e57fe5b7fa445ec1ee84565906bc8646dcb62f6d1a75fc7a782a03e72220fb97f60ba89ab83600001546040518082815260200191505060405180910390a160016000846000015481526020019081526020016000206000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556003820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556004820160009055505081935050505091905056fea2646970667358221220602e22c4a3c6f16979c452060dfbb623b84957d760a0bda14e3d5356f5d4ca4064736f6c63430007050033
+\ No newline at end of file
diff --git a/solidity/SimpleMultisig.sol b/solidity/SimpleMultisig.sol
@@ -0,0 +1,75 @@
+pragma solidity >=0.6.12;
+
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+contract SimpleMultisig {
+ struct Transaction {
+ uint256 serial;
+ address sender;
+ address recipient;
+ address token;
+ uint256 value;
+ }
+ uint256 public serial;
+ mapping(uint256 => Transaction) public requests;
+ mapping(address => bool) public approvers;
+
+ event NewRequest(address indexed _sender, address indexed _recipient, address indexed _token, uint256 _value, uint256 _serial);
+ event NewExecution(uint256 serial);
+ event NewRejection(uint256 serial);
+
+ constructor(address[] memory _approvers) public {
+ for (uint i = 0; i < _approvers.length; i++) {
+ approvers[_approvers[i]] = true;
+ }
+ serial = 1;
+ }
+
+ function request(address _recipient, address _token, uint256 _value) public returns (uint256) {
+ Transaction storage txx = requests[serial];
+
+ txx.serial = serial;
+ txx.recipient = _recipient;
+ txx.sender = msg.sender;
+ txx.token = _token;
+ txx.value = _value;
+
+ serial++;
+
+ emit NewRequest(txx.sender, txx.recipient, txx.token, txx.value, txx.serial);
+
+ return txx.serial;
+ }
+
+ function execute(uint256 _serial) public returns (bool) {
+ require(approvers[msg.sender], 'ERR_NO_ACCESS');
+
+ Transaction storage txx = requests[_serial];
+ require(txx.serial > 0, 'ERR_INVALID_REQUEST');
+
+ (bool success, bytes memory _) = txx.token.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", txx.sender, txx.recipient, txx.value));
+ assert(success);
+
+ emit NewExecution(txx.serial);
+
+ delete requests[txx.serial];
+
+ return success;
+ }
+
+ function reject(uint256 _serial) public returns (bool) {
+ require(approvers[msg.sender], 'ERR_NO_ACCESS');
+
+ Transaction storage txx = requests[_serial];
+ require(txx.serial > 0, 'ERR_INVALID_REQUEST');
+
+ (bool success, bytes memory _) = txx.token.call(abi.encodeWithSignature("transferFrom(address,address,uint256)", txx.sender, txx.sender, txx.value));
+ assert(success);
+
+ emit NewRejection(txx.serial);
+
+ delete requests[txx.serial];
+
+ return success;
+ }
+}